10 min
Technology

TAIOS ARM64 İşletim Sistemi - Sıfırdan Geliştirme Faz 1 (Part 2)

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:

  1. EL3 → EL2 → EL1 inişi yaptık.
  2. Exception table, stack ve FPU ayarlandı.
  3. 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

Etiketler

#ARM64 #OperatingSystems #Assembly #AppleInternals #SystemsProgramming #TAIOS #ExceptionLevels #Kernel

Tags

ARM64 Operating Systems Assembly Apple Internals Systems Programming TAIOS Exception Levels Kernel

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