Bootloader: Viết Bootloader đầu tiên cho kiến trúc x86
Viết bootloader đầu tiên cho kiến trúc x86 với assembly NASM, hiểu quá trình boot từ BIOS và test trên QEMU.
1. Quá trình khởi động máy tính
1.1. Power Supply Unit (PSU)
Máy tính của chúng ta khởi động như thế nào? Chắc hẳn nhiều bạn từng tự hỏi điều gì thực sự xảy ra khi ta nhấn nút nguồn.
Có một thành phần gọi là PSU (Power Supply Unit) trong phần cứng của hệ thống. Khi bạn nhấn nút nguồn:
- PSU nhận tín hiệu từ nút nguồn
- Chuyển đổi dòng điện xoay chiều (AC) thành dòng điện một chiều (DC)
- Cung cấp năng lượng cho các bộ phận khác của hệ thống
- Gửi tín hiệu ‘power-good’ đến BIOS khi điện áp ổn định
1.2. Power On Self Test (POST)
Khi nhận được tín hiệu ‘power-good’, BIOS bắt đầu quá trình POST (Power On Self Test):
- Kiểm tra nguồn điện có đúng hay không
- Kiểm tra bộ nhớ có bị hỏng không
- Phát hiện các thiết bị đang được kết nối
- Báo lỗi bằng mã số trên I/O port hoặc chuỗi tiếng bíp nếu có vấn đề
1.3. BIOS và Interrupt Vector Table
Sau khi POST hoàn tất, quyền điều khiển được chuyển đến BIOS:
- BIOS tạo ra Interrupt Vector Table (IVT)
- IVT lưu trữ địa chỉ của các Interrupt Service Routines
- Interrupt là cơ chế phần cứng sử dụng để báo hiệu sự kiện cho CPU
1.4. Boot Sector
Công việc chính của BIOS là tải bootloader. Nhưng làm sao biết nó nằm ở đâu?
Boot sector là khu vực đặc biệt trên thiết bị lưu trữ:
- Thường là sector đầu tiên của disk (sector 0, track 0, head 0)
- BIOS load dữ liệu từ boot sector vào bộ nhớ tại địa chỉ 0x7c00
- Sử dụng ngắt 0x19 để nhảy đến địa chỉ này
2. Chuẩn bị môi trường
Chúng ta cần cài đặt các công cụ cần thiết:
1
sudo apt-get install nasm qemu-system-x86
- NASM: Assembler cho Intel x86 architecture
- QEMU: Trình giả lập để test bootloader
3. Viết Bootloader đầu tiên
3.1. Source code
Tạo file myboot.asm
:
org 0x7c00
bits 16
Start:
cli
hlt
times 510 - ($-$$) db 0
dw 0xAA55
3.2. Giải thích từng dòng code
org 0x7c00
- Bootloader được load vào bộ nhớ tại địa chỉ 0x7c00
- Yêu cầu NASM set tất cả addresses theo địa chỉ này
bits 16
- Kiến trúc x86 khởi động ở 16-bit real mode
- Có thể chuyển sang 32-bit protected mode sau này
cli
và hlt
cli
: Clear all interruptshlt
: Halt the system- Đảm bảo CPU không chạy random instructions
times 510 - ($-$$) db 0
- Một sector chỉ chứa 512 bytes
$
: địa chỉ dòng hiện tại$$
: địa chỉ dòng đầu tiên($-$$)
: kích thước chương trình- Padding các byte còn lại bằng 0 để đủ 510 bytes
dw 0xAA55
- Hai byte cuối (511-512) lưu boot signature
0xAA
tại byte 511,0x55
tại byte 512- Cho BIOS biết sector này có thể boot được
4. Build và test Bootloader
4.1. Assemble code
Chuyển đổi assembly code sang machine code:
1
nasm -f bin -o myboot.bin myboot.asm
4.2. Tạo floppy image
Sao chép bootloader vào sector đầu tiên của floppy image:
1
dd status=noxfer conv=notrunc if=myboot.bin of=myboot.flp
4.3. Chạy trên QEMU
Khởi động bootloader đầu tiên của bạn:
1
qemu-system-i386 -fda myboot.flp
Bootloader chạy thành công trên QEMU
Chúc mừng! Bạn đã viết thành công bootloader đầu tiên cho kiến trúc x86. Mặc dù nó chỉ halt hệ thống, nhưng đây là bước đầu quan trọng để hiểu về low-level system programming.