Faz 1 (Part 3) — C Dünyasına Geçiş: kernel_main, İlk I/O ve Temel Bellek İskeleti
Projenin Github Linki : TAIOS
Faz 1( ve part2)’de boot.S tarafını tamamladık; EL3→EL2→EL1 inişi, VBAR_EL1 ayarı, stack kurulumu ve C tarafına zıplama işini çözdük. Şimdi “çekirdeğin kalbi” diyebileceğimiz kernel_main
akışına geçiyoruz.
Bu fazın hedefi: C’ye geçiş yaptığımızı kanıtlamak, görünür I/O sağlamak ve sonraki fazların bel kemiği olacak temel bellek iskeletini ayağa kaldırmak.
Not: Ama burada bir karışıklık var, o da şu, kernel.c dosyası bir çok c dosyasından fonksiyonları çağırıyorlar, onlar ayrı blog yazısı olduğundan, şimdilik kısaca değineceğim.
Bu Fazda Ne Var?
- C giriş noktası:
kernel_main
akışı - İlk I/O: UART ile karakter/dize/hex/dec çıktı
- Boot ekranı: Renkli terminal kontrolü
- Temel bellek hattı:
- Physical bellek görünümü,
- sayfa ayırıcı,
- heap (özet)
- MMU hazırlığı: sonraki faza köprü (derinlik Faz 4’te)
Şimdi biz boot.S de şunları yaptık;
1. EL1’e iniş yaptık,
2. Stack ve .bss temizliğini sağladık
3. Linker yerleşimini oturttuk
Şimdi sıradaki hedefimiz;
- Seri konsolda düzenli boot logları
- Bellek/başlatma adımlarının “başarılı” mesajları
- Basit bir “heartbeat”
kernel_main — Orkestra Şefi
C tarafında ilk durak kernel_main
.
Mantık şu: “Önce konuşmayı sağlayalım, sonra ayakları yere basalım.”
Github’daki Step2 Tag’indeki kernel.c dosyasının kernel_main
fonksiyonuna bakarsanız, şunları göreceksiniz
// kernel_main akışı - C dünyasına geçiş
void kernel_main(void) {
uart_init(); // UART donanımını başlat
boot_screen_init(); // Terminal temizleme
boot_screen_show_logo(); // TAIOS logosu
memory_init(); // Bellek haritasını kur
boot_screen_show_progress("Memory", 1);
page_allocator_init(); // Sayfa ayırıcı
page_reserve(0x40000000, 0x47000000); // Kernel alanı
page_reserve(0x09000000, 0x09001000); // UART alanı
boot_screen_show_progress("Page Allocator", 2);
heap_init(); // Kernel heap
boot_screen_show_progress("Heap", 3);
mmu_init(); // MMU hazırlığı
boot_screen_show_progress("MMU", 4);
boot_screen_show_ready(); // Hazır mesajı
kernel_init(); // Kernel başlatma
kernel_main_loop(); // Ana döngü
}
Şimdi bunları sırayla izâh etmeye gayret edelim.
uart_init();
Bu fonksiyon, işletim sisteminin UART donanımını (Universal Asynchronous Receiver/Transmitter) başlatmasını sağlar.
UART, çekirdeğin dış dünya ile ilk iletişim kurduğu arabirimdir.
Bu fonksiyon tamamlandığında, uart_putc()
veya uart_puts()
ile karakter ve dize gönderebiliyoruz, ve terminalde “boot log” yazılarını yazabiliyoruz.
Yani terminale birşeyler basmak, console.log
gibi veya seri bağlantı üzerinden veri almak için kullanıyoruz. Detaylı bilgiler sonraki blog yazılarında anlatılacaktır. Şimdilik sadece console.log’un ya da printf in derindeki yeri gibi düşünebilirsiniz.
void boot_screen_init(void) {
uart_puts("\033[2J"); // ekranı temizle
uart_puts("\033[H"); // imleci başa al
uart_puts("\033[?25l"); // imleci gizle (daha temiz görünüm)
}
Komut | Açıklama | ANSI Kodu |
---|---|---|
\033[2J | Ekranı tamamen temizler | ESC [2J “clear screen” |
\033[H | İmleci (cursor) sol üst köşeye taşır | ESC [H “move cursor to home” |
\033[?25l | İmleci gizler | ESC [?25l “hide cursor” |
Eğer başka hangi kodlar var diye merak edeniniz varsa :D, şöyle linkini bırakayım. (https://en.wikipedia.org/wiki/ANSI_escape_code)
*Not: Bu kodlara çok dalmayın, bir süre sonra gerçekten sıkılabilirsiniz… ;-)*
Her neyse şimdi bir kaç hazır kodu gönderdik, terminali temizledik. Yani bildiğiniz ctrl+l yaptık :D şimdi geldik, eğlenceli kısıma;
boot_screen_show_logo();
Bu kısmın kodlarına bakarsınız, TAIOS logosunu terminale renkli basmak için.
Küçük bir itiraf, bende bu logoyu AI’a yazdırdım. Arada işe yarıyor kereta :D
Kodu satır satır açıklamayacağım, zaten her satırda Türkçe açıklama var. Merak ederseniz oradan kurcalarsınız.
Şimdi geldik Bellek işlerine. Aaaa burası sıkıntı. Dikkatli olmak gerek.
memory_init(); // Bellek haritasını kur
boot_screen_show_progress("Memory", 1);
Ne yapar memory.c de anlatacağız ama kısaca;
- Fiziksel bellek haritasını (hangi adres hangi işe ayrılmış) kurar,
- Tüm bellek yapısını sıfırdan deterministik hale getirir,
- Basit (memory detection) yaparak toplam RAM miktarını belirler,
- Ve ilerleyen fazlarda page allocator veya heap yöneticisine veri sağlar.
boot_screen_show_progress("Memory", 1);
Bu fonksiyonun bir espirisi yok, bildiğiniz log metodunu buna bağladım. Her defasında uart_puts yazmamak için…
page_allocator_init();
Şimdi memory init yaptık ya, RAM üzerindeki 4 KB’lık fiziksel sayfaları yönetebilmesi için temel veri yapısını (bitmap’i) kurmamız lazım.
Bu ne demek :D, nasıl anlatsam şöyle;
- Toplam RAM’i sayfalara böler, 1 GB RAM, 262 144 adet 4 KB sayfa.
Neden 4KB diye sorarsanız, sormayın. Altın Oran :D. Şaka değil, yılların mühendislik know-how’u ile 4KB en makul olarak tespit edilmiş. Beni de darlamayın :D
- Bu sayfaları takip edecek bir bitmap oluşturur (yaklaşık 32 KB).
- Bitmap’i RAM’de uygun bir yere yerleştirir
- Tüm sayfaları boş olarak işaretler (yani kullanılabilir).
- Kernel, heap ve cihaz (UART) alanlarını rezerve eder, böylece bu bölgeler “tahsis edilmez.”
- Toplam sayfa ve bellek miktarını log olarak yazdırır.
Aslında şimdi dökümanı yazarken farkettim, memory init yaparken, page_allacator_init de yapabilirdim. Her neyse… Nasılsa eğitim amaçlı bu seri…
page_reserve(0x40000000, 0x47000000); // Kernel alanı (112MB)
page_reserve(0x09000000, 0x09001000); // UART alanı (4KB)
Şimdi geldik; çal zurnacı diline üşenme hoppaaa dediğimiz yere…
Şimdi agalar, burası biraz karışık. Şöyle ki, aslında biz burada yazılımsal manifesto veriyoruz. Biz bu alanlara yazmayacağız diyoruz, ama donanımsal koruma henüz yok.
Yani bizim boot.S loader’ımız RAM’e yazıldı ya, hah işte onlar bu alanlara yazıldı. Kernel + heap bölgesini rezerve ettik. Ama bu sadece yazılımsal bir koruma. Donanımsal MMU koruması henüz devreye girmedi, ama mühim değil. Biz sadece eğitim amaçlı yazıyoruz. Kalkıp da kendi kodumuzu yazdığımız adrese bir şey basacak değiliz. O yüzden ne olur ne olmaz, koruyoruz.
Yani;
Benim RAM haritamın şu kısmında çekirdeğim var,
şu kısmı cihaz belleği,
bu alanlara bir daha dokunma.
MMU devreye girdiğinde de buralara duvar öreceğim.
Şimdilik dutluk ama ilerisi için rezervasyon yaptık.
heap_init();
Şimdi bu şu demek, bu kernel yaşayan bir program, e gardaş benim de ram’e ihtiyacım var. Bu ihtiyacımı kimden talep edeceğim. İşte kendime tahsis ediyorum. Eğitim için sabit 64MB ayırdım ama gerçek OS larda bu dinamik olur. İleride user_space kısımlarına geçtiğimiz zaman göreceksiniz, o zaman kullanıcılara user heap vs tanımlayacağız.
Yani kısaca heap_init() Patronun kendine ayırdığı RAM.
Yanlış anlaşılmasın, QEMU’da 1GB sanal ram verirseniz, yine gerisi duruyor. Bu sadece patronun kendine ayırdığı…
mmu_init();
Yukarıda bahsettiğim, her bellek erişiminde izin kontrolü yapan, korumalı modda çalışan gerçek bir işletim sistemi çekirdeği haline getirdik. Yine ileride detaylı detaylı anlatacağız, endişeye mahal yok.
kernel_main_loop();
İşte bu kadar kolay. İşlem tamamlandı. Şimdi kernel_main_loop fonksiyonuna bakarsanız, sonsuz döngü görebilirsiniz. Ama orada bir interrup var, o da
asm volatile("wfe"); // Wait For Event - ARM64 talimatı
wfe = Wait For Event ARM64 talimatı.
CPU’ya “bir olay (interrupt, wake signal) gelene kadar bekle” der.
Böylece CPU:
• Boşta döngüde enerji harcamaz,
• Bekleme moduna geçer (low-power idle),
• Yeni bir “event” (örneğin interrupt, timer tick, IPI) gelince uyanır.
Son Söz;
Başında da dediğim gibi, bu yol çok uzun bir yol, o yüzden yavaş yavaş sindire sindire gideceğiz.Sıkıcı olabilir ama hala buradaysanız ve merak ediyorsanız ne diyeyim, sonraki blog yazısında buluşmak üzere ;-)
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