64 bit mod için BIOS önyükleyicisi yazmak
(thasso.xyz)- 512 baytlık önyükleme sektöründen başlayıp x86_64 CPU’yu 16 bit real mode’dan 64 bit long mode’a çıkaran en küçük önyükleyici akışı adım adım kuruluyor
- Önce BIOS kesmeleriyle çıktının çalıştığı doğrulanıyor, ardından
nasm,ld,objcopy, QEMU birleştirilerek önyükleme imajının gerçekten çalışıp çalışmadığı sınanıyor - Önyükleme sektörünün kapasite sınırı nedeniyle yapı stage 1/stage 2 olarak ayrılıyor ve protected mode’a girmeden önce BIOS
int 0x13ile sonraki kod diskten okunuyor - 32 bit protected mode’dan itibaren BIOS rutinleri kullanılamadığı için GDT, flat segmentation, VGA tamponuna doğrudan yazma gibi donanım başlatmaları gerekiyor
- 64 bit long mode’a geçiş için sayfa tabloları, PAE,
EFER.LME,cr0.PGve 64 bit için GDT’nin hepsinin doğru ayarlanması gerekiyor; sonrasında freestanding C kodu bir çekirdek gibi çağrılabiliyor
BIOS’tan başlayan 16 bit ortam
- x86 CPU, reset sonrasında real mode durumundadır ve varsayılan işlenen boyutu 16 bittir
- real mode, segmentation ile 20 bitlik adres alanı oluşturur ve en fazla 1MB belleği kullanabilir
- BIOS’un ilk çalıştırdığı kod diskin önyükleme sektöründe bulunur
- BIOS, ilk sektörü
0xaa55magic number ile biten diski arar - Bu sektörü
0x7c00bellek adresine yükler
- BIOS, ilk sektörü
- BIOS’un verdiği alan yalnızca 512 bayt olduğu için bu kod, önyükleyicinin geri kalanını yükleyen bootstrap görevine odaklanır
- BIOS rutinleri yalnızca real mode’da kalındığı sürece kullanılabilir
Gerekenler ve derleme ortamı
- Takip etmek için Intel 64 and IA-32 Architectures Software Developer’s Manual, assembler ve QEMU gerekiyor
- Örnek assembler olarak nasm kullanılıyor ve x86 assembly ile nasm sözdizimini bildiğiniz varsayılıyor
- x86_64 CPU olmasa bile QEMU’da x86 CPU emüle edilerek çalıştırılabilir
- 32 bit moda kadarki temel bilgiler için Writing a Simple Operating System — from Scratch kaynağına başvuruluyor
Önyükleme sektörü oluşturup çalıştığını doğrulamak
- İlk önyükleme sektörü, BIOS rutinleriyle
"Hello, world!"yazdırdıktan sonrahltve bir döngüyle duruyor - Metin yazdırmak için BIOS video servis çağrısı
int 0x10veah = 0x0ekullanılıyor Makefile, nasm ile object dosyası oluşturuyor, linker script ile bağlıyor veobjcopy -O binaryile ham önyükleme imajı üretiyormake boot, imajı QEMU ile çalıştırıyorqemu-system-x86_64 -no-reboot -drive file=$<,format=raw,index=0,media=disk
- linker script, önyükleme sektörünün
0x7c00tabanına yerleştirilmesini sağlayacak şekilde ayarlanıyorMEMORYiçindekiboot_sectororigin değeri0x7c00, uzunluğu512.bootsignbölümü0x7c00 + 510konumuna0x55,0xaaekliyor
- Önyükleme sektörü assembly’si içinde offset ve magic number doğrudan da işlenebilir, ancak burada bu görevi linker script üstleniyor
stage 1’in stage 2’yi diskten okuması
- stage 1, BIOS’un yüklediği önyükleme sektörü kodudur ve hedefi stage 2’yi belleğe almaktır
- stage 2, 16 bit real mode’dan 32 bit protected mode’a geçiş kodunu içerir
- protected mode’a girdikten sonra BIOS rutinleri kullanılamadığı için disk sektörü okuma işlemi geçişten önce tamamlanmalıdır
- Disk erişimi için BIOS disk servisleri
int 0x13kullanılırah = 0x42, BIOS extended read işlevidirdl = 0x80, sürücü numarasıdır- disk address packet içinde okunacak sektör sayısı, hedef adres ve başlangıç sektörü bulunur
- Örnek,
READ_SECTORS_NUM equ 64ile 64 sektör okur- Önyükleme sektörü sector 0 olduğundan stage 2, sector 1’den itibaren okunur
- Hedef adres
BOOT_LOAD_ADDR + SECTOR_SIZE, yani0x7c00 + 512
- Kod içinde, istenenden daha az sektör okunması durumunu kabul eden geçici bir işlem hâlâ bulunuyor
- stage 2 başlangıçta real mode için
print_stringfonksiyonunu kopyalayıp"Hello from stage 2"yazdırarak stage 1’den sıçramanın düzgün devam ettiğini doğruluyor
32 bit protected mode’a geçmek
- protected mode’a girmek için önce Global Descriptor Table(GDT) tanımlanmalıdır
- protected mode’da segmentation, temelde bellek koruması için kullanılır
- 64 bit long mode’da paging gereklidir, ancak ondan önceki protected mode aşamasına geçmek için önce segmentation ayarı gerekir
- Örnek GDT, Intel kılavuzundaki flat modeli izler
- bir code segment ve bir data segment içerir
- bu iki segment tüm linear address space’e eşlenir
- long mode’a geçiş için ara aşama olduğundan en basit model kullanılır
- GDT, bellekte bulunan bitişik bir yapı dizisidir
- başlangıçta geçersiz çevirileri yakalamak için null descriptor bulunur
- ardından code segment descriptor ve data segment descriptor gelir
- Geçiş şu sırayla yapılır
cliile interrupt’lar devre dışı bırakılırlgdt [gdt32_pseudo_descriptor]ile GDT adresi ve uzunluğu GDTR’ye yüklenircr0.PE, yanicr0içindeki bit 0 ayarlanarak protected mode açılır- instruction pipeline’ı boşaltmak ve
csdeğerini yeni code segment ile güncellemek için far jump yapılır
- protected mode’a girdikten sonra eski segment değerleri artık anlam taşımadığından
ds,ss,es,fs,gsyeni data segment selector değeriyle ayarlanır - Tüm ayarlardan sonra interrupt’ları yeniden açmak için ek işlemler gerekir
BIOS olmadan ekrana yazmak
- protected mode’da artık BIOS rutinleri çağrılamaz
- Metin yazdırma yöntemi, VGA buffer’a doğrudan yazmaya dönüşür
print_string32,0xb8000adresine karakter ve renk baytlarını yazar- renk değeri
0xf - her karakter hücresi 2 bayt kullanır
- renk değeri
- Bu çıktı fonksiyonu çok basittir; mesaj her zaman ekranın sol üst köşesinde görünür
long mode’a giriş için sayfa tabloları
- Intel belgelerindeki IA-32e mode, AMD64 kılavuzundaki long mode’a karşılık gelir
- long mode’a geçmek için CPU’nun protected mode’da olması ve paging’in de etkinleştirilmiş olması gerekir
- paging kavramı için Introduction to Paging ve OSTEP kaynaklarına başvuruluyor
- PAE etkin long mode’da 4 seviyeli sayfa tabloları kullanılır
build_page_table, belirtilen adreste 4 seviyeli sayfa tablolarını oluşturur- page boyutu
0x1000 - her page table boyutu
0x1000 - entry sayısı
512 - önce dört tablo 0 ile başlatılarak tüm entry’ler not present durumuna getirilir
- PML4 → PDP → PD → page table zincirindeki ilk entry’ler birbirine bağlanır
- en alt page table katmanında 512 entry ayarlanır
- page boyutu
64 bit için GDT ve long mode’a geçiş sırası
- paging, virtual address space ve yetki yönetimini üstlense de long mode’da da GDT gerekir
- 64 bit için GDT de flat model’i izler ve protected mode için kullanılan GDT ile neredeyse aynıdır
- Fark, long mode ile ilgili bit ayarlarındadır
- code segment için 64-bit code segment flag’i ayarlanır
- bu flag ayarlandığında default operation size bit’i 0 olmalıdır
- long mode’a geçiş şu akışla ilerler
0x1000adresinde 4 seviyeli page table oluşturulurcr3içine PML4 table adresi yazılırcr4içindeki bit 5 ayarlanarak PAE etkinleştirilir0xc0000080MSR okunur veEFER.LME, yani bit 8 ayarlanırcr0içindeki bit 31 olanPGflag’i ayarlanarak paging etkinleştirilir- 64 bit için GDT
lgdtile yüklenir - 64 bit code segment’e far jump yapılarak 64 bit moda girilir
- paging açıldıktan hemen sonra durum IA-32e compatibility mode’dur; 64 bit segment flag’i ayarlanmış GDT’ye sıçranınca 64 bit moda geçilir
- Başarının doğrulama mesajı, VGA buffer üzerinden ekranın sol üst köşesinde gösterilir
freestanding C kodu çağırmak
- 64 bit long mode’a kadar gelindikten sonra freestanding C kodu çağrılabilir
kernel.c, VGA buffer0xb8000alanını temizleyip"Hello from C"yazdırır- Assembly tarafındaki
start_long_mode, 64 bit için metin yazdırdıktan sonraextern _start_kernelbildirir ve_start_kernelfonksiyonunu çağırır - linker script bellek alanını üç bölüme ayırır
boot_sector:0x7c00, length512stage2:0x7e00, length512kernel:0x8000, length0x10000
.text,.data,.rodata,.bssbölümleri kernel alanına yerleştirilirMakefile, hem assembly hem de C derlenecek şekilde değiştirilir- C compiler
gcc’dir - başlıca CFLAGS değerleri
-std=c99 -ffreestanding -m64 -mno-red-zone -fno-builtin -nostdinc -Wall -Wextra
- C compiler
- Tüm örnek kod indirme bağlantısı üzerinden sunuluyor
1 yorum
Hacker News yorumları
Koruma modundan geçmeden, çok daha az kodla doğrudan long mode'a geçmek mümkün: https://wiki.osdev.org/Entering_Long_Mode_Directly
Bu yöntemle yapılmış, küçük bir 64 bit çekirdek için bir bootloader vardı; diskteki çekirdeği okuyup VESA modunu ayarlayan kod da dahil olmasına rağmen boot sector'a rahatça sığıyordu. 2. aşama yükleyiciye de gerek yoktu
Gerçek anlamda modern bir sabit disk kullanacaksan MBR yerine GPT'ye bakmak daha iyi olur. Bölüm tablosu sınırlarına takılmadan 2 TB'dan büyük disklerle de çalışabilirsin. UEFI bu sorunları ortadan kaldırır ve ciddi bir zorluk olmadan düzgün bir disk yerleşimi kullanmanı sağlar
64 bit moda geçmek için koruma modu gerekmez. Ama BIOS kullanmamak daha iyidir. Dağınıktır ve işleri sadece daha zahmetli hale getirir
UEFI'yi EDK2 ya da GnuEFI ile kullanmak daha iyidir; ikisini de uygulamak oldukça kolay ve rahattır. UEFI'nin ilk kavramlarına alışmak biraz zaman alır ama GitHub'daki örnek projelere bakınca yapıyı kolayca kavrayabilirsin. EDK'deki .dec ve .inf dosyaları biraz can sıkıcı, GnuEFI'de ise işlev bulmak için başlık dosyalarını okumak gerekiyor; ama bunların ikisi de belirtimi belirsiz BIOS arayüzlerinden çok daha iyidir. Gerçek donanımda int 0x10, int 0x15 vb. şeylerin düzgün şekilde var olduğunu varsayamazsın bile
UEFI sistemlerde kararlı bir asgari temeli varsayabilirsin ve donanım ya da platform özelliklerini de makul biçimde listeleyebilirsin. Ayrıca UEFI platformu zaten büyük ölçüde ayarlamış olduğundan, OS yükleyici bileşeninde çok fazla ilklendirme yapmadan doğrudan OS, sürücüler ve çekirdek tasarımına uygun bileşenleri yükleyebilirsin. Bellek haritasını alır, EFI dosya sistemine erişir ve gerekenleri oradan okursun
80286'da 16 bitlik bir register olan Machine Status Word (MSW) vardı, 80386 bunu 32 bitlik CR0 register'ına genişletti. Daha sonra 64 bit long mode, EFER MSR'ını ekledi ve CR0'ı 64 bite genişletti, ama bugün bile CR0'da yalnızca 11 bit kullanılıyor ve EFER'de de yalnızca 8 etkin bit var
Intel/AMD'nin mevcut register'lardaki boş bitleri kullanmak yerine neden iki kez yeni bir tercih yaptığını merak ediyorum: https://wiki.osdev.org/CPU_Registers_x86-64#CR0
CR1 ile CR5~CR7 hâlâ ayrılmış durumdayken CR8'in ortaya çıkmasının nedeni de benzer görünüyor
Bu yazıdaki gereksiz yere en karmaşık görünen şey Makefile ve linker script. NASM düz ikili çıktı destekliyor, ama görünüşe göre bunu kullanmak fazla “hacky” bulunmuş
Eskiden OS'imde bununla dalga geçmek için make.sh diye bir dosya vardı. Şimdi “dosya biçimi” gibi havalı şeyler kullanıyorum; bu yüzden -fbin ve --oformat=binary artık geçici birer hatıra. Veri C dosyaları ile kod C dosyalarını ayırıp bunları ikili olarak dökerek oradan bir canavar toplamaya uzun süre çalıştım, ama bağlama ve yükleme çok zorlaşıyor. Sadece ELF ya da PE kullanmak daha iyi; zaten görünüşe göre bu biçimler tam da bunun için var
Harika ve iyi bir egzersiz gibi görünüyor, ama işe yarayıp yaramadığından emin değilim. Çalışırken ayarları kontrol etmeyi ya da değiştirmeyi sağlayan, Fisher-Price oyuncağı gibi bir UX bile var mı merak ediyorum
Boot süreci, minimize edilmiş moddan tek kullanıcılı moda, oradan da recovery mode'da havalanan bir duruma giden bir süreçtir
Xenix/DOS döneminden beri, Microsoft ürünleriyle birlikte Unix de kullanıyorum; sanırım yaklaşık 40 yıl oldu
Linux'u da İsveç versiyonundan, yani ilk sürümünden beri kullandım; GNU 0.1'i de denedim
Xenix'e Unix dediğim için özür dilerim. Xenix, çıktığı andan çöküşüne kadar zaten geçmişte kalmak isteyen, taklitçi karakterde karmakarışık bir üründü
Microsoft bana, ürün çıkarmaktan çok müşterilerinin üstüne kedi kumu kutusunu boşaltan bir şirket gibi geliyor. Buna son örnekler Copilot ve 22H2
F1 arabasının, kurşun kalemin ve cep hesap makinesinin nasıl geliştiğine bakınca, kullanılabilir bir ideale ne kadar yaklaştığımızı merak ediyorum
Neden bootloader'ın statik çekirdek modunda olmadığını da merak ediyorum. Eskiden öyleydi; yakın zamanda biri yeniden öyle olması gerektiğini önerdi, ben de katıldım
https://wiki.osdev.org/A20_Line
CPU’yu doğru moda geçirmek için gereken adımların tamamının gereksiz görünmesi şaşırtıcı. Bunların çoğu geriye dönük uyumluluk yüzünden gerekli prosedürlermiş gibi duruyor
Intel’in en baştan doğru modda başlatan bir bayrak ya da komut sağlayıp sağlayamayacağını, ya da geriye dönük uyumluluğu kaldırıp kaldıramayacağını merak ediyorum
ARM64’te de kısmen benzer sorunlar olduğunu hatırlıyor gibiyim. Başından beri 64 bit olarak tasarlanmış, geriye dönük uyumluluğa ihtiyaç duymayan ve varsayılan olarak istenen durumda açılan bir CPU olup olmadığını merak ediyorum. Itanium’un hedefi ya da tasarımı böyle bir şey miydi diye düşündüm
Itanium, yani Itanic için de durum aynıydı
Geriye dönük uyumluluk; ARM, MIPS, RISC-V vb. yerine x86’ı seçmenin asıl nedenidir. Ne yazık ki Intel ve AMD’deki bazı insanlar bunu pek anlamıyor gibi görünüyor
Harika bir proje. Burada UEFI taraftarları neden ille de yeni bir önyükleyici yöntemi yapıldığını sorguluyorsa, insanların böyle şeyleri neden yaptığını kaçırıyor gibiler
Yazarın sonunda yazdığı “buraya kadar geldiysen harikasın” sözü tam yerinde; gerçekten harika
UEFI çıkalı ne kadar oldu diye düşünüyorum. Keşke long mode ile birlikte BIOS da kaldırılmış olsaydı
Kaldırılma sürecinde demek silinmiş demek değil; kaldırılması hedeflendiği için artık güncellenmediği veya geliştirilmediği anlamına geliyor
Bu önyükleme sürecinin EFI/UEFI üzerinde de çalışıp çalışmadığını merak ediyorum. Çalışıyorsa, UEFI denetleyicisinin real mode, protected mode ve long mode geçişlerini emüle edip etmediğini, yoksa bunların gerçek donanım üzerinde mi yapıldığını da merak ediyorum
Hobi amaçlı bir OS için UEFI önyükleyicisi oluşturma sürecini burada yazmıştım: https://0xc0ffee.netlify.app/osdev/05-bootloader-p1.html
Bunun ARM tarafında daha basit olup olmadığını merak ediyorum