4 คะแนน โดย GN⁺ 2025-10-27 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • อธิบายขั้นตอนการสร้าง “ลินุกซ์ดิสโทรขนาดจิ๋ว” แบบเป็นลำดับ โดย คอมไพล์ลินุกซ์เคอร์เนลด้วยตัวเองและประกอบ user space ขั้นต่ำ
  • กล่าวถึง บทบาทของเคอร์เนลระบบปฏิบัติการ องค์ประกอบของลินุกซ์ดิสโทร และความสัมพันธ์ระหว่างเคอร์เนลกับ user space ตั้งแต่พื้นฐาน
  • ใช้สถาปัตยกรรม RISC-V (เครื่อง riscv64 virt ของ QEMU) เป็นตัวอย่าง แต่ หลักการเดียวกันสามารถนำไปใช้กับสถาปัตยกรรมอื่นอย่าง x86 ได้เช่นกัน
  • สร้าง สภาพแวดล้อมลินุกซ์ขั้นต่ำที่สามารถรันได้จริง ซึ่งมี init process, initramfs และเชลล์อย่างง่ายที่เขียนด้วย Go
  • ปิดท้ายด้วยการแนะนำวิธีสร้าง ไมโครดิสโทรที่ใช้งานได้จริง ด้วยโปรเจ็กต์ u-root และสรุปเป็น คู่มือเริ่มต้นที่ช่วยให้เข้าใจโครงสร้างโดยรวมของระบบลินุกซ์

เคอร์เนลของระบบปฏิบัติการคืออะไร

  • เคอร์เนลคือองค์ประกอบหลักของระบบปฏิบัติการที่ทำหน้าที่ จัดการทรัพยากรฮาร์ดแวร์และควบคุมการทำงานของโปรแกรม
    • แม้อยู่ในสภาพแวดล้อมแบบคอร์เดียว ก็มี ความสามารถในการจัดการ multitasking ที่ทำให้ดูเหมือนหลายโปรแกรมกำลังทำงานพร้อมกัน
  • เคอร์เนลทำหน้าที่ abstract การควบคุมอุปกรณ์อินพุต/เอาต์พุต เพื่อให้แอปพลิเคชันไม่ต้องจัดการรายละเอียดอย่างที่อยู่ฮาร์ดแวร์หรือค่ารีจิสเตอร์โดยตรง
    • ตัวอย่างเช่น โปรแกรมเพียงแค่สั่งว่า “ให้เขียนข้อความไปยัง standard output” แล้วเคอร์เนลจะจัดการการสื่อสารกับฮาร์ดแวร์จริงเอง
  • เคอร์เนลมอบ อินเทอร์เฟซระบบไฟล์ เพื่อให้เข้าถึงข้อมูลในระดับสูง
    • ไฟล์ไม่ได้เป็นแค่ข้อมูลบนดิสก์ แต่ทำหน้าที่เป็น logical interface สำหรับการสื่อสารกับเคอร์เนลด้วย
  • เคอร์เนลมี โมเดลการแยกและการสื่อสารระหว่างโปรเซส ทำให้แต่ละแอปพลิเคชันทำงานแยกจากกันหรือทำงานร่วมกันได้
  • Linux kernel เป็นโอเพนซอร์ส รองรับสถาปัตยกรรมหลากหลาย และเป็นหนึ่งในเคอร์เนลที่ถูกใช้งานแพร่หลายที่สุดในโลก

ลินุกซ์ดิสโทรคืออะไร

  • ลินุกซ์เคอร์เนลเพียงอย่างเดียวไม่เพียงพอให้ผู้ใช้รันเว็บเบราว์เซอร์หรือแอป GUI ได้ และต้องมี โครงสร้างพื้นฐานซอฟต์แวร์หลายชั้นเหนือเคอร์เนล
  • งานอย่างการตั้งค่าเครือข่าย การกำหนด IP หรือการจัดการ VPN เป็นหน้าที่ของ โปรแกรมระดับ user space ไม่ใช่ของเคอร์เนล
  • ดังนั้นลินุกซ์ดิสโทรจึงนิยามได้ว่าเป็น การรวมกันของเคอร์เนล + โครงสร้างพื้นฐานใน user space
  • ดิสโทรประกอบด้วย แพ็กเกจ เครื่องมือ การตั้งค่า และ process เริ่มต้น (init) ที่อยู่บนความสามารถพื้นฐานที่เคอร์เนลมอบให้
  • ระดับความซับซ้อนของดิสโทรมีได้หลากหลาย ตั้งแต่แบบมินิมอลอย่าง Arch Linux ไปจนถึงแบบใช้งานง่ายสำหรับผู้ใช้อย่าง Ubuntu

โครงสร้างพื้นฐานนอกเคอร์เนล: user space และ init process

  • เมื่อเคอร์เนลบูตเสร็จ จะรัน init ซึ่งเป็น process หมายเลข PID 1 เป็นตัวแรก
    • init เป็นบรรพบุรุษของทุก process ใน user space หลังจากนั้น และจะค่อย ๆ รันบริการและเครื่องมือต่าง ๆ ของระบบ
  • ชุดของ process และเครื่องมือที่ init เรียกใช้งานคือ องค์ประกอบที่แท้จริงของลินุกซ์ดิสโทร
  • เมื่อดิสโทรซับซ้อนขึ้น บางครั้งก็ถูกวิจารณ์ว่า “bloated” เพราะมีฟังก์ชันที่ไม่จำเป็นสะสมมากเกินไป
  • ในทางกลับกัน หากสร้าง ไมโครดิสโทรแบบคัสตอม ก็สามารถได้ระบบน้ำหนักเบาที่มีเฉพาะความสามารถขั้นต่ำที่จำเป็น

การคอมไพล์ลินุกซ์เคอร์เนลสำหรับ RISC-V

  • บนสภาพแวดล้อม x86 สามารถคอมไพล์เคอร์เนลสำหรับ RISC-V โดยใช้ cross-compilation toolchain
    • ดาวน์โหลดซอร์ส linux-6.5.2.tar.xz จาก kernel.org แล้วรัน make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
  • สามารถแก้ไขค่าคอนฟิกเคอร์เนลแบบเห็นภาพได้ผ่าน menuconfig
  • หลังคอมไพล์แบบขนานด้วย make -j16 จะได้ไฟล์ arch/riscv/boot/Image
  • บูตใน QEMU ด้วย qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Image
    • ในบันทึกการบูตจะเห็นข้อความอย่าง การตรวจพบเลเยอร์ SBI, การเริ่มต้น UART และการเปิดใช้งาน printk

อุปสรรคแรก: ไม่มี root filesystem

  • ระหว่างการบูตเคอร์เนลจะเกิด kernel panic พร้อมข้อผิดพลาด VFS: Unable to mount root fs
    • สาเหตุคือไม่มี root filesystem (initramfs) ถูกจัดเตรียมไว้
  • ระบบไฟล์สามารถสร้างอยู่บน RAM (initramfs) ได้ ไม่ได้จำกัดเฉพาะดิสก์
  • initramfs ถูกแพ็กในรูปแบบ cpio และใน QEMU สามารถโหลดได้ด้วยออปชัน -initrd

สร้าง initramfs และรัน “Hello world”

  • ข้อกำหนดขั้นต่ำคือจะต้องมีไบนารี /init
    • เขียน init.c แล้วคอมไพล์แบบลิงก์สแตติก (-static)
    • แพ็กด้วย cpio -o -H newc < file_list.txt > initramfs.cpio
  • เมื่อรัน QEMU จะพิมพ์ “Hello world” แล้วเกิด kernel panic อีกครั้งเพราะ init จบการทำงาน
    • วิธีแก้คือเพิ่มลูปไม่รู้จบเพื่อไม่ให้ init ออกจากโปรแกรม

เพิ่มเชลล์อย่างง่ายที่เขียนด้วย Go

  • init ใช้ fork และ execl เพื่อรัน /little_shell
  • little_shell.go เป็นเชลล์เรียบง่ายที่รับอินพุตจากผู้ใช้และ echo คำสั่งกลับออกมา
    • คอมไพล์สำหรับ RISC-V ด้วย GOOS=linux GOARCH=riscv64 go build little_shell.go
  • ทั้ง init และ little_shell ใช้ UART ร่วมกันในการแสดงผล
    • standard input/output ถูกจัดการในรูปของ file handle และถูกสืบทอดต่อเมื่อ fork
  • ผลลัพธ์คือได้ สภาพแวดล้อมลินุกซ์พื้นฐาน ที่มี “Hello from init” และอินพุตของเชลล์แสดงสลับกันออกมา

สรุปบทบาทของเคอร์เนล

  • abstract ฮาร์ดแวร์: โปรแกรมฝั่งผู้ใช้สามารถแสดงผลได้โดยไม่ต้องรู้รายละเอียดของ UART หรืออุปกรณ์
  • มอบอินเทอร์เฟซระดับสูง: เข้าถึงไบนารีอื่นอย่าง little_shell ผ่านระบบไฟล์
  • แยกโปรเซสออกจากกัน: init และเชลล์ทำงานอยู่ในหน่วยความจำคนละส่วน
  • เคอร์เนลมอบ รากฐานการรันที่เสถียรและพกพาได้สูง บนฮาร์ดแวร์ที่ซับซ้อน

นิยามของระบบปฏิบัติการ

  • บางมุมมองอาจนับเฉพาะเคอร์เนลว่าเป็นระบบปฏิบัติการ หรืออาจมองว่า ดิสโทรทั้งชุดคือระบบปฏิบัติการ
  • สิ่งสำคัญคือการเข้าใจ ขอบเขตบทบาทและโครงสร้างปฏิสัมพันธ์ ระหว่างเคอร์เนลกับ user space

สร้างไมโครดิสโทรที่ใช้งานได้จริงด้วย u-root

  • โปรเจ็กต์ u-root มอบ ชุดเครื่องมือ user space ที่สร้างด้วย Go
    • u-root มีทั้ง bootloader ฝั่ง user space และสภาพแวดล้อมเชลล์ ที่ทำงานอยู่บนลินุกซ์เคอร์เนล
  • หลังติดตั้งแล้ว สามารถสร้าง initramfs อัตโนมัติได้ด้วยคำสั่ง GOOS=linux GOARCH=riscv64 u-root
    • ไฟล์ /tmp/initramfs.linux_riscv64.cpio ที่ได้สามารถนำไปรันบน QEMU ได้
  • ตอนบูตจะมีแบนเนอร์ “Welcome to u-root!” พร้อม พรอมป์ต์เชลล์พื้นฐาน
    • รองรับคำสั่งพื้นฐานอย่าง ls, pwd, echo และมีฟีเจอร์ tab completion

ทดลองเชื่อมต่อเครือข่าย

  • เพิ่มอุปกรณ์ virtio-net-device และ virtio-rng-pci ให้ QEMU
    • ใช้ออปชัน -device virtio-net-device,netdev=usernet -netdev user,id=usernet
  • ใช้ dhclient ของ u-root เพื่อรับ IP อัตโนมัติผ่าน DHCP
    • ตัวอย่างเช่น eth0 ได้รับ 10.0.2.15/24
  • เข้าถึงเครือข่ายภายนอกสำเร็จด้วย wget http://google.com และยืนยันการดาวน์โหลด index.html ได้

ความสำคัญของ package manager และ init

  • ดิสโทรทั่วไปใช้ package manager เพื่อติดตั้งและอัปเดตซอฟต์แวร์แบบไดนามิก
    • แต่การทดลองนี้เป็นแนวทางแบบ embedded ที่ต้องรีบิลด์อิมเมจทั้งก้อนใหม่
  • init ไม่ใช่แค่ตัวรัน process ธรรมดา แต่เป็น องค์ประกอบสำคัญของการเริ่มต้นอุปกรณ์ การจัดการบริการ และการควบคุมการบูตของระบบ
    • สามารถดูขั้นตอนการตั้งค่าอุปกรณ์ต่าง ๆ เช่น /dev ได้จากซอร์สโค้ด init ของ u-root

ที่เก็บโค้ดบน GitHub

  • โค้ดและตัวอย่างทั้งหมดของคู่มือนี้มีให้ที่ popovicu/linux-micro-distro
    • สามารถใช้สร้างอิมเมจ initramfs และทำซ้ำการทดลองได้

1 ความคิดเห็น

 
GN⁺ 2025-10-27
ความคิดเห็นจาก Hacker News
  • ผมกำลังทำ ดิสโทร Linux ขนาดจิ๋ว ขึ้นมาเองมาหลายเดือนแล้ว
    โหมดผู้ใช้ประกอบด้วยไบนารีแบบ static เพียงไฟล์เดียว และมีแค่ไฟล์ไม่กี่ไฟล์สำหรับรองรับ confidential microVM container
    โดยเฉพาะโครงสร้างของ initramfs น่าสนใจมาก กระบวนการที่เคอร์เนลแตก cpio archive, เข้า tmpfs แล้วรัน /init นั้นให้ความรู้สึกราวกับเวทมนตร์
    ยังสามารถนำ cpio archive หลายชุดมาต่อกันได้ แต่ละชุดบีบอัดได้ และถูก overlay ตามลำดับ
    ด้วยการออกแบบที่เรียบง่ายแต่สง่างามนี้ ทำให้ผมได้เรียนรู้อะไรมากมายจากการเขียนโค้ด unpack ด้วยตัวเอง

  • ช่วงนี้ qemu เริ่มรองรับ uftrace บนสถาปัตยกรรมหลักแล้ว
    เวลาผู้เชี่ยวชาญถามว่า “จะดีบักสิ่งนี้อย่างไร?” นี่แหละคือคำตอบ
    ดูข้อมูลที่เกี่ยวข้องได้ในเธรดนี้

  • ผมก็กำลังทำโปรเจกต์คล้ายกันอยู่ — azathos
    มี toy init, shell และยูทิลิตีอีกเล็กน้อยที่ทำขึ้นเอง
    ใส่ GNU coreutils ไว้สำหรับดีบัก และตอนนี้กำลังโฟกัสกับ ความสามารถในการวาดหน้าต่าง ลงบน framebuffer

  • โปรเจกต์นี้เจ๋งมาก ทำให้นึกถึงสมัยปี 98 ที่ทำ “ดิสโทร” แบบใช้ฟลอปปีดิสก์เพื่ออิมเมจเครื่อง Windows PC ผ่าน UDP broadcast
    “make bzimage”, ข้อผิดพลาดในสคริปต์ init, รีบูตไม่รู้จบ… มีความทรงจำเยอะมาก
    น่าสนใจที่วิธีสมัยนี้ก็ไม่ได้ต่างกันมาก ถ้าพอร์ตไปลง Raspberry Pi น่าจะทั้งสนุกและได้ความรู้ อาจจะลองทำเองก็ได้

    • ผมเองก็จำได้ว่าในปี 98 เคยพยายามติดตั้ง Mandrake Linux ผ่าน NetBIOS และ ISDN แล้วต้องเริ่มใหม่เป็นสิบ ๆ ครั้งเพราะ checksum error
      สุดท้ายเพื่อนต้องไรต์เนื้อหา sftp ลง CD ให้ถึงจะแก้ได้ ตอนนั้นเขียนได้แค่ 2x เท่านั้น
  • สงสัยว่าถ้าจะรันสิ่งนี้เป็น cloud image (เช่น Vultr, DigitalOcean) หรือเปิด GUI แล้วรัน Firefox จะยากแค่ไหน

    • การรันเป็น cloud image ค่อนข้างง่าย แค่มีไดรเวอร์พื้นฐานของเคอร์เนลแล้วติดตั้งอิมเมจก็พอ
      ยังสามารถบูตด้วยดิสโทรอื่นก่อน แล้วใช้ kexec เพื่อรันเคอร์เนลของตัวเองและติดตั้งบนหน่วยความจำได้ด้วย
      ดูตัวอย่างการทำจริงได้จาก nixos-anywhere
    • สร้างอิมเมจที่รวม virtio driver สำหรับเครือข่ายและสตอเรจ แล้วแปลงเป็น qcow2 เพื่อนำไปลงทะเบียนกับ DigitalOcean เป็นต้น
      เป็นงานที่ง่ายกว่าที่คิด
  • ถ้ามีเวอร์ชันของโปรเจกต์นี้ที่ทำมาสำหรับ Raspberry Pi โดยเฉพาะก็น่าจะน่าสนใจมาก

  • ตอนแรกผมสงสัยว่าทำไมต้องสร้างอะไรแบบนี้เอง แล้วก็คิดว่าศึกษา Linux ผ่าน Gentoo อย่างเดียวไม่ได้หรือ

    • Gentoo เป็นแบบ “build from source” แต่ package manager จะช่วยจัดการงานส่วนใหญ่ให้
      ปรับแต่ง user space ได้ก็จริง แต่ไม่เหมาะนักถ้าต้องการเรียนรู้ Linux เองจริง ๆ
      แค่ดู stage3 tarball ก็อยู่ในระดับ “มินิดิสโทร” แล้ว
  • ถ้าเอาไว้เรียนรู้ถือว่าดีมาก และถ้าอยากทำให้เสร็จเร็ว buildroot เป็นตัวเลือกที่ดี

  • บทความนี้ทำให้ผมได้เรียนรู้อะไรมากจริง ๆ ขอบคุณสำหรับ โพสต์ที่ข้อมูลแน่นมาก