TAIOS ARM64 İşletim Sistemi - Sıfırdan Geliştirme Faz 2
Bir önceki yazıda Faz 1 boot.S dosyasına giriş yapmıştık ve maalesef BOOT ENTRY kısmına anca gelebildik. Aslında hem bu kadar fazla teknik olmaması açısından, hem de sıkıcı teknik bilgilerle boğulmamak için biraz daha kısa ve akıcı şekilde devam edeceğim inşallah ;-)
Efendim tabi bir işletim sistemi geliştirmek osdev’de dendiği gibi, tek başına yapılabilecek bir şey değil, o yüzden biz hedefimizi aslında, Apple Internals’ı daha iyi anlamak için FreeBSD kaynak kodundan yola çıkarak bulmaya çalışacağız. O yüzden şimdi BOOT ENTRY kısmından hızlıca devam edelim.
Boot Entry - İşletim Sisteminin İlk Nefesi
Şimdi sıra geldi asıl giriş noktası olan _start
etiketine. İşletim sisteminin kalbi burada atmaya başlıyor.
// --------------------- BOOT ENTRY ------------------------
_start:
// Interrupt'ları kapat (DAIF = Debug, SError, IRQ, FIQ)
msr daifset, #0xf // DAIF registerındaki tüm mask bitlerini set et
// Mevcut Exception Level'ı oku
mrs x0, CurrentEL // CurrentEL register'ını x0'a al
and x0, x0, #0xC // Sadece [3:2] bitlerini maskele (EL seviyesi)
cmp x0, #0x8 // EL2 (0b1000) mi?
b.eq init_el2 // Evetse init_el2'ye git
cmp x0, #0x4 // EL1 (0b0100) mi?
b.eq init_el1 // Evetse init_el1'e git
b init_el3 // Değilse EL3 kabul et, init_el3'e git
Buradaki _start
aslında programın ilk çalışacağı nokta. Linker’a “sen benim kodumu buradan başlat” diyoruz. ARM64 mimarisinde her şey bu etiketten sonra şekilleniyor.
Interrupt’ları Kapatmak
// Interrupt'ları kapat (DAIF = Debug, SError, IRQ, FIQ)
msr daifset, #0xf // DAIF registerındaki tüm mask bitlerini set et
İlk iş olarak interrupt’ları kapatıyoruz. Çünkü henüz hiçbir şey hazır değil. Ne memory mapping var, ne stack var, ne de driver’lar.
Gerçek dünya asenkron problemleri ile uğraşmamak için mesela bir IRQ gelse (keyboarda basıldı gibi), o kod nereye gidecek? O yüzden en temelden başlayıp: “Beni rahatsız etme kardeşim” diyerek bütün kesmeleri kapatıyoruz.
Beni hor görme kardeşim
sen altınsın, ben tunç muyum?
Aynı vardan varolmuşuz,
sen gümüşsün, ben sac mıyım?
diyerek yolumuza devam ediyoruz.
Exception Level Tespiti
// Mevcut Exception Level'ı oku
mrs x0, CurrentEL // CurrentEL register'ını x0'a al
Burada mrs
(Move from System Register) talimatı ile şu an hangi Exception Level’de (EL0, EL1, EL2, EL3) olduğumuzu öğreniyoruz. ARM mimarisinde bu seviyeler çok kritik:
- EL0: User mode (app’ler burada çalışır)
- EL1: Kernel mode (işletim sistemi çekirdeği)
- EL2: Hypervisor (sanallaştırma katmanı)
- EL3: Secure monitor (TrustZone, firmware işleri)
Bizim hedefimiz aslında EL1’de kernel’i başlatmak, ama donanım ve firmware’e göre sistem başka bir seviyede açılabilir.
and x0, x0, #0xC // Sadece [3:2] bitlerini maskele (EL seviyesi)
Burada ufak bir bit maskesi yapıyoruz. CurrentEL
register’ında daha fazla bilgi var ama bizi ilgilendiren sadece [3:2] bitleri. Çünkü exception level 2 bitten okunuyor. Yani 00
, 01
, 10
, 11
gibi değerlerle hangi seviyede olduğumuzu anlıyoruz.
cmp x0, #0x8 // EL2 (0b1000) mi?
b.eq init_el2 // Evetse init_el2'ye git
cmp x0, #0x4 // EL1 (0b0100) mi?
b.eq init_el1 // Evetse init_el1'e git
b init_el3 // Değilse EL3 kabul et, init_el3'e git
Eğer sistem EL2’de başlamışsa (mesela QEMU bazen hypervisor olarak açabilir), o zaman init_el2
fonksiyonuna dallanıyoruz. Burada EL2’ye özel bazı ayarlar yapılacak.
Eğer doğrudan EL1’deysek (bazı donanımlar böyle açılıyor), direk kernel init koduna geçiyoruz.
Son ihtimal: Eğer ikisi de değilse, demek ki EL3’te açılmışız (genelde gerçek donanımda firmware böyle bırakır). O zaman işimizi garantiye alıyoruz, init_el3
’e gidiyoruz.
Özetle bu kısım aslında şunu yapıyor:
“Ben hangi seviyede başladım? EL3 mü, EL2 mi, yoksa direk EL1 mi? Ona göre doğru init fonksiyonuna dallanayım.”
Yani böyle havalı durduğuna bakmayın bildiğin if/else :D
Tabi ufak bir if/else daha kernel’in stack’ini bile kurmadan çalışma seviyesini netleştiriyoruz. Çünkü bu seviyeye göre MMU ayarları, güvenlik yetkileri ve yapılacak işler değişiyor.
Exception Level Geçişleri
Şimdi geldik katmanlara
EL3 → EL2 Geçişi
// --------------------- EL3 → EL2 -------------------------
init_el3:
// EL3'ten EL2'ye geçiş
mov x0, #0x80000000 // SCR_EL3: RW=1 → alt seviye AArch64 çalışacak
msr scr_el3, x0
Burası en üst kat ama patron değil. SCR_EL3
register’ında RW=1
yaparak diyoruz ki:
“Ben (EL2) 32-bit değil, 64-bit çalışacam. Ne o öyle mö gibi 32 bit.”
ARM64 netlik ister., “benim alt katım da AArch64 olacak” demeden aşağıya inemiyoruz.
mov x0, #0x3c9 // SPSR_EL3: Hedef = EL2h, interrupt'lar maskeli
msr spsr_el3, x0
Burada SPSR_EL3 yani Saved Program Status Register ayarlanıyor. Biz eret
dediğimizde, CPU “nereye gideyim, hangi modda olayım, interrupt’lar açık mı kapalı mı” diye buraya bakıyor.
0x3c9
demek:
- Hedef EL2h (Hypervisor mode, AArch64)
- Bütün interrupt’lar kapalı (çünkü daha hazır değiliz, sürpriz sevmiyoruz)
adr x0, init_el2 // init_el2 adresini al
msr elr_el3, x0 // ELR_EL3'e yaz (eret sonrası PC buraya gider)
eret // Exception return → EL2'ye atla
Burada olayı bitiriyoruz. eret
dediğimiz anda CPU “tamamdır, EL2’de şu adresten devam edeyim” diyor ve EL2’ye fışşşt ışınlanıyoruz.
EL2 → EL1 Geçişi
// --------------------- EL2 → EL1 -------------------------
init_el2:
// EL2'den EL1'e geçiş
mov x0, #0x80000000 // HCR_EL2: RW=1 → alt seviye AArch64 çalışacak
msr hcr_el2, x0
EL2’ye düştük, burası hypervisor katı. Burada da HCR_EL2
register’ına aynı ayarı yapıyoruz:
“Benim alt katım (EL1) de 64-bit çalışsın.”
Yeter da, 64 bit işte, ne sıkıcı dimi :D
mov x0, #0x3c5 // SPSR_EL2: Hedef = EL1h, interrupt'lar maskeli
msr spsr_el2, x0
adr x0, init_el1 // init_el1 adresini al
msr elr_el2, x0 // ELR_EL2'ye yaz
eret // Exception return → EL1'e atla
Tıpkı EL3’te yaptığımız gibi, burada da SPSR_EL2 ayarlanıyor. Yine interrupt’lar kapalı. Sonrasında eret
ile nihayet kernel moduna (EL1) iniş yapıyoruz.
EL1 Initialization - Kernel’in Doğuşu
// --------------------- EL1 INIT --------------------------
init_el1:
// Artık EL1'deyiz (kernel modu)
Şükür, gelebildik. Buradan sonrası çekirdek.
Exception Vector Table Ayarı
// Exception vector tablosunu ayarla
adr x0, exception_vector_table // Tablonun adresini al
msr vbar_el1, x0 // VBAR_EL1'e yaz
Burada CPU’ya diyoruz ki:
“Bir interrupt ya da exception gelirse buradaki tabloya bak.”
Yani kendi güvenlik sınıfımızı oluşturuyoruz gibi düşünün.
Stack Pointer Kurulumu
// Stack pointer'ı ayarla
ldr x0, =_stack_top // Linker script'ten stack top sembolünü al
mov sp, x0 // sp register'ına yaz
Stack olmadan kernel olmaz! Burada sp
(stack pointer) register’ını _stack_top
’a koyuyoruz. Bundan sonra fonksiyon çağrıları, local değişkenler falan artık düzgün çalışabilecek.
FPU ve SIMD Aktivasyonu
// Floating Point / SIMD kullanımını aç
mrs x0, cpacr_el1 // CPACR_EL1 register'ını oku
orr x0, x0, #(3 << 20)// FPEN bitlerini 11 yap (FP/SIMD serbest)
msr cpacr_el1, x0 // Geri yaz
isb // Instruction barrier (etkin olsun diye)
Burada da FPU ve SIMD (NEON) ünitelerini açıyoruz. Çünkü kernel içinde bazen floating point işler gerekebilir. Normalde güvenlik ve performans için bunlar kapalı gelir. GPU olmazsa SIMD var, diyerek yolumuza devam ediyoruz.
C Kernel’e Geçiş
// C tarafındaki kernel girişine zıpla
bl kernel_main // kernel_main() fonksiyonunu çağır
Ve işte en kritik nokta: Artık Assembly’den çıkıp C dünyasına geçiyoruz.
Burada kernel_main()
çağrılıyor. İşletim sisteminin kalanı artık C ile yazılacak. Assembly sadece kapıyı açtı.
Hang Loop - Son Durak
// --------------------- HANG LOOP -------------------------
hang:
wfe // Wait For Event (düşük güç bekleme)
b hang // Sonsuz döngü
Son olarak, hang
etiketi: Kernel bir yerde patlarsa veya daha init bitmeden bir şey ters giderse, sistem buraya gelir. wfe
(Wait For Event) ile düşük güçte bekler ve sonsuz döngüde kalır. Yani “ben bittim, burada takılıyorum” modu.
Özet
Özetle:
- EL3 → EL2 → EL1 inişi yaptık.
- Exception table, stack ve FPU ayarlandı.
- Kernel’i C tarafında başlatıp topu
kernel_main()
'e attık.
Böylece işletim sistemimizin ilk nefesini aldırmış olduk.
Sonraki bölümde: kernel_main()
fonksiyonunda neler olacağını, memory management’ın nasıl kurulacağını ve ilk device driver’ların nasıl yükleneceğini göreceğiz.
Kaynaklar
- ARM Architecture Reference Manual
- TAIOS GitHub Repository
- FreeBSD Source Code
- Apple Darwin/XNU Open Source
Etiketler
#ARM64
#OperatingSystems
#Assembly
#AppleInternals
#SystemsProgramming
#TAIOS
#ExceptionLevels
#Kernel
Tags
Uğur Toprakdeviren
Cryptographer, security researcher, and systems engineer with over two decades of experience building secure systems. Currently focused on Apple internals, decentralized messaging protocols, ARM64 architectures, and the philosophical implications of digital privacy.
Learn more about me