Quá trình khởi động Linux: từ nút nguồn đến kernel
(0xkato.xyz)- Bài giải thích kỹ thuật mô tả theo từng bước quá trình từ lúc nhấn nút nguồn trên máy tính cho đến khi Linux kernel được thực thi
- Trình bày cụ thể cách CPU khởi động ở real mode rồi chuyển sang protected mode và long mode
- Mô tả chi tiết vai trò và nguyên lý hoạt động của từng giai đoạn như firmware BIOS/UEFI, bootloader (GRUB), giải nén kernel và tái định vị địa chỉ
- Giải thích các khái niệm cốt lõi cần cho quá trình khởi tạo kernel như memory mapping, interrupt, page table, kASLR kèm các ví dụ ngắn gọn
- Việc hiểu cơ chế nội bộ của quá trình khởi động Linux mang lại góc nhìn về kiến trúc hệ thống, bảo mật và tối ưu hiệu năng
Part 1 — Từ nút nguồn đến lần thực thi đầu tiên của kernel
-
Khi nhấn nút nguồn, CPU được reset về real mode và thực thi lệnh khởi tạo đầu tiên
- Real mode là một cơ chế địa chỉ đơn giản tồn tại từ thời 8086, tính địa chỉ vật lý bằng cách kết hợp segment và offset
- Ví dụ:
physical_address = (segment << 4) + offset - Sau khi reset, CPU nhảy tới địa chỉ
0xFFFFFFF0(reset vector) và chuyển quyền điều khiển cho firmware
-
Register là các ô lưu trữ siêu tốc bên trong CPU, gồm CS (code segment), IP (instruction pointer), v.v.
- CS chỉ vị trí của mã hiện tại, còn IP trỏ tới lệnh sẽ được thực thi tiếp theo
BIOS và UEFI
- BIOS là firmware kiểu cũ; sau POST (power-on self test), nó kiểm tra thứ tự khởi động và tìm đĩa có thể boot
- Một đĩa có thể boot sẽ có hai byte cuối của sector 512 byte đầu tiên là
0x55AA - BIOS sao chép sector này tới địa chỉ
0x7C00rồi nhảy tới đó để thực thi
- Một đĩa có thể boot sẽ có hai byte cuối của sector 512 byte đầu tiên là
- UEFI là công nghệ thay thế hiện đại, có thể hiểu trực tiếp filesystem và nạp các chương trình boot lớn hơn
- Khác với BIOS, nó không bị ràng buộc bởi giới hạn “sector đầu tiên”, đồng thời truyền cho hệ điều hành nhiều thông tin hệ thống phong phú hơn
Bootloader
- Bootloader là chương trình nạp kernel vào bộ nhớ và chuẩn bị để thực thi
- Phổ biến nhất là GRUB, nó đọc file cấu hình rồi nạp kernel và initial ramdisk (initrd) vào bộ nhớ
- File kernel gồm một chương trình thiết lập nhỏ cho real mode và phần thân kernel đã được nén
- GRUB ghi thông tin như vị trí kernel, command line, vị trí initrd vào cấu trúc setup header rồi nhảy tới mã thiết lập của kernel
Chương trình thiết lập (setup code)
- Trước khi kernel chạy, nó tạo ra một không gian làm việc có thể dự đoán được
- Căn chỉnh các segment register (CS, DS, SS) và xóa direction flag để thao tác sao chép bộ nhớ hoạt động nhất quán
- Tạo stack để lưu dữ liệu tạm trong lúc gọi hàm
- Khởi tạo vùng BSS (không gian biến toàn cục phải bắt đầu bằng 0) về 0
- Nếu có tùy chọn
earlyprintk, nó có thể cấu hình cổng serial để in ra các thông điệp debug sớm - Nó yêu cầu firmware cung cấp bản đồ RAM (e820) để biết vùng nhớ nào khả dụng và vùng nào được dành riêng
- Khi mọi thứ đã sẵn sàng, nó gọi
main— hàm C đầu tiên — rồi bước vào giai đoạn chuyển mode
Interrupt
- Interrupt là cơ chế cho phép CPU tạm dừng công việc hiện tại để xử lý một sự kiện khẩn cấp
- Các sự kiện phần cứng như phím nhấn hoặc timer là ví dụ điển hình
- Maskable interrupt có thể bị chặn tạm thời, còn NMI (Non-Maskable Interrupt) thì luôn được xử lý
- Trong lúc chuyển mode, interrupt sẽ bị chặn tạm thời để tránh các sự cố không mong muốn
Part 2 — Từ real mode sang 32-bit, rồi 64-bit
- Linux hiện đại hoạt động trong long mode của kiến trúc x86_64
- Cần chuyển tuần tự theo các bước: real mode → protected mode → long mode
Protected mode
- Đây là chế độ 32-bit được giới thiệu để vượt qua các giới hạn của thập niên 1980, với hai cấu trúc cốt lõi
- GDT (Global Descriptor Table): định nghĩa địa chỉ bắt đầu, kích thước và quyền truy cập của segment
- Linux dùng flat model để đơn giản hóa toàn bộ không gian 32-bit thành một vùng liên tục duy nhất
- IDT (Interrupt Descriptor Table): lưu địa chỉ của các handler sẽ được gọi khi có interrupt
- Trong lúc boot chỉ nạp một IDT tối thiểu, rồi cài đặt IDT đầy đủ sau khi kernel khởi tạo xong
- GDT (Global Descriptor Table): định nghĩa địa chỉ bắt đầu, kích thước và quyền truy cập của segment
Quá trình chuyển mode
- Mã thiết lập trước tiên sẽ vô hiệu hóa interrupt, dừng chip PIC, kích hoạt đường A20, và khởi tạo bộ đồng xử lý toán học
- Đường A20 là một cơ chế lịch sử để giải quyết vấn đề quấn địa chỉ ở mốc 1MB
- Sau khi nạp GDT và IDT tối thiểu, nó đặt bit PE của thanh ghi CR0 và thực hiện far jump
- Từ đây CPU đi vào protected mode, rồi các segment và stack pointer được cấu hình lại theo hệ thống địa chỉ mới
Control registers
- CR0: kích hoạt protected mode
- CR3: lưu địa chỉ cấp cao nhất của page table
- CR4: kích hoạt các tính năng mở rộng như PAE
Chuẩn bị vào long mode
- Để chuyển sang chế độ 64-bit, cần hai điều kiện
- Bật paging: thực hiện ánh xạ giữa địa chỉ ảo và địa chỉ vật lý
- Đặt bit LME (Long Mode Enable) trong thanh ghi EFER
- Page table ánh xạ các trang theo đơn vị 4KB; ở giai đoạn boot ban đầu, nó thường được cấu hình đơn giản bằng identity map theo đơn vị 2MB
Quy trình bật paging
- Bật tính năng PAE trong CR4 và tạo page table tối thiểu để bao phủ vùng địa chỉ thấp theo các khối 2MB
- Ghi địa chỉ của bảng cấp cao nhất vào CR3 rồi bật paging
- Đặt bit LME trong EFER và nhảy sang mã 64-bit để đi vào long mode
- Khi địa chỉ và register đã được mở rộng lên 64-bit, kernel đã sẵn sàng để thực thi
Part 3 — Giải nén kernel, chỉnh lại địa chỉ và tự di chuyển
- Lúc này CPU đã ở chế độ 64-bit, và trong bộ nhớ đang có ảnh kernel đã được nén
- Một stub 64-bit nhỏ sẽ đảm nhận việc giải nén kernel và điều chỉnh địa chỉ
Dọn dẹp ban đầu và thiết lập cơ chế an toàn
- Stub tính toán vị trí thực tế mà nó đang chạy; nếu kernel có nguy cơ chồng lấn, nó sẽ tự sao chép (self-relocation) sang một vị trí an toàn
- Nó khởi tạo vùng BSS của chính mình và nạp một IDT đơn giản (bao gồm handler cho page fault và NMI)
- Khi xảy ra page fault, nó có thể lập tức bổ sung ánh xạ còn thiếu để phục hồi
- Nó tạo identity mapping cho các vùng cần thiết như kernel, boot parameter, command line buffer, v.v.
Giải nén kernel
- Hàm
extract_kernelđược thực thi để giải nén kernel- Hỗ trợ nhiều thuật toán nén như gzip, xz, zstd, lzo
- Sau khi giải nén, nó đọc ELF header để sao chép các section mã/dữ liệu tới đúng địa chỉ
- Nếu địa chỉ mà kernel được build khác với địa chỉ được nạp thực tế, nó sẽ thực hiện relocation
- Các lệnh hoặc con trỏ chứa địa chỉ sẽ được sửa lại cho khớp với vị trí thực trong bộ nhớ
- Khi mọi thứ đã sẵn sàng, nó nhảy tới hàm
start_kernel, và quá trình khởi tạo kernel chính thức bắt đầu
Cơ chế tự dịch chuyển của kernel (kASLR)
- kASLR (Kernel Address Space Layout Randomization) ngẫu nhiên hóa địa chỉ vật lý và địa chỉ ảo của kernel để tăng độ khó cho tấn công
- Trong lúc boot, nó chọn ngẫu nhiên hai mốc cơ sở
- Base vật lý: địa chỉ RAM nơi kernel thực sự được đặt
- Base ảo: điểm bắt đầu của địa chỉ ảo mà kernel sẽ sử dụng
- Trong lúc boot, nó chọn ngẫu nhiên hai mốc cơ sở
- Quá trình lựa chọn
- Tạo danh sách các vùng cần được bảo vệ như bootloader, initrd, command line buffer, v.v.
- Quét bản đồ bộ nhớ từ firmware để tìm các khoảng trống đủ lớn
- Dùng entropy thu được từ các lệnh sinh số ngẫu nhiên phần cứng, v.v. để chọn một slot ngẫu nhiên
- Nếu không có vùng phù hợp, nó quay về địa chỉ mặc định; với tùy chọn
nokaslr, việc ngẫu nhiên hóa sẽ bị vô hiệu hóa
Tóm tắt thuật ngữ
- Hexadecimal (hệ 16): được biểu diễn bằng tiền tố
0x, thuận tiện cho cấu trúc bit phần cứng và căn chỉnh - Register: bộ nhớ tạm bên trong CPU (CS, DS, SS, IP, SP, v.v.)
- Segment/Offset: cách tính địa chỉ trong real mode
(segment * 16 + offset) - BIOS/UEFI: firmware phụ trách khởi tạo hệ thống và nạp chương trình boot
- Bootloader (GRUB): nạp kernel và truyền thông tin hệ thống
- Stack/BSS: vùng lưu tạm cho hàm và vùng biến toàn cục được khởi tạo bằng 0
- Interrupt/NMI: cơ chế xử lý sự kiện phần cứng/phần mềm
- GDT/IDT: bảng định nghĩa segment và interrupt
- A20 Line: công tắc ngăn hiện tượng quấn địa chỉ 1MB
- Protected Mode/Long Mode: chế độ thực thi 32-bit và 64-bit
- Paging/Page Tables: địa chỉ ảo và địa chỉ vật lý
1 bình luận
Ý kiến Hacker News
Có thể xem bài của tôi tại Booting x86-64
Cách nói “UEFI khởi động máy” hơi dùng thuật ngữ chưa chính xác. Thực tế là firmware khởi động máy, còn chúng ta giao tiếp với firmware thông qua UEFI
Bài này đã lược bỏ khá nhiều phần thú vị của firmware hiện đại. Ví dụ, khi gọi
ExitBootServices()thì hệ thống đã vào long mode rồi. Không cần phải đi qua quá trình chuyển đổi real/protected mode nữaTrên Chromebook, phần này được công bố mã nguồn mở cùng với bootloader coreboot
Tài liệu liên quan có tại Chromium EC Zephyr README
Một điều nữa tôi tò mò là tại thời điểm nhấn nút nguồn vật lý thì quá trình đó được nối tới reset vector như thế nào. Đó là kiểu phép thuật của phần cứng và mạch điện tử
Về phần này, tài liệu cấu trúc đĩa của Pixelbeat khá hữu ích
Ngoài gợi ý là hãy giải thích sâu hơn thì tôi không nhận được thêm trợ giúp nào. Cuối cùng tôi đã không chuyển tới Dublin, và thôi thì mấy chuyện surveillance capitalism các kiểu, cứ coi như chùm nho vẫn còn xanh vậy