Contoh yang bisa dijalankan
Secara teknis, program yang berjalan tanpa OS, adalah OS. Jadi mari kita lihat cara membuat dan menjalankan beberapa OS dunia halo yang sangat kecil.
Kode semua contoh di bawah ini ada pada repo GitHub ini .
Sektor boot
Pada x86, hal tingkat paling sederhana dan terendah yang dapat Anda lakukan adalah membuat Master Boot Sector (MBR) , yang merupakan jenis sektor boot , dan kemudian instal ke disk.
Di sini kita membuat satu dengan satu printf
panggilan:
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
Hasil:
Diuji pada Ubuntu 18.04, QEMU 2.11.1.
main.img
berisi yang berikut ini:
\364
dalam oktal == 0xf4
dalam hex: pengkodean untuk hlt
instruksi, yang memberitahu CPU untuk berhenti bekerja.
Karenanya program kami tidak akan melakukan apa-apa: hanya memulai dan berhenti.
Kami menggunakan oktal karena \x
nomor hex tidak ditentukan oleh POSIX.
Kami dapat memperoleh enkode ini dengan mudah dengan:
echo hlt > a.asm
nasm -f bin a.asm
hd a
tetapi 0xf4
pengkodean juga didokumentasikan pada manual Intel tentu saja.
%509s
menghasilkan 509 ruang. Diperlukan untuk mengisi file hingga byte 510.
\125\252
dalam oktal == 0x55
diikuti oleh 0xaa
: byte ajaib yang diperlukan oleh perangkat keras. Itu harus byte 511 dan 512.
Jika tidak ada, perangkat keras tidak akan memperlakukan ini sebagai disk yang dapat di-boot.
Perhatikan bahwa bahkan tanpa melakukan apa pun, beberapa karakter sudah dicetak di layar. Itu dicetak oleh firmware, dan berfungsi untuk mengidentifikasi sistem.
Jalankan pada perangkat keras nyata
Emulator memang menyenangkan, tetapi perangkat keras adalah masalahnya.
Perhatikan bahwa ini berbahaya, dan Anda dapat menghapus disk Anda secara tidak sengaja: hanya lakukan ini pada mesin lama yang tidak mengandung data penting! Atau bahkan lebih baik, devboards seperti Raspberry Pi, lihat contoh ARM di bawah ini.
Untuk laptop biasa, Anda harus melakukan sesuatu seperti:
Bakar gambar ke stik USB (akan menghancurkan data Anda!):
sudo dd if=main.img of=/dev/sdX
pasang USB di komputer
Hidupkan
katakan itu untuk boot dari USB.
Ini berarti membuat firmware memilih USB sebelum hard disk.
Jika itu bukan perilaku default mesin Anda, terus tekan Enter, F12, ESC atau kunci aneh lainnya setelah dinyalakan hingga Anda mendapatkan menu boot di mana Anda dapat memilih untuk boot dari USB.
Seringkali dimungkinkan untuk mengonfigurasi urutan pencarian di menu-menu itu.
Misalnya, pada Lenovo Thinkpad T430 lama saya, UEFI BIOS 1.16, saya dapat melihat:
Halo Dunia
Sekarang kita telah membuat program minimal, mari kita pindah ke dunia halo.
Pertanyaan yang jelas adalah: bagaimana cara melakukan IO? Beberapa pilihan:
- minta firmware, misalnya BIOS atau UEFI, untuk melakukannya jika untuk kita
- VGA: wilayah memori khusus yang akan dicetak ke layar jika ditulis. Dapat digunakan pada mode Protected.
- menulis driver dan berbicara langsung ke perangkat keras layar. Ini adalah cara yang "tepat" untuk melakukannya: lebih kuat, tetapi lebih kompleks.
port serial . Ini adalah protokol standar yang sangat sederhana yang mengirim dan mengambil karakter dari terminal host.
Sumber .
Sayangnya ini tidak terpapar pada kebanyakan laptop modern, tetapi merupakan cara umum untuk menggunakan papan pengembangan, lihat contoh ARM di bawah ini.
Ini benar-benar memalukan, karena antarmuka seperti itu sangat berguna untuk men-debug kernel Linux misalnya .
gunakan fitur debug chip. ARM menyebut semihosting mereka misalnya. Pada perangkat keras nyata, ini memerlukan beberapa perangkat keras dan dukungan perangkat lunak tambahan, tetapi pada emulator itu bisa menjadi pilihan nyaman gratis. Contoh .
Di sini kita akan melakukan contoh BIOS karena lebih sederhana pada x86. Tetapi perhatikan bahwa itu bukan metode yang paling kuat.
utama
.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)
}
}
Kumpulkan dan tautkan dengan:
gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
Hasil:
Diuji pada: Lenovo Thinkpad T430, UEFI BIOS 1.16. Disk dihasilkan pada host Ubuntu 18.04.
Selain instruksi perakitan pengguna darat standar, kami memiliki:
.code16
: memberitahu GAS untuk mengeluarkan kode 16-bit
cli
: nonaktifkan interupsi perangkat lunak. Itu bisa membuat prosesor mulai berjalan lagi setelahhlt
int $0x10
: melakukan panggilan BIOS. Inilah yang mencetak karakter satu per satu.
Bendera tautan penting adalah:
--oformat binary
: mengeluarkan kode rakitan biner mentah, jangan membengkokkannya di dalam file ELF seperti halnya untuk executable userland biasa.
Gunakan C sebagai ganti perakitan
Karena C mengkompilasi ke perakitan, menggunakan C tanpa pustaka standar cukup sederhana, pada dasarnya Anda hanya perlu:
- skrip linker untuk meletakkan sesuatu di memori di tempat yang tepat
- bendera yang memberi tahu GCC untuk tidak menggunakan perpustakaan standar
- titik masuk perakitan kecil yang menetapkan status C diperlukan
main
, terutama:
TODO: tautan jadi beberapa contoh x86 di GitHub. Ini ARM yang saya buat .
Akan lebih menyenangkan jika Anda ingin menggunakan pustaka standar, karena kami tidak memiliki kernel Linux, yang mengimplementasikan banyak fungsi pustaka standar C melalui POSIX .
Beberapa kemungkinan, tanpa masuk ke sistem operasi lengkap seperti Linux, termasuk:
Newlib
Contoh terperinci di: https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
Di Newlib, Anda harus mengimplementasikan syscall sendiri, tetapi Anda mendapatkan sistem yang sangat minim, dan sangat mudah untuk menerapkannya.
Misalnya, Anda dapat mengarahkan ulang printf
ke sistem UART atau ARM, atau menerapkannya exit()
dengan semihosting .
sistem operasi tertanam seperti FreeRTOS dan Zephyr .
Sistem operasi seperti itu biasanya memungkinkan Anda untuk mematikan penjadwalan pre-emptive, sehingga memberi Anda kendali penuh atas runtime program.
Mereka dapat dilihat sebagai semacam Newlib pra-implementasi.
LENGAN
Dalam ARM, ide-ide umumnya sama. Saya telah mengunggah:
Untuk Raspberry Pi, https://github.com/dwelch67/raspberrypi sepertinya tutorial yang paling populer saat ini.
Beberapa perbedaan dari x86 termasuk:
IO dilakukan dengan menulis ke alamat sihir secara langsung, tidak ada in
dan out
instruksi.
Ini disebut memori yang dipetakan IO .
untuk beberapa perangkat keras nyata, seperti Raspberry Pi, Anda dapat menambahkan sendiri firmware (BIOS) ke gambar disk.
Itu adalah hal yang baik, karena itu membuat memperbarui firmware itu lebih transparan.
Firmware
Sebenarnya, sektor boot Anda bukan perangkat lunak pertama yang berjalan pada CPU sistem.
Apa yang sebenarnya berjalan pertama adalah apa yang disebut firmware , yang merupakan perangkat lunak:
- dibuat oleh produsen perangkat keras
- biasanya sumber tertutup tetapi kemungkinan berbasis C
- disimpan dalam memori hanya-baca, dan karenanya lebih sulit / tidak mungkin untuk dimodifikasi tanpa persetujuan vendor.
Firmwares yang terkenal meliputi:
- BIOS : firmware x86 lama semua sekarang. SeaBIOS adalah implementasi open source default yang digunakan oleh QEMU.
- UEFI : penerus BIOS, lebih terstandarisasi, tetapi lebih mampu, dan sangat kembung.
- Coreboot : upaya sumber terbuka lintas lengkung mulia
Firmware melakukan hal-hal seperti:
lewati setiap hard disk, USB, jaringan, dll. hingga Anda menemukan sesuatu yang dapat di-boot.
Ketika kami menjalankan QEMU, -hda
mengatakan itu main.img
adalah hard disk yang terhubung ke perangkat keras, dan
hda
adalah yang pertama diadili, dan digunakan.
muat 512 byte pertama ke alamat memori RAM 0x7c00
, letakkan RIP CPU di sana, dan biarkan berjalan
menunjukkan hal-hal seperti menu boot atau panggilan cetak BIOS pada tampilan
Firmware menawarkan fungsionalitas mirip OS di mana sebagian besar OS bergantung. Misalnya subset Python telah porting untuk dijalankan di BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Dapat dikatakan bahwa firmware tidak dapat dibedakan dari OS, dan bahwa firmware adalah satu-satunya pemrograman bare metal yang "benar" yang dapat dilakukan.
Seperti yang dikatakan CoreOS dev ini :
Bagian yang sulit
Saat Anda menyalakan PC, chip yang membentuk chipset (northbridge, southbridge dan SuperIO) belum diinisialisasi dengan benar. Meskipun ROM BIOS sejauh mungkin dihapus dari CPU, ini dapat diakses oleh CPU, karena harus demikian, jika tidak, CPU tidak akan memiliki instruksi untuk mengeksekusi. Ini tidak berarti bahwa ROM BIOS sepenuhnya dipetakan, biasanya tidak. Tetapi cukup dipetakan untuk memulai proses boot. Perangkat lain, lupakan saja.
Ketika Anda menjalankan Coreboot di bawah QEMU, Anda dapat bereksperimen dengan lapisan Coreboot yang lebih tinggi dan dengan muatan, tetapi QEMU menawarkan sedikit peluang untuk bereksperimen dengan kode startup tingkat rendah. Untuk satu hal, RAM hanya berfungsi sejak awal.
Poskan keadaan awal BIOS
Seperti banyak hal dalam perangkat keras, standardisasi lemah, dan salah satu hal yang tidak boleh Anda andalkan adalah keadaan awal register ketika kode Anda mulai berjalan setelah BIOS.
Jadi bantulah diri Anda sendiri dan gunakan beberapa kode inisialisasi seperti berikut: https://stackoverflow.com/a/32509555/895245
Mendaftar suka %ds
dan %es
memiliki efek samping yang penting, jadi Anda harus membidiknya meskipun Anda tidak menggunakannya secara eksplisit.
Perhatikan bahwa beberapa emulator lebih bagus daripada perangkat keras asli dan memberikan Anda kondisi awal yang bagus. Kemudian ketika Anda menjalankannya pada perangkat keras nyata, semuanya rusak.
GNU GRUB Multiboot
Sektor boot sederhana, tetapi tidak terlalu nyaman:
- Anda hanya dapat memiliki satu OS per disk
- kode muat harus sangat kecil dan muat dalam 512 byte. Ini dapat diatasi dengan panggilan BIOS 0x13 int .
- Anda harus melakukan banyak startup sendiri, seperti pindah ke mode terlindungi
Karena alasan itulah GNU GRUB membuat format file yang lebih nyaman yang disebut multiboot.
Contoh kerja minimal: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Saya juga menggunakannya pada repo contoh GitHub saya untuk dapat dengan mudah menjalankan semua contoh pada perangkat keras nyata tanpa membakar USB satu juta kali. Pada QEMU tampilannya seperti ini:
Jika Anda menyiapkan OS Anda sebagai file multiboot, GRUB kemudian dapat menemukannya di dalam sistem file biasa.
Inilah yang dilakukan sebagian besar distro, menempatkan gambar OS di bawah /boot
.
File multiboot pada dasarnya adalah file ELF dengan header khusus. Mereka ditentukan oleh GRUB di: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Anda dapat mengubah file multiboot menjadi disk yang dapat di-boot dengan grub-mkrescue
.
El Torito
Format yang dapat dibakar ke CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Dimungkinkan juga untuk menghasilkan gambar hibrida yang bekerja pada ISO atau USB. Ini dapat dilakukan dengan grub-mkrescue
( contoh ), dan juga dilakukan oleh kernel Linux saat make isoimage
menggunakan isohybrid
.
Sumber daya