การออกแบบการจัดการข้อผิดพลาดในระบบคล้าย Unix ที่มีมาหลายทศวรรษยังคงสร้างความท้าทายอย่างมากในการแก้ไขบั๊กสำหรับนักพัฒนาที่ทำงานกับ Linux kernel การอภิปรายล่าสุดในชุมชนนักพัฒนาได้เน้นย้ำถึงความยุ่งยากที่ยังคงมีอยู่ในการติดตามแหล่งที่มาของข้อผิดพลาดระบบ โดยเฉพาะเมื่อต้องจัดการกับการดำเนินงาน kernel ที่ซับซ้อน
เซสชันการแก้ไขบั๊กหลายสัปดาห์สำหรับข้อผิดพลาดง่าย ๆ
หนึ่งในตัวอย่างที่โดดเด่นที่สุดที่นักพัฒนาแบ่งปันกันเกี่ยวข้องกับกรณีลูกค้าที่ system call การเขียนส่งคืน ENOSPC (No space left on device) แม้ว่าระบบไฟล์จะดูเหมือนมีพื้นที่เพียงพอ การสืบสวนเพื่อหาว่าข้อผิดพลาดนี้เกิดขึ้นจากที่ไหนภายใน kernel ใช้เวลาหลายสัปดาห์ในการแก้ไข สิ่งนี้เน้นย้ำถึงข้อจำกัดพื้นฐานในวิธีที่ Linux จัดการการรายงานข้อผิดพลาด - ระบบไม่บันทึกว่าข้อผิดพลาดถูกตั้งค่าหรือเกิดขึ้นที่ไหนภายใน kernel
ความท้าทายเกิดจากวิธีที่ข้อผิดพลาดแพร่กระจายผ่านโค้ด kernel ไม่เหมือนภาษาโปรแกรมมิ่งระดับสูงที่อาจมีการจัดการข้อยกเว้นแบบมีโครงสร้าง Linux kernel มักจะส่งคืนข้อผิดพลาดเป็นค่าลบ ทำให้ยากต่อการติดตามจุดที่แน่นอนที่ปัญหาเกิดขึ้นครั้งแรก นักพัฒนามักจะต้องเพิ่มคำสั่งพิมพ์ดีบักจำนวนมากทั่วโค้ด kernel ซึ่งต้องการการคอมไพล์ kernel ใหม่และรีบูตระบบระหว่างการพยายามแก้ไขบั๊กแต่ละครั้ง
*ENOSPC: รหัสข้อผิดพลาดระบบที่หมายถึง No space left on device มักจะส่งคืนเมื่ออุปกรณ์จัดเก็บข้อมูลหมดพื้นที่ที่ใช้ได้
การอภิปรายปรัชญาการออกแบบ errno
รากเหง้าของความยากลำบากในการแก้ไขบั๊กเหล่านี้ย้อนกลับไปถึงปรัชญาการออกแบบ Unix ดั้งเดิมจากทศวรรษ 1970 โดยเฉพาะการใช้ตัวแปรโกลบอล errno ระบบนี้ได้รับการออกแบบโดยคำนึงถึงประสิทธิภาพ - errno จะถูกตั้งค่าเฉพาะเมื่อเกิดข้อผิดพลาด และ system call ที่สำเร็จจะไม่ล้างมัน วิธีการนี้ลดค่าใช้จ่ายในระบบที่มีทรัพยากรจำกัดเช่น PDP-11
อย่างไรก็ตาม ตัวเลือกการออกแบบนี้ล้าสมัยไปแล้ว นักพัฒนาสมัยใหม่โต้แย้งว่าแม้ว่าวิธีการนี้จะสมเหตุสมผลในทศวรรษ 1970 เมื่อทรัพยากรคอมพิวเตอร์มีจำกัดอย่างมาก แต่มันสร้างความซับซ้อนที่ไม่จำเป็นในสภาพแวดล้อมปัจจุบัน ลักษณะโกลบอลของ errno อาจนำไปสู่สถานการณ์ที่ข้อผิดพลาดก่อนหน้านี้ปิดบังหรือทำให้เกิดความสับสนในความพยายามแก้ไขบั๊กปัจจุบัน โดยเฉพาะในแอปพลิเคชันที่ซับซ้อนที่มี system call หลายตัว
เรามีกรณีลูกค้าจริงเมื่อเร็ว ๆ นี้ที่ write call ส่งคืน ENOSPC แม้ว่าระบบไฟล์จะดูเหมือนไม่ได้หมดพื้นที่ และการค้นหาสถานที่ที่ข้อผิดพลาดนั้นเกิดขึ้นเป็นการเดินทางหลายสัปดาห์
ตัวอย่างของ System Calls ที่มีปัญหา
- getpriority(): สามารถคืนค่า -1 ได้อย่างถูกต้องเมื่อสำเร็จ จำเป็นต้องตรวจสอบ errno
- sched_yield(): สามารถคืนค่า -1 ได้อย่างถูกต้องเมื่อสำเร็จ จำเป็นต้องตรวจสอบ errno
- strtol(): คืนค่า LONG_MAX ทั้งในกรณี overflow และค่าสูงสุดที่ถูกต้อง
- getservbyname(): คืนค่า NULL ทั้งในกรณีเกิดข้อผิดพลาดและเมื่อไม่พบรายการ
วิธีการทางเลือกและโซลูชันสมัยใหม่
การอภิปรายได้เปิดเผยว่านักพัฒนาบางคนได้พบวิธีแก้ปัญหาโดยใช้เครื่องมือแก้ไขบั๊ก kernel และซอฟต์แวร์วิเคราะห์แบบคงที่ เครื่องมือเช่น Coverity, clang's static analyzer และ cppcheck สามารถจับบั๊กที่เกี่ยวข้องกับ errno บางตัวได้ระหว่างการพัฒนา อย่างไรก็ตาม โซลูชันเหล่านี้ต้องการการตั้งค่าเพิ่มเติมและความเชี่ยวชาญที่ทีมพัฒนาหลายทีมขาด
น่าสนใจที่ไลบรารี pthread ในระบบ POSIX จริง ๆ แล้วใช้วิธีการที่ทันสมัยกว่า - ฟังก์ชัน pthread ส่งคืนข้อผิดพลาดโดยตรงแทนที่จะใช้ตัวแปรโกลบอล errno สิ่งนี้แสดงให้เห็นว่าแม้ในระบบเดียวกัน ก็มีการยอมรับว่ามีรูปแบบการจัดการข้อผิดพลาดที่ดีกว่าอยู่
นักพัฒนาบางคนยังสังเกตว่า system call บางตัวเช่น getpriority() และ sched_yield() สามารถส่งคืน -1 เมื่อสำเร็จได้อย่างถูกต้อง ทำให้การตรวจจับข้อผิดพลาดซับซ้อนยิ่งขึ้นและต้องการการตรวจสอบ errno อย่างระมัดระวัง
เครื่องมือดีบักข้อผิดพลาดทั่วไปใน Linux
- Coverity: เครื่องมือวิเคราะห์แบบคงที่ที่สามารถตรวจจับข้อบกพร่องที่เกี่ยวข้องกับ errno ด้วยการเปิดใช้งาน MISRA หรือ CERT checkers
- clang static analyzer: การวิเคราะห์ซอร์สโค้ดแบบลึกผ่านคำสั่ง scan-build
- cppcheck: เครื่องมือวิเคราะห์แบบคงที่แบบโอเพนซอร์สสำหรับโค้ด C/C++
- pc-lint: เครื่องมือวิเคราะห์แบบคงที่เชิงพาณิชย์
- PVS-Studio: เครื่องมือวิเคราะห์แบบคงที่เชิงพาณิชย์ที่มีความสามารถในการตรวจจับข้อผิดพลาด errno
เส้นทางไปข้างหน้า
แม้ว่าการเปลี่ยนแปลงพฤติกรรม Unix พื้นฐานจะทำลายซอฟต์แวร์ที่มีอยู่หลายทศวรรษ การอภิปรายของชุมชนแสดงให้เห็นว่ามีการยอมรับที่เพิ่มขึ้นว่าจำเป็นต้องมีเครื่องมือติดตามและแก้ไขบั๊กที่ดีกว่า Kernel ขาดวัฒนธรรมการบันทึกดีบักอย่างครอบคลุมเมื่อเกิดข้อผิดพลาด ทำให้นักพัฒนาต้องติดตั้งโค้ดด้วยตนเองระหว่างการแก้ไขปัญหา
การอภิปรายสะท้อนถึงความตึงเครียดที่กว้างขึ้นในการพัฒนาซอฟต์แวร์ระหว่างการรักษาความเข้ากันได้แบบย้อนหลังและการนำวิธีการที่ทันสมัยและเป็นมิตรกับนักพัฒนามาใช้ เมื่อต้นทุนการบำรุงรักษาซอฟต์แวร์ยังคงเพิ่มขึ้น ประโยชน์ด้านประสิทธิภาพที่เดิมใช้เป็นเหตุผลสำหรับการตัดสินใจออกแบบเหล่านี้อาจไม่สามารถชดเชยความซับซ้อนในการแก้ไขบั๊กได้อีกต่อไป
ในตอนนี้ นักพัฒนาที่ทำงานกับโค้ด Linux kernel ต้องยังคงนำทางผ่านความท้าทายเหล่านี้โดยใช้เครื่องมือและเทคนิคที่มีอยู่ ในขณะที่หวังว่าจะมีการปรับปรุงโครงสร้างพื้นฐานการแก้ไขบั๊ก kernel อย่างค่อยเป็นค่อยไป