- Tự biên dịch kernel Linux và dựng một user space tối thiểu để tạo ra một “bản phân phối Linux siêu nhỏ”, với quy trình được giải thích từng bước
- Trình bày từ nền tảng về vai trò của kernel hệ điều hành, các thành phần của bản phân phối Linux, và mối quan hệ giữa kernel với user space
- Dùng kiến trúc RISC-V (máy
riscv64 virt của QEMU) làm ví dụ, nhưng cùng nguyên lý có thể áp dụng cho các kiến trúc khác như x86
- Xây dựng môi trường Linux tối thiểu có thể chạy trực tiếp bao gồm tiến trình
init, initramfs, và một shell đơn giản viết bằng Go
- Cuối cùng giới thiệu cách dùng dự án
u-root để tạo một micro distro thực sự hữu ích, và khép lại như một hướng dẫn nhập môn giúp hiểu tổng thể cấu trúc hệ thống Linux
Kernel hệ điều hành là gì
- Kernel là thành phần cốt lõi của hệ điều hành, chịu trách nhiệm quản lý tài nguyên phần cứng và điều khiển việc thực thi chương trình
- Ngay cả trong môi trường một lõi, kernel vẫn cung cấp cơ chế quản lý đa nhiệm để nhiều chương trình trông như chạy đồng thời
- Kernel trừu tượng hóa việc điều khiển thiết bị I/O để ứng dụng không phải xử lý trực tiếp địa chỉ phần cứng hay giá trị thanh ghi
- Ví dụ, chương trình chỉ cần yêu cầu “ghi thông điệp ra standard output”, còn kernel sẽ xử lý tương tác với phần cứng thực tế
- Kernel cung cấp cách truy cập dữ liệu ở mức cao thông qua giao diện filesystem
- File không chỉ là dữ liệu trên đĩa, mà còn hoạt động như một giao diện logic để giao tiếp với kernel
- Kernel cung cấp mô hình cô lập và giao tiếp giữa các process, cho phép từng ứng dụng chạy độc lập hoặc phối hợp với nhau
- Linux kernel là mã nguồn mở, có thể chạy trên nhiều kiến trúc khác nhau, và là một trong những kernel được dùng rộng rãi nhất trên thế giới
Bản phân phối Linux là gì
- Chỉ riêng Linux kernel thì người dùng chưa thể chạy trình duyệt web hay ứng dụng GUI; cần có nhiều lớp hạ tầng phần mềm trên kernel
- Cấu hình mạng, cấp phát IP, quản lý VPN... không phải việc của kernel mà do các chương trình user space cấp cao hơn đảm nhiệm
- Vì vậy, một bản phân phối Linux được định nghĩa là sự kết hợp giữa kernel + hạ tầng user space
- Bản phân phối bao gồm gói phần mềm, công cụ, cấu hình, tiến trình khởi tạo (
init)... bên trên các chức năng cơ bản mà kernel cung cấp
- Mức độ phức tạp của distro rất đa dạng, từ cấu hình tối giản như Arch Linux đến cấu hình thân thiện với người dùng như Ubuntu
Hạ tầng bên ngoài kernel: user space và tiến trình init
- Khi kernel hoàn tất khởi động, việc đầu tiên nó làm là chạy tiến trình
init có PID 1
init là tổ tiên của toàn bộ process trong user space về sau, và sẽ lần lượt khởi chạy các service cùng công cụ của hệ thống
- Tập hợp các process và công cụ mà
init chạy chính là thành phần thực chất của một bản phân phối Linux
- Khi distro trở nên phức tạp hơn, nó đôi khi bị chỉ trích là “bloated” do tích lũy quá nhiều chức năng không cần thiết
- Ngược lại, nếu tự tạo micro distro tùy biến, bạn có thể dựng một hệ thống nhẹ chỉ gồm những chức năng tối thiểu cần thiết
Biên dịch Linux kernel cho RISC-V
- Trên môi trường
x86, dùng toolchain cross-compile để biên dịch kernel cho RISC-V
- Tải mã nguồn
linux-6.5.2.tar.xz từ kernel.org, sau đó chạy make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
- Có thể chỉnh sửa trực quan cấu hình kernel thông qua
menuconfig
- Sau khi biên dịch song song bằng
make -j16, file arch/riscv/boot/Image sẽ được tạo ra
- Khởi động trong QEMU bằng
qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Image
- Trong log khởi động có thể thấy các thông báo như phát hiện lớp SBI, khởi tạo UART, kích hoạt printk
Trở ngại đầu tiên: không có root filesystem
- Trong lúc kernel khởi động, lỗi
VFS: Unable to mount root fs gây ra kernel panic
- Nguyên nhân: không cung cấp root filesystem (
initramfs)
- Filesystem không chỉ nằm trên đĩa mà cũng có thể được dựng trên RAM (
initramfs)
initramfs được đóng gói theo định dạng cpio, và trong QEMU có thể nạp bằng tùy chọn -initrd
Dựng initramfs và chạy “Hello world”
- Yêu cầu tối thiểu là phải có binary
/init
- Viết
init.c rồi biên dịch với liên kết tĩnh (-static)
- Đóng gói bằng
cpio -o -H newc < file_list.txt > initramfs.cpio
- Khi chạy QEMU, sau khi in “Hello world”, việc
init kết thúc lại gây ra kernel panic
- Cách khắc phục: thêm vòng lặp vô hạn để
init không thoát
Thêm một shell đơn giản viết bằng Go
init dùng fork và execl để chạy /little_shell
little_shell.go là một shell đơn giản nhận input từ người dùng và echo lại lệnh
- Biên dịch cho RISC-V bằng
GOOS=linux GOARCH=riscv64 go build little_shell.go
- Cả
init và little_shell đều dùng chung đầu ra thông qua UART
- Standard input/output được quản lý bằng file handle và được kế thừa khi
fork
- Kết quả là tạo được một môi trường Linux cơ bản nơi “Hello from init” và input từ shell được in xen kẽ
Tóm tắt vai trò của kernel
- Trừu tượng hóa phần cứng: chương trình người dùng có thể xuất dữ liệu mà không cần biết chi tiết về UART hay thiết bị
- Cung cấp giao diện mức cao: truy cập các binary khác (
little_shell) thông qua filesystem
- Cô lập process:
init và shell chạy trong các vùng nhớ độc lập
- Trên lớp phần cứng phức tạp, kernel cung cấp nền tảng thực thi ổn định và có tính di động cao
Định nghĩa về hệ điều hành
- Có quan điểm xem riêng kernel là hệ điều hành, cũng có quan điểm xem toàn bộ bản phân phối là hệ điều hành
- Điều quan trọng là hiểu ranh giới vai trò và cấu trúc tương tác giữa kernel với user space
Tạo một micro distro thực sự hữu ích với u-root
- Dự án u-root cung cấp bộ công cụ user space dựa trên Go
u-root bao gồm bootloader user space và môi trường shell chạy trên Linux kernel
- Sau khi cài đặt, lệnh
GOOS=linux GOARCH=riscv64 u-root sẽ tự động tạo initramfs
- Có thể chạy file
/tmp/initramfs.linux_riscv64.cpio trong QEMU
- Khi khởi động, hệ thống hiển thị banner “Welcome to u-root!” cùng shell prompt mặc định
- Hỗ trợ các lệnh cơ bản như
ls, pwd, echo và cả tính năng tab completion
Thực hành kết nối mạng
- Thêm các thiết bị
virtio-net-device và virtio-rng-pci vào QEMU
- Dùng các tùy chọn
-device virtio-net-device,netdev=usernet -netdev user,id=usernet
- Dùng
dhclient của u-root để tự động nhận IP qua DHCP
- Ví dụ: cấp phát
10.0.2.15/24 cho eth0
- Dùng
wget http://google.com để truy cập mạng bên ngoài thành công và xác nhận tải về index.html
Tầm quan trọng của package manager và init
- Các distro thông thường dùng package manager để cài đặt và cập nhật phần mềm một cách động
- Bài thực hành này theo cách tiếp cận kiểu embedded, nên phải build lại toàn bộ image
init không chỉ là trình chạy process đơn giản, mà còn là thành phần cốt lõi cho khởi tạo thiết bị, quản lý service và điều khiển quá trình boot hệ thống
- Có thể xem mã nguồn
init của u-root để hiểu quy trình thiết lập nhiều thiết bị như /dev
Kho lưu trữ GitHub
- Toàn bộ mã nguồn và ví dụ của hướng dẫn này có tại popovicu/linux-micro-distro
- Có thể build image
initramfs và tái hiện lại bài thực hành
1 bình luận
Ý kiến trên Hacker News
Tôi đã tự làm bản phân phối Linux siêu nhỏ này được vài tháng rồi
Chế độ người dùng được cấu thành từ một binary tĩnh duy nhất, cùng chỉ vài tệp để hỗ trợ các container microVM bảo mật
Đặc biệt, cấu trúc initramfs rất thú vị. Quá trình kernel giải nén kho lưu trữ cpio, đi vào tmpfs rồi chạy /init gần như giống phép màu
Cũng có thể nối nhiều kho lưu trữ cpio lại với nhau, mỗi cái đều có thể nén và được overlay theo thứ tự
Nhờ thiết kế vừa đơn giản vừa thanh lịch này, tôi đã học được rất nhiều khi tự viết mã unpack
Gần đây, qemu đã bắt đầu hỗ trợ uftrace trên các kiến trúc chính
Đây chính là câu trả lời khi các chuyên gia hỏi “debug cái này kiểu gì?”
Có thể tham khảo nội dung liên quan trong chuỗi thảo luận này
Tôi cũng đang làm một dự án tương tự — azathos
Nó bao gồm toy init, shell tự viết, và một vài tiện ích
Tôi đã thêm GNU coreutils để debug, và hiện đang tập trung vào chức năng vẽ cửa sổ lên framebuffer
Dự án này thật sự rất hay. Nó gợi nhớ thời tôi làm một “bản phân phối” chạy trên floppy vào năm 98 để imaging máy Windows PC qua UDP broadcast
“make bzimage”, lỗi script init, reboot vô hạn… quá nhiều kỷ niệm
Thật thú vị khi cách làm ngày nay cũng không khác mấy. Nếu port sang Raspberry Pi thì chắc sẽ vừa vui vừa mang tính giáo dục. Có khi tôi cũng sẽ thử
Cuối cùng một người bạn đã ghi nội dung sftp ra CD cho tôi nên mới giải quyết được, mà hồi đó chỉ ghi được ở tốc độ 2x
Tôi tự hỏi sẽ khó đến mức nào nếu chạy thứ này thành cloud image (ví dụ: Vultr, DigitalOcean) hoặc bật GUI để chạy Firefox
Cũng có thể boot bằng một bản phân phối khác rồi dùng kexec để chạy kernel của chính mình và cài đặt trong bộ nhớ
Có thể tham khảo nixos-anywhere để xem ví dụ triển khai thực tế
Đây là việc đơn giản hơn bạn nghĩ
Nếu có một phiên bản của dự án này nhắm tới Raspberry Pi thì sẽ thật sự rất thú vị
Ban đầu tôi thắc mắc tại sao phải tự làm thứ này, và nghĩ rằng chẳng phải cứ khám phá Linux bằng Gentoo là được sao
Bạn có thể tùy biến user space, nhưng nó không thật sự phù hợp để học chính bản thân Linux
Chỉ nhìn stage3 tarball thôi cũng đã ở mức một “mini distro” rồi
Xét cho mục đích học tập thì đây thực sự rất tốt, còn nếu muốn hoàn thành nhanh thì buildroot là một lựa chọn hay
Nhờ bài viết này mà tôi học được rất nhiều. Cảm ơn vì một bài đăng giàu thông tin như vậy