Hiểu về Linux Kernel: Quy trình khởi động chi tiết từ A đến Z
Bài viết này sẽ dẫn dắt bạn qua hành trình phức tạp diễn ra trong vài giây sau khi nhấn nút nguồn. Từ việc giải nén nhân, thiết lập bảng trang nhớ, khởi tạo bộ lập lịch và bộ quản lý bộ nhớ, cho đến chuyển giao quyền kiểm soát cho không gian người dùng, chúng ta sẽ khám phá cách Linux biến phần cứng thô sơ thành một hệ điều hành sống động.

Bạn đã bao giờ tự điều gì thực sự xảy ra giữa khoảnh khắc bạn nhấn nút nguồn và khi màn hình đăng nhập hiện lên không? Khoảng thời gian vỏn vẹn vài giây đó ẩn chứa một trong những chuỗi khởi tạo phức tạp nhất trong lĩnh vực máy tính. Hôm nay, chúng ta sẽ cùng nhau khám phá quy trình khởi động của nhân Linux (Linux Kernel).
Đây là bài đầu tiên trong series tìm hiểu sâu về nội tại của nhân Linux. Nếu bạn từng tò mò về những gì diễn ra "dưới mui xe" của hệ điều hành này, bạn đang ở đúng nơi.
Linux Header
Hãy tưởng tượng chúng ta đang thiết lập một thuộc địa không gian trên một hành tinh hoang sơ. Không khí, đường xá, hay điện năng đều chưa có. Chúng ta cử một đội tiên phong đi trước để biến tảng đá này thành một nơi có thể sinh sống trước khi nguồn cung cấp oxy cạn kiệt. Đội tiên phong này chính là mã khởi động của nhân Linux, và nhiệm vụ của họ là phải làm mọi thứ theo một trình tự cực kỳ cụ thể.
Giao nhận từ Bootloader
Trình khởi động (bootloader) như GRUB giao quyền kiểm soát cho nhân. Tuy nhiên, những gì chúng ta có trong tay lúc này rất ít ỏi. CPU đang chạy nhưng ở một chế độ cụ thể (thường là 32-bit Protected Mode hoặc 64-bit Long Mode). Bộ nhớ thì đang gặp vấn đề: nhân được nạp ở địa chỉ vật lý thấp nhưng lại được biên dịch để chạy ở địa chỉ ảo cao. Chúng ta cũng có bản đồ bộ nhớ từ firmware (E820) và một số tham số khởi động, nhưng chưa có bảng điều khiển (console), không có bộ cấp phát bộ nhớ (allocator), và không có ngắt (interrupt).
Giai đoạn 1: Bước nhảy Assembly
Giải nén nhân trước
Tệp mà bootloader giao cho chúng ta thực chất là một bzImage đã nén. CPU không thể thực thi các byte đã nén, nên đoạn mã đầu tiên chạy là một chương trình giải nén nhỏ nằm trong arch/x86/boot/compressed/. Nhiệm vụ của nó là giải nén nhân thực sự vào bộ nhớ và nhảy đến đó. Nó cũng chọn một địa chỉ cơ sở ngẫu nhiên cho nhân (KASLR) để gây khó khăn cho các kẻ tấn công.
Nhảy vào nhân thực sự
Sau khi giải nén, điều khiển được chuyển đến startup_64. Tại đây, chúng ta ở trạng thái "trần trụi" gần như hoàn toàn bằng Assembly. Việc đầu tiên nhân làm là thiết lập con trỏ ngăn xếp (stack pointer) và cài đặt các bảng tra cứu tối thiểu mà CPU yêu cầu (GDT và IDT).
Kiểm tra phần cứng và mã hóa
Trước khi đi xa hơn, nhân kiểm tra xem CPU có hỗ trợ Long Mode và SSE2 hay không thông qua verify_cpu. Nếu thiếu thứ gì đó quan trọng, quá trình khởi động sẽ dừng lại ngay lập tức. Sau đó, nếu phần cứng hỗ trợ mã hóa bộ nhớ (như SME của AMD), nhân sẽ bật tính năng này ngay lập tức vì không thể mã hóa dữ liệu đã ghi sau này.
Sửa lỗi địa chỉ Nhớ sự không khớp về địa chỉ bộ nhớ lúc đầu? Nhân sửa lỗi này bằng "page table fixup". Nó tính toán sự chênh lệch giữa nơi mã nghĩ nó đang chạy và nơi nó thực sự nằm, sau đó vá các mục trong bảng trang. Khi xong, địa chỉ ảo sẽ trỏ đúng đến byte vật lý.
Linux Kernel Startup Diagram 1
Giai đoạn 2: Khởi tạo C sớm
Với bảng trang đã được sửa, nhân chuyển sang hàm C đầu tiên: x86_64_start_kernel. Chúng ta vẫn ở chế độ đội tiên phong, nhưng công cụ tốt hơn một chút.
Dọn dẹp bàn làm việc
clear_bss() xóa sạch phần .bss - vùng chứa các biến toàn cục chưa được khởi tạo. Điều này đảm bảo mọi thùng chứa bắt đầu đều trống trước khi đưa dữ liệu vào.
KASAN và Quy trình khẩn cấp Nếu nhân được xây dựng với KASAN (Kernel Address Sanitizer) để phát hiện lỗi bộ nhớ, chúng ta cần kích hoạt nó. Tuy nhiên, vì chưa có bộ cấp phát bộ nhớ thực sự, chúng ta trỏ toàn bộ vùng shadow của KASAN vào một trang zero duy nhất.
Tiếp theo, chúng ta cài đặt một IDT tối thiểu (idt_setup_early_handler). Nếu có lỗi xảy ra (như chia cho 0), CPU cần biết phải gọi hàm nào. Nếu không có IDT, CPU sẽ gặp triple fault và khởi động lại mà không có thông báo lỗi gì.
Lưu ghi chú của Bootloader
copy_bootdata() sao chép các thông tin quan trọng từ bootloader (dòng lệnh, bản đồ bộ nhớ, vị trí initrd) vào cấu trúc boot_params của nhân để tránh bị ghi đè.
Vá CPU Cuối cùng, nhân vá microcode của chính CPU. Các lỗi phần cứng như Spectre hay MDS thường được sửa bằng các bản cập nhật microcode này, và nhân áp dụng chúng ngay trong giai đoạn này.
Giai đoạn 3: Khám phá phần cứng và Thiết lập bộ nhớ
Bây giờ chúng ta gọi setup_arch(). Đây là lúc nhân tìm hiểu chính xác về loại "hành tinh" mà nó đang đứng.
Danh mục kỹ năng của CPU
early_cpu_init() hỏi trực tiếp chip CPU về khả năng của nó (hướng dẫn vector, mã hóa phần cứng, v.v.) và lưu vào boot_cpu_data. Cấu trúc này giúp nhân quyết định việc sử dụng phiên bản tối ưu của một hàm nào đó sau này.
Đọc bản đồ khảo sát
e820__memory_setup() đọc bản đồ bộ nhớ từ firmware và làm sạch nó. Firmware thường không đáng tin cậy, nên nhân phải chuẩn hóa nó. Sau đó, e820__memblock_setup() đưa bản đồ sạch này vào memblock - một bộ cấp phát sơ khai chỉ theo dõi "vùng này trống, vùng đó dành riêng".
Đài phát thanh đầu tiên
Cho đến nay, nhân hoàn toàn im lặng. parse_early_param() xử lý các tham số khởi động sớm. Nếu bạn truyền earlyprintk=serial,ttyS0, một trình điều khiển serial tối thiểu sẽ được thiết lập, cho phép nhân gửi tin nhắn debug ra bên ngoài.
Ánh xạ toàn bộ RAM
Sau khi biết địa hình, init_mem_mapping() xây dựng bản đồ trực tiếp (direct map) của nhân - một vùng địa chỉ ảo khổng lồ nơi mọi trang vật lý đều có địa chỉ ảo cố định. Nhờ đó, nhân có thể tiếp cận bất kỳ byte RAM nào.
Linux Kernel Startup Diagram 2
Giai đoạn 4: Các hệ thống con cốt lõi hoạt động
Chúng ta bước vào start_kernel() - hàm dài nhất trong quá trình khởi động. Đây là lúc chúng ta chuyển từ "lều và lương thực khẩn cấp" sang "một thị trấn hoạt động tốt".
Làm cho CPU khả dụng
Đầu tiên, boot_cpu_init() đánh dấu CPU này là trực tuyến. Sau đó, các tính năng như static keys và self-patching được khởi tạo để tối ưu hóa hiệu suất runtime.
Bộ nhớ sống động: mm_core_init()
Đây là bước ngoặt lớn. Trước đây chỉ có memblock thô sơ. Giờ đây, các bộ cấp phát thực sự được kích hoạt:
- Buddy allocator: Cấp phát trang vật lý theo khối lũy thừa của 2.
- Slab allocator (SLUB): Chuyên về các đối tượng nhỏ như
task_struct, giúpkmalloc()hoạt động. - Vmalloc: Cấp phát vùng nhớ liên tục trong không gian ảo từ các trang vật lý rời rạc.
Bộ lập lịch: sched_init()
Trước đây chỉ có một luồng duy nhất. Sau sched_init(), bộ lập lịch hoạt động. Mỗi CPU có hàng đợi chạy (runqueue) riêng. Luồng khởi động ban đầu (init_task) được gán nhãn lại thành tác vụ rảnh rỗi (idle task) của CPU khởi động.
RCU, Workqueues và Ngắt
Các cơ chế đồng bộ hóa như RCU (Read-Copy-Update) và Workqueues được kích hoạt. Sau đó là hệ thống Ngắt và Thời gian. timekeeping_init() đọc đồng hồ thực tế (RTC) để xác định thời gian. Cuối cùng, local_irq_enable() bật ngắt toàn cục. Từ giờ, phần cứng có thể ngắt nhân để báo cáo sự kiện.
Bảng điều khiển thực
console_init() kích hoạt các trình điều khiển bảng điều khiển thực sự. Tất cả thông điệp log đã bị kẹt trong bộ đệm giờ đây sẽ được đổ ra màn hình trong một lần.
Tối ưu hóa CPU
arch_cpu_finalize_init() phát hiện đầy đủ tính năng CPU và áp dụng các bản vá mitigations cho Spectre/Meltdown. Hơn nữa, alternative_instructions() cho phép nhân tự viết lại mã của mình để tối ưu cho CPU cụ thể này (ví dụ: dùng AVX-512 nếu có).
Giai đoạn 5: Từ đơn luồng sang đa nhiệm
Linux là hệ điều hành đa nhiệm. Sự chuyển đổi này xảy ra trong rest_init().
Tạo ra các quy trình thực sự đầu tiên Hàm này tạo ra hai luồng kernel:
- PID 1 (kernel_init): Sẽ trở thành tiến trình
inittrong không gian người dùng (tổ tiên của tất cả các tiến trình khác). Đây là "thống đốc" của thuộc địa. - PID 2 (kthreadd): Trình daemon quản lý luồng kernel. Bất cứ khi nào mã kernel cần tạo luồng mới, yêu cầu sẽ được gửi đến đây.
Vòng lặp Idle
Sau khi tạo ra hai luồng trên, luồng khởi động ban đầu gọi schedule_preempt_disabled() và nhường CPU. Khi điều khiển quay lại nó, nó sẽ trở thành tác vụ rảnh rỗi (PID 0) và chạy vòng lặp do_idle() mãi mãi. Đội tiên phong đã hoàn thành nhiệm vụ và trở thành đội bảo trì.
Giai đoạn 6: Hoàn thiện và Khởi chạy không gian người dùng
kernel_init (PID 1) còn một danh sách việc phải làm trước khi chuyển sang không gian người dùng.
Thu nhân công và Đánh thức các CPU khác
workqueue_init() hoàn tất hệ thống workqueue và các luồng worker (kworker/...) bắt đầu đi làm để xử lý các công việc bị hoãn. Sau đó, smp_init() gửi ngắt liên vi xử lý (IPI) để đánh thức các nhân CPU khác đang ngủ. Mỗi nhân sẽ tự khởi động qua quy trình đơn giản hóa của riêng mình trước khi gia nhập hệ thống.
Từ đây, kernel_init sẽ tiếp tục chuẩn bị hệ thống, gắn các hệ thống tập tin root, và cuối cùng thực thi lệnh exec() để chạy chương trình init đầu tiên trong không gian người dùng (như /sbin/init hay systemd), chính thức bàn giao quyền kiểm soát cho người dùng.
Bài viết liên quan

Công nghệ
Cerebras, đối tác thân thiết của OpenAI, sẵn sàng cho đợt IPO kỷ lục định giá tới 26,6 tỷ USD
04 tháng 5, 2026

Công nghệ
Microsoft giới thiệu Surface Pro 12 và Surface Laptop 8: Sức mạnh chip Intel, giá thành gây sốc
19 tháng 5, 2026
Công nghệ
Trang web ngăn chặn tự tử tại Hà Lan bị phát hiện chia sẻ dữ liệu người dùng cho các công ty công nghệ
13 tháng 5, 2026
