ประสบการณ์รันระบบปฏิบัติการตระกูล Unix อย่าง Xv6 ด้วย CPU และคอมไพเลอร์ C ที่สร้างเองที่บ้าน
(fuel.edby.coffee)- แชร์ประสบการณ์จากโปรเจ็กต์สมัยมหาวิทยาลัยที่พยายามพอร์ตระบบปฏิบัติการ Xv6 โดยใช้ CPU ที่ออกแบบเองบนพื้นฐาน RISC ISA และ คอมไพเลอร์ C ที่พัฒนาขึ้นเอง
- โปรเจ็กต์นี้ดำเนินไปในรูปแบบที่สร้างทุกองค์ประกอบขึ้นเอง ไม่ว่าจะเป็น การออกแบบ CPU, การพัฒนาคอมไพเลอร์ C (Ucc), และการพอร์ต Xv6
- ทีมได้ท้าทายตัวเองด้วยการออกแบบ ฟังก์ชันฮาร์ดแวร์และซอฟต์แวร์หลัก เช่น interrupt, virtual memory, cache เพื่อให้ OS ทำงานได้
- ระหว่างการพอร์ต Xv6 ต้องเผชิญกับ ปัญหาด้าน portability, ความยากในการดีบัก, บั๊กของ cache และอุปสรรคอีกมากมาย แต่สุดท้ายก็แก้ไขได้ด้วยตนเอง
- ประสบความสำเร็จในการรัน แอปแบบโต้ตอบอย่าง SL, Minesweeper, 2048 รวมถึงโปรแกรม ray-tracing ทำให้ได้ทั้งความสำเร็จเชิงการศึกษาและเทคนิคอย่างมาก
ภาพรวมของโปรเจ็กต์
- โปรเจ็กต์นี้เป็นหนึ่งในงานทดลองเด่นของภาควิชาสารสนเทศศาสตร์ Tokyo University โดยมุ่งให้ผู้เรียนได้สะสมประสบการณ์ด้านหน่วยประมวลผลกลาง (CPU), การออกแบบฮาร์ดแวร์, การสร้างคอมไพเลอร์ และการรันแอปพลิเคชัน
- เป้าหมายของโปรเจ็กต์คือการนำ ISA ของ CPU ที่ออกแบบเอง ไปใช้งานจริงบน FPGA สร้าง C toolchain (Ucc) สำหรับมัน และขยายไปสู่การ พอร์ตระบบปฏิบัติการ อย่าง Xv6
- กระบวนการทดลองส่วนใหญ่ดำเนินไปในรูปแบบ การเรียนรู้ด้วยตนเอง
การทดลอง CPU และโจทย์งาน
- ทีมขนาด 4–5 คนแต่ละทีมร่วมกันออกแบบ สถาปัตยกรรม CPU ใหม่ นำไปใช้งานบน FPGA และพัฒนาคอมไพเลอร์สำหรับสถาปัตยกรรมนั้น
- หลังจากสร้างคอมไพเลอร์สำหรับ OCaml subset แล้ว การรัน โปรแกรม ray tracing ถือเป็นเกณฑ์ประเมินบังคับ
- หากมีเวลาเพิ่มเติม สามารถตั้งโจทย์ท้าทายเฉพาะของตนเองได้ โดยนักศึกษาบางส่วนได้ทำ การทดลองต่อยอด เช่น เร่งความเร็ว CPU, พัฒนา multicore, รันเพลงหรือเกม
- ทีม Group 6 ตั้งเป้าหมายไปที่ การพอร์ตระบบปฏิบัติการ โดยจากจุดนี้จึงมีการรวมทีมใหม่ในชื่อ Group X
Xv6 OS และความท้าทายในการพอร์ต
- เลือก Xv6 (พัฒนาขึ้นโดย MIT เพื่อการศึกษา บนพื้นฐาน Unix v6 และสำหรับ x86) เป็นเป้าหมายในการพอร์ต
- แม้ Xv6 จะเป็น OS แบบยูนิกซ์ที่เรียบง่าย แต่ก็มีปัญหาหลายด้านสำหรับการรันบนฮาร์ดแวร์จริง เช่น ต้องใช้คอมไพเลอร์ C89, interrupt แบบเฉพาะทาง, การรองรับ virtual address และการขาด portability
- Xv6 ถูกสร้างขึ้นโดยตั้งสมมติฐานคุณลักษณะของ x86 เช่น
charมีขนาด 1 ไบต์ และintมีขนาด 4 ไบต์ จึงเกิดปัญหาจำนวนมากเมื่อพอร์ตไปยังสถาปัตยกรรมอื่น
การพัฒนาคอมไพเลอร์และ toolchain (Ucc)
- ในการทดลองเดิม มาตรฐานคือการสร้างคอมไพเลอร์ OCaml แต่เพื่อให้ Xv6 รันได้ ทีมจึงตัดสินใจพัฒนา คอมไพเลอร์ C89 ขึ้นเอง
- ใช้ ต้นแบบคอมไพเลอร์ C ของสมาชิกคนหนึ่งในทีมเป็นฐาน แล้วสร้าง toolchain ใหม่ขึ้นเองในชื่อ Ucc
- นอกจากคอมไพเลอร์แล้ว ยังออกแบบ Primitive Linker และเครื่องมือดีบักต่าง ๆ ขึ้นเองด้วย
การออกแบบ CPU และซิมูเลเตอร์
- ออกแบบวงจร CPU ด้วยฮาร์ดแวร์ดีสคริปชันแลงกวेज (HDL เช่น Verilog / VHDL) แล้วนำไปทำ logic synthesis ด้วย Vivado/Quartus เพื่อนำไปใช้งานจริงบน FPGA
- กระบวนการ logic synthesis ที่ต้องทำซ้ำใช้เวลานานมาก ทำให้มีช่วงเวลารอคอยจำนวนมากอย่างหลีกเลี่ยงไม่ได้
- หลังจากทำฟังก์ชันพื้นฐานของ CPU แล้ว ยังต้องออกแบบส่วนสนับสนุนฮาร์ดแวร์สำหรับการรัน OS เพิ่มเติม เช่น interrupt, MMU, TLB
- CPU ที่ได้เสร็จสมบูรณ์ถูกตั้งชื่อว่า GAIA
- ในซิมูเลเตอร์ยังมีการเพิ่ม interrupt จริง, การแปลง virtual address และเครื่องมือดีบักเข้าไปด้วย
ปัญหาและการแก้ไขระหว่างการพอร์ต
- เนื่องจาก portability ของ Xv6 ต่ำ จึงเกิดพฤติกรรมผิดปกติตามสเปกของ CPU และคอมไพเลอร์
- ตัวอย่าง: ปัญหาที่
charและintถูกนิยามเป็น 32 บิต ทำให้การคำนวณ pointer และโครงสร้าง stack พัง - ทีมจึงปรับปรุงคอมไพเลอร์ Ucc ให้
charมีขนาด 8 บิต
- ตัวอย่าง: ปัญหาที่
- การจัดการ interrupt เป็นส่วนที่ยากมาก จึงเพิ่ม disassembler, state dump และเครื่องมือดีบักอื่น ๆ ลงในซิมูเลเตอร์ที่พัฒนาขึ้นเอง
- ปัญหา cache alias เกิดจาก GAIA ใช้ virtual address เป็นดัชนีของ cache และแก้ไขได้ด้วยเทคนิค Page Coloring
ผลลัพธ์สุดท้าย: รัน OS และแอปได้สำเร็จ
- ในวันที่ 1 มีนาคม ทีมประสบความสำเร็จในการรัน Xv6 ได้อย่างสมบูรณ์ทั้งบนซิมูเลเตอร์และบน CPU จริง (ฮาร์ดแวร์)
- สามารถรัน แอปแบบโต้ตอบ อย่าง mini curses, คำสั่ง SL, Minesweeper, 2048 ได้สำเร็จ
- โดยเฉพาะ 2048 มีการเพิ่มการรองรับอินพุตแบบ non-canonical
- ยังปรับแก้ Xv6 เพื่อเพิ่มความสามารถที่เทียบได้กับ ioctl และ termios ในสไตล์ POSIX
- มีการพัฒนา assembler ขนาดเล็ก, mini vi ฯลฯ จนกลายเป็น ‘สภาพแวดล้อมการเขียนโปรแกรมแบบเรียลไทม์’ ที่ใช้งานได้จริง
- ยังสามารถรันโปรแกรม ray tracing บนระบบปฏิบัติการ ได้อีกด้วย ซึ่งถือว่าเกินเป้าหมายเดิมของการทดลอง
ความหมายของโปรเจ็กต์และกรณีต่อยอด
- หลังการทดลองนี้ นักศึกษาหลายรุ่นยังคง สร้าง CPU และ OS ด้วยตนเองเพื่อทำการทดลองหลากหลายอย่างต่อเนื่อง
- ตัวอย่างเช่น ขยายไปสู่การใช้ RISC-V ISA, สร้าง OS เอง หรือรัน Linux
- การลงมือทำจริงทำให้ได้สัมผัสทั้งสแตกฮาร์ดแวร์/ซอฟต์แวร์ และช่วยเพิ่มความเข้าใจเชิงปฏิบัติเกี่ยวกับ อัลกอริทึม การบูรณาการฮาร์ดแวร์-ซอฟต์แวร์ และโครงสร้างระดับล่าง
- แม้อาจถูกวิจารณ์ว่าการ “ประดิษฐ์ล้อขึ้นใหม่” ไม่มีประสิทธิภาพ แต่ผลลัพธ์ด้านการเรียนรู้และความสนุกจากการได้สร้างด้วยตัวเองนั้นสูงมาก
การทดลองใช้งานจริงและซอร์สโค้ด
- สามารถทดลองรันได้โดยตรงที่ GAIA CPU + Xv6 เดโม
- เวอร์ชันพอร์ตสำหรับ MIPS เปิดซอร์สไว้ที่ ที่นี่
บทสรุป
- โปรเจ็กต์นี้ตอกย้ำบทเรียนว่า “ไม่มีอะไรทำให้เรียนรู้ได้ดีเท่ากับการลงมือสร้างเอง” พร้อมเน้นย้ำ ความสำคัญของประสบการณ์บูรณาการฮาร์ดแวร์-ซอฟต์แวร์
- นักศึกษารุ่นถัดไปก็ยังคงท้าทายเป้าหมายใหม่ ๆ ต่อไป และหวังว่าในอนาคตอาจได้เห็น Linux หรือ VM รันอยู่บน ISA ที่สร้างขึ้นเอง
- เรื่องราวปิดท้ายด้วยรายชื่อสมาชิกผู้เข้าร่วมโปรเจ็กต์
4 ความคิดเห็น
อิจฉาที่ได้มีประสบการณ์แบบนี้ในมหาวิทยาลัยนะครับ น่าจะสนุกมากเลย..
คงสนุกมากจริง ๆ
ลองเลื่อนลงไปข้างล่างอีกหน่อย…
ทำ CPU 8 บิต…?
https://eater.net/8bit
ความคิดเห็นบน Hacker News
ทำให้นึกถึงโปรเจกต์กลุ่มสมัยเรียนมหาวิทยาลัยเมื่อก่อน ที่ทำกันสามคนอยู่ 3 สัปดาห์ ในหลายหัวข้อมีงานอย่างการสร้างระบบปฏิบัติการพื้นฐานมาก ๆ ขึ้นมาเองด้วย ตอนนั้นเราไปถามอาจารย์ว่าขอพอร์ต MINIX3 ไปลง Raspberry Pi ได้ไหม และก็ได้รับอนุญาตมา (เพราะ MINIX3 มี ARM พอร์ตสำหรับ BeagleBoard อยู่แล้ว เลยคิดว่าน่าจะพอเป็นไปได้). มันยากกว่าที่คาดไว้มาก และมีปัญหาทางเทคนิคที่ไม่เคยคาดคิดโผล่มาไม่หยุด โดยเฉพาะ Raspberry Pi 3 ที่บูตเข้า hypervisor mode แทน supervisor mode ทำให้ลำบากมาก และความแม่นยำของ Raspberry Pi emulation ใน QEMU ก็แย่มากจนแทบไม่มีประโยชน์กับการพัฒนา OS เลย ต้องติดอยู่กับการดีบักฮาร์ดแวร์ระดับล่างเป็นอาทิตย์เพื่อหาทางแก้. สุดท้ายก็ทำพอร์ตที่ใช้งานได้พร้อม UART, GPIO และ framebuffer driver สำเร็จ และรันได้บน Raspberry Pi 2 กับ 3 ตอนพรีเซนต์ก็ใช้ฮาร์ดแวร์จริง มีเชลล์สคริปต์สำหรับเขียนอิมเมจ ramdisk และเฝ้าดู GPIO pin เพื่อเลื่อนสไลด์ก่อนหน้า/ถัดไป โดยใช้มีดช็อตพินเพื่อสั่งงานเองด้วย ถือเป็นการพรีเซนต์ที่เท่มากแบบกินขาดในแง่ originality และคิดว่าน่าจะยังเก็บ SD card image นั้นไว้อยู่จนถึงตอนนี้
ฟังดูเป็นประสบการณ์ที่ยอดเยี่ยมมาก พอพูดว่าจะพอร์ต MINIX3 ไปลง Raspberry Pi อาจารย์อาจจะแอบคิดไว้แล้วก็ได้ว่าน่าจะล้มเหลว ตอนที่ความแม่นยำของ Raspberry Pi emulation ใน QEMU ไม่ค่อยดี ฉันใช้กลยุทธ์ทำให้ OS รันบน QEMU ให้ได้ก่อน แล้วค่อยไปเสี่ยงดวงบนฮาร์ดแวร์จริง ซึ่งก็ยังพอใช้ได้ อยากรู้ว่าตอนดีบักบน Raspberry Pi จริง ๆ ทำกันยังไง
พอได้ยินว่าใช้มีดช็อต GPIO ก็ทำให้นึกถึงตอนเคยช็อต power pin สองขาบนเมนบอร์ด ATX เพื่อบูตโดยไม่มีสวิตช์เปิดเครื่อง
ที่ SFU ก็เคยทำอะไรคล้าย ๆ กันเมื่อ 25~30 ปีก่อน ไม่ได้ถึงขั้นเอา OS กับคอมไพเลอร์ขึ้นไป และก็ไม่ใช่โปรเจกต์ทีมด้วย ถ้าสนใจการทดลองแบบนี้ ขอแนะนำ Turing Complete ซึ่งมีทั้งไกด์และเครื่องมือที่เข้าถึงง่าย คุณสามารถค่อย ๆ สร้างจากเกตไม่กี่ตัวไปจนถึงคอมพิวเตอร์จริงได้ มีทั้งคอมมูนิตี้และคอมโพเนนต์ให้แชร์กัน และยังมี RiscV core ด้วย สนุกมาก แนะนำให้ลองบน Steam ลิงก์ Turing Complete บน Steam
อ่านโพสต์นี้แล้วนึกถึงโปรเจกต์เชิงวิชาการ(?) ที่คล้ายกันขึ้นมาได้ลาง ๆ อย่างน้อยเท่าที่จำได้คือมีทั้ง custom C compiler และ custom OS แต่จำชื่อที่แน่ชัดไม่ได้
ดูหัวข้อที่เกี่ยวข้องที่เคยโพสต์ไว้ก่อนหน้านี้ได้ ลิงก์โพสต์ก่อนหน้า
เวลาสร้าง CPU + compiler + OS ขึ้นมาเอง แพลตฟอร์มที่อยู่ข้างล่างจะไม่มีอยู่เลย ฉันนี่แหละคือแพลตฟอร์ม. บั๊กก็คือกฎของระบบ ปกติเรามักดีบักอยู่บน abstraction layer ที่คนอื่นสร้างไว้ แต่ในที่นี้แม้แต่กฎพวกนั้นก็เป็นสิ่งที่ฉันต้องกำหนดเอง OP จึงเหมือนกับกำลังดีบักกฎของตัวเองโดยตรง
น่าประทับใจจริง ๆ งานฝั่ง low-end มักจะน่าเบื่อและกินเวลามาก โดยเฉพาะถ้าไม่มีเครื่องมือสำคัญอย่างดีบักเกอร์ก็ยิ่งหนักเข้าไปอีก
kprintfแปลก ๆ ก็ยังถือว่ายังไม่เคยลิ้มรสของงาน low-level แบบแท้จริงMagic-1 กับ BMOW ก็เคยทำอะไรคล้าย ๆ กันมาก่อน รายละเอียดดูได้ที่ homebrewcpu.com รายชื่อเว็บไซต์คนที่สร้าง CPU เองดูได้ที่ homebrewcpuring.org
ถึงเวลาแล้วที่จะไม่ใช่แค่เอาไปทำบน FPGA แต่ต้องวิ่งเข้าห้องแล็บเซมิคอนดักเตอร์แล้วขอให้ช่วยผลิต CPU ขึ้นมาจริง ๆ เองเลย