Ví dụ runnable
Về mặt kỹ thuật, một chương trình chạy mà không có HĐH, là HĐH. Vì vậy, hãy xem cách tạo và chạy một số hệ điều hành rất nhỏ xin chào thế giới.
Mã của tất cả các ví dụ dưới đây có mặt trên repo GitHub này .
Giày cao cổ
Trên x86, điều đơn giản nhất và ở mức thấp nhất bạn có thể làm là tạo Master Boot sector (MBR) , đây là một loại sector khởi động , sau đó cài đặt nó vào đĩa.
Ở đây chúng tôi tạo một printf
cuộc gọi với một cuộc gọi duy nhất :
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
Kết quả:
Đã thử nghiệm trên Ubuntu 18.04, QEMU 2.11.1.
main.img
chứa những điều sau đây:
\364
in octal == 0xf4
in hex: mã hóa cho một hlt
lệnh, thông báo cho CPU ngừng hoạt động.
Do đó, chương trình của chúng tôi sẽ không làm gì cả: chỉ bắt đầu và dừng lại.
Chúng tôi sử dụng số bát phân vì \x
số hex không được chỉ định bởi POSIX.
Chúng ta có thể có được mã hóa này một cách dễ dàng với:
echo hlt > a.asm
nasm -f bin a.asm
hd a
nhưng 0xf4
mã hóa cũng được ghi lại trong hướng dẫn sử dụng Intel.
%509s
sản xuất 509 không gian. Cần phải điền vào tệp cho đến byte 510.
\125\252
trong bát phân == 0x55
theo sau 0xaa
: byte ma thuật được yêu cầu bởi phần cứng. Chúng phải là byte 511 và 512.
Nếu không có, phần cứng sẽ không coi đây là đĩa khởi động.
Lưu ý rằng ngay cả khi không làm gì, một vài ký tự đã được in trên màn hình. Chúng được in bởi phần sụn và phục vụ để xác định hệ thống.
Chạy trên phần cứng thực
Trình giả lập là niềm vui, nhưng phần cứng là vấn đề thực sự.
Tuy nhiên, xin lưu ý rằng điều này rất nguy hiểm và bạn có thể xóa nhầm đĩa của mình: chỉ thực hiện việc này trên các máy cũ không chứa dữ liệu quan trọng! Hoặc thậm chí tốt hơn, các bảng điều khiển như Raspberry Pi, xem ví dụ ARM bên dưới.
Đối với một máy tính xách tay thông thường, bạn phải làm một cái gì đó như:
Ghi hình ảnh vào thanh USB (sẽ hủy dữ liệu của bạn!):
sudo dd if=main.img of=/dev/sdX
cắm USB trên máy tính
bật nó lên
bảo nó khởi động từ USB.
Điều này có nghĩa là làm cho phần sụn chọn USB trước đĩa cứng.
Nếu đó không phải là hành vi mặc định của máy của bạn, hãy tiếp tục nhấn Enter, F12, ESC hoặc các phím lạ khác sau khi bật nguồn cho đến khi bạn nhận được menu khởi động nơi bạn có thể chọn khởi động từ USB.
Thường có thể định cấu hình thứ tự tìm kiếm trong các menu đó.
Ví dụ: trên Lenovo Thinkpad T430 cũ của tôi, UEFI BIOS 1.16, tôi có thể thấy:
Chào thế giới
Bây giờ chúng tôi đã thực hiện một chương trình tối thiểu, hãy chuyển sang một thế giới xin chào.
Câu hỏi rõ ràng là: làm thế nào để làm IO? Một vài lựa chọn:
- yêu cầu phần sụn, ví dụ BIOS hoặc UEFI, làm gì cho chúng tôi
- VGA: vùng nhớ đặc biệt được in ra màn hình nếu được ghi vào. Có thể được sử dụng trên chế độ Bảo vệ.
- viết một trình điều khiển và nói chuyện trực tiếp với phần cứng màn hình. Đây là cách "phù hợp" để làm điều đó: mạnh mẽ hơn, nhưng phức tạp hơn.
cổng nối tiếp . Đây là một giao thức được tiêu chuẩn hóa rất đơn giản để gửi và lấy các ký tự từ một thiết bị đầu cuối máy chủ.
Nguồn .
Thật không may, nó không được tiếp xúc trên hầu hết các máy tính xách tay hiện đại, nhưng là cách phổ biến để đi đến các bảng phát triển, xem các ví dụ ARM dưới đây.
Đây thực sự là một sự xấu hổ, vì các giao diện như vậy thực sự hữu ích để gỡ lỗi kernel Linux chẳng hạn .
sử dụng các tính năng gỡ lỗi của chip. ARM gọi semihosting của họ chẳng hạn. Trên phần cứng thực, nó đòi hỏi một số hỗ trợ phần cứng và phần mềm bổ sung, nhưng trên trình giả lập, nó có thể là một tùy chọn tiện lợi miễn phí. Ví dụ .
Ở đây chúng tôi sẽ làm một ví dụ BIOS vì nó đơn giản hơn trên x86. Nhưng lưu ý rằng nó không phải là phương pháp mạnh mẽ nhất.
chính.S
.code16
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
link.ld
SECTIONS
{
. = 0x7c00;
.text :
{
__start = .;
*(.text)
. = 0x1FE;
SHORT(0xAA55)
}
}
Lắp ráp và liên kết với:
gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
Kết quả:
Đã thử nghiệm trên: Lenovo Thinkpad T430, UEFI BIOS 1.16. Đĩa được tạo trên máy chủ Ubuntu 18.04.
Bên cạnh các hướng dẫn lắp ráp người dùng tiêu chuẩn, chúng tôi có:
.code16
: báo cho GAS xuất mã 16 bit
cli
: vô hiệu hóa các ngắt phần mềm. Chúng có thể khiến bộ xử lý bắt đầu chạy lại sauhlt
int $0x10
: thực hiện cuộc gọi BIOS. Đây là những gì in từng ký tự một.
Các cờ liên kết quan trọng là:
--oformat binary
: xuất mã lắp ráp nhị phân thô, không làm cong nó trong tệp ELF như trường hợp đối với các tệp thực thi người dùng thông thường.
Sử dụng C thay vì lắp ráp
Vì C biên dịch để lắp ráp, sử dụng C mà không có thư viện chuẩn khá đơn giản, về cơ bản bạn chỉ cần:
- một tập lệnh liên kết để đặt mọi thứ vào bộ nhớ ở đúng nơi
- các cờ báo cho GCC không sử dụng thư viện chuẩn
- một điểm nhập lắp ráp nhỏ đặt trạng thái C yêu cầu
main
, đáng chú ý là:
TODO: liên kết để một số ví dụ x86 trên GitHub. Đây là một ARM tôi đã tạo .
Tuy nhiên, mọi thứ sẽ trở nên thú vị hơn nếu bạn muốn sử dụng thư viện tiêu chuẩn, vì chúng tôi không có nhân Linux, nơi thực hiện nhiều chức năng thư viện tiêu chuẩn C thông qua POSIX .
Một vài khả năng, mà không cần đến một hệ điều hành toàn diện như Linux, bao gồm:
CÁNH TAY
Trong ARM, các ý tưởng chung là như nhau. Tôi đã tải lên:
Đối với Raspberry Pi, https://github.com/dwelch67/raspberrypi trông giống như hướng dẫn phổ biến nhất hiện nay.
Một số khác biệt từ x86 bao gồm:
IO được thực hiện bằng cách viết trực tiếp vào địa chỉ ma thuật, không có in
và out
hướng dẫn.
Đây được gọi là bộ nhớ ánh xạ IO .
đối với một số phần cứng thực sự, như Raspberry Pi, bạn có thể tự thêm phần sụn (BIOS) vào hình ảnh đĩa.
Đó là một điều tốt, vì nó làm cho việc cập nhật firmware đó trở nên minh bạch hơn.
Chương trình cơ sở
Trên thực tế, khu vực khởi động của bạn không phải là phần mềm đầu tiên chạy trên CPU của hệ thống.
Cái thực sự chạy đầu tiên là cái gọi là phần sụn , là một phần mềm:
- được thực hiện bởi các nhà sản xuất phần cứng
- nguồn thường đóng nhưng có khả năng dựa trên C
- được lưu trữ trong bộ nhớ chỉ đọc và do đó khó / không thể sửa đổi nếu không có sự đồng ý của nhà cung cấp.
Các phần mềm nổi tiếng bao gồm:
- BIOS : firmware x86 cũ tất cả hiện tại. SeaBIOS là triển khai mã nguồn mở mặc định được QEMU sử dụng.
- UEFI : Người kế vị BIOS, được tiêu chuẩn hóa tốt hơn, nhưng có khả năng hơn, và vô cùng nở rộ.
- Coreboot : nỗ lực mã nguồn mở chéo cao quý
Phần sụn thực hiện những việc như:
lặp qua từng đĩa cứng, USB, mạng, v.v. cho đến khi bạn tìm thấy thứ gì đó có khả năng khởi động.
Khi chúng tôi chạy QEMU, hãy -hda
nói rằng đó main.img
là một đĩa cứng được kết nối với phần cứng và
hda
là cái đầu tiên được thử, và nó được sử dụng.
tải 512 byte đầu tiên vào địa chỉ bộ nhớ RAM 0x7c00
, đặt RIP của CPU ở đó và để nó chạy
hiển thị những thứ như menu khởi động hoặc lệnh in BIOS trên màn hình
Phần sụn cung cấp chức năng giống như hệ điều hành mà hầu hết các hệ điều hành phụ thuộc. Ví dụ: một tập hợp con Python đã được chuyển để chạy trên BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Có thể lập luận rằng các phần mềm không thể phân biệt được với các HĐH và phần sụn đó là chương trình kim loại trần "thật" duy nhất mà người ta có thể làm.
Như nhà phát triển CoreOS này đặt nó :
Phần khó
Khi bạn cấp nguồn cho PC, các chip tạo nên chipset (cầu bắc, cầu nam và SuperIO) vẫn chưa được khởi tạo đúng cách. Mặc dù ROM ROM đã bị loại bỏ khỏi CPU hết mức có thể, nhưng CPU này có thể truy cập được, bởi vì nó phải như vậy, nếu không CPU sẽ không có hướng dẫn để thực thi. Điều này không có nghĩa là BIOS ROM được ánh xạ hoàn toàn, thường thì không. Nhưng vừa đủ được ánh xạ để quá trình khởi động diễn ra. Bất kỳ thiết bị khác, chỉ cần quên nó.
Khi bạn chạy Coreboot theo QEMU, bạn có thể thử nghiệm các lớp Coreboot cao hơn và với tải trọng, nhưng QEMU cung cấp rất ít cơ hội để thử nghiệm mã khởi động cấp thấp. Đối với một điều, RAM chỉ hoạt động ngay từ đầu.
Đăng trạng thái ban đầu của BIOS
Giống như nhiều thứ trong phần cứng, tiêu chuẩn hóa còn yếu và một trong những điều bạn không nên dựa vào là trạng thái đăng ký ban đầu khi mã của bạn bắt đầu chạy sau BIOS.
Vì vậy, hãy tạo cho mình một ưu tiên và sử dụng một số mã khởi tạo như sau: https://stackoverflow.com/a/32509555/895245
Các thanh ghi thích %ds
và %es
có tác dụng phụ quan trọng, vì vậy bạn nên loại bỏ chúng ngay cả khi bạn không sử dụng chúng một cách rõ ràng.
Lưu ý rằng một số trình giả lập đẹp hơn phần cứng thực và cung cấp cho bạn trạng thái ban đầu tốt đẹp. Sau đó, khi bạn chạy trên phần cứng thực sự, mọi thứ sẽ phá vỡ.
GNU GRUB Multiboot
Các phần khởi động rất đơn giản, nhưng chúng không thuận tiện lắm:
- bạn chỉ có thể có một hệ điều hành trên mỗi đĩa
- mã tải phải thực sự nhỏ và vừa với 512 byte. Điều này có thể được giải quyết bằng cuộc gọi BIOS int 0x13 .
- bạn phải tự khởi động rất nhiều, như chuyển sang chế độ được bảo vệ
Chính vì những lý do đó mà GNU GRUB đã tạo ra một định dạng tệp thuận tiện hơn gọi là multiboot.
Ví dụ làm việc tối thiểu: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Tôi cũng sử dụng nó trên repo ví dụ GitHub của mình để có thể dễ dàng chạy tất cả các ví dụ trên phần cứng thực mà không cần ghi USB một triệu lần. Trên QEMU nó trông như thế này:
Nếu bạn chuẩn bị HĐH của mình dưới dạng tệp multiboot, GRUB có thể tìm thấy nó trong một hệ thống tệp thông thường.
Đây là những gì hầu hết các distro làm, đặt hình ảnh hệ điều hành /boot
.
Các tệp Multiboot về cơ bản là một tệp ELF với một tiêu đề đặc biệt. Chúng được GRUB chỉ định tại: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Bạn có thể biến một tệp multiboot thành một đĩa có thể khởi động được grub-mkrescue
.
El Torito
Định dạng có thể được ghi vào đĩa CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_st Chuẩn% 29
Cũng có thể tạo ra một hình ảnh lai hoạt động trên cả ISO hoặc USB. Điều này có thể được thực hiện với grub-mkrescue
( ví dụ ) và cũng được nhân Linux thực hiện khi make isoimage
sử dụng isohybrid
.
Tài nguyên