Hello World selamlaması
(thecoder08.github.io)-
Modern
Hello Worldprogramının ardında gizlenen soyutlama dünyasına bir keşif- Bu yazı, C ile yazılmış bir
Hello Worldprogramını ele alıyor. C, yorumlayıcı/derleyici/JIT dünyasında program gerçekten çalışmadan önce dilin ne yaptığını düşünmeyi gerektirmeyen üst düzey diller arasında en alt katmana en yakın olanlardan biri. - Aslında kodlama geçmişi olan herkesin anlayabileceği şekilde yazılmak istenmiş, ancak en azından C ya da assembly bilgisine sahip olmak faydalı olacaktır.
- Bu yazı, C ile yazılmış bir
-
Hello Worldprogramına başlangıç- Herkesin
Hello Worldprogramına aşina olduğu varsayılabilir. Python'da muhtemelen yazdığınız ilk programprint('Hello World!')gibi bir şeydi. - Bu yazıda C programlama diliyle yazılmış
Hello Worlde bakılacak. C'de programı çalıştırmak için bir yorumlayıcı çağrılamaz. Önce derleyicinin çalıştırılması ve programın, bilgisayar işlemcisinin doğrudan çalıştırabileceği makine koduna dönüştürülmesi gerekir.
- Herkesin
-
Programımızın analizi
- Derlenmiş program dosyası incelendiğinde bunun bir ELF yürütülebilir dosyası olduğu ve x86-64 komut kümesi mimarisi için üretildiği görülebilir.
- ELF yürütülebilir dosyası, Linux'ta Windows'un
.exedosyasına karşılık gelir. - x86-64, 1981'de IBM PC'nin tanıtılmasından bu yana PC'lerde kullanılan CPU mimarisidir.
- Bu dosya, CPU'nun anlayabildiği tek dil olan makine kodunu içerir.
-
Assembly kodunun analizi
- Programın başlangıç adresi olan entry point bulunup assembly kodu inceleniyor.
- Assembly dili, makine kodunun insan tarafından okunabilir biçimde ifade edilmiş halidir.
- Derleyici (daha doğrusu linker) tarafından otomatik olarak eklenen ilklendirme kodu görülüyor ve
__libc_start_mainfonksiyonunun çağrıldığı fark ediliyor. - Ancak bu kod programımızda tanımlı değil; başka bir yerde bulunuyor.
-
C standart kütüphanesi
__libc_start_mainfonksiyonu, sistemin standart C kütüphanesi olanlibc.so.6içinde tanımlıdır.- C standart kütüphanesi, bilgisayardaki neredeyse tüm programların kullandığı rutinler ve fonksiyonlar koleksiyonudur.
- C kütüphanesi ilklendirme işlerini yapar ve bizim yazdığımız
main()fonksiyonunu çağırır.main()döndüğünde de verdiğimiz çıkış koduyla programı sonlandırır.
-
main()fonksiyonunun analizimain()fonksiyonunda stack frame kurulur,Hello Worlddizgesinin adresi fonksiyon çağrısının argümanı olarak ayarlanır ve ardındanputs()fonksiyonu çağrılır.puts(), aslındaprintf()çağrısının derleyici tarafından yapılan optimizasyonla değiştirilmiş halidir. Çünküprintf()karmaşıktır,puts()ise yalnızca biçimlendirme içermeyen bir dizgeyi yazar.
-
Hello Worlddizgesi- Dizge,
"Hello World!"ifadesinin ardından bir NULL sonlandırıcı gelecek şekilde tutulur. - C'de dizgelerle birlikte uzunluk bilgisi bulunmadığı için dizgenin sonu NULL sonlandırıcıyla işaretlenir. NULL sonlandırıcı olmazsa program izin verilmeyen belleği okumaya devam eder ve bir Segmentation Fault ile çöker.
- Derleyici optimizasyonu nedeniyle
printf()içinde kullanılan yeni satır (\n) kaldırılmıştır. Çünküputs()dizgeyi yazdıktan sonra zaten yeni satır ekler.
- Dizge,
-
puts()fonksiyonuputs()fonksiyonu yeniden standart kütüphane içindeki kodu çağırır.- glibc koduna bakıldığında
_IO_puts -> _IO_new_file_xsputnsırasıyla çağrıldığını görmek mümkündür, ancak kod oldukça karmaşıktır ve açıklaması zordur. musl libcdurumunda ise yapı daha basittir.puts -> fputs -> fwrite -> __fwritex -> __stdio_write -> syscallsırasıyla çağrılır.
-
Sistem çağrısı
- C kütüphanesi ne kadar büyük olursa olsun donanımla doğrudan iletişim kuramaz. Bunu yalnızca kernel yapabilir.
- Bu yüzden
puts()çağrısı eninde sonunda işletim sisteminden bir şey yapmasını istemekle sonuçlanır. Burada yapılan şey, çıktı akışına bir dizge yazmaktır. musl libc, birden çok tamponu tek seferde yazmaya izin verenwritevadlı sistem çağrısını kullanır.- Sistem çağrısı, register'lara parametreler yerleştirilip
syscallkomutu çalıştırılarak yapılır. Ardından denetim kernel'a geçer; kernel parametreleri okuyup sistem çağrısını gerçekleştirir.
-
Kernel
- Linux kernel'i, sistem çağrısıyla talep edilen işlemi yerine getirmek zorundadır.
writesistem çağrısı kernel'e, dosya sistemindeki açık bir dosyaya ya da akışa yazmasını söyler. write, yazılacak dosya tanımlayıcısı, yazılacak tampon ve yazılacak bayt sayısı olmak üzere 3 parametre alır.- Gerçekte nereye yazılacağı duruma göre değişir. Bir terminal emülatörü ise sanal terminal (pty) olarak görünür; uzaktan oturum açma varsa
sshd'ye iletilir; fiziksel terminal varsa seri-USB adaptörüne gider. Framebuffer konsoluysa kernel metni render edip ekrana çıkarır.
- Linux kernel'i, sistem çağrısıyla talep edilen işlemi yerine getirmek zorundadır.
-
Sonuç
- Modern yazılım sistemleri donanım üzerinde son derece karmaşık ve sofistike biçimde çalıştığı için, bilgisayarın yaptığı küçük bir işi tamamen anlamaya çalışmak aslında çok anlamlı değildir.
- Her şeyi açıklayabilmek için pek çok ayrıntının atlanması gerekti.
Hello Worldmesajını göndermek, şu anda bilgisayarda çalışan sayısız sistem çağrısı ve programdan yalnızca biridir.
GN⁺'nin görüşü
- Bu yazı, bilişim sisteminin her katmanının soyutlama yoluyla alt katmanın karmaşıklığını gizlediğini ve bunun da geliştiricilerin uygulamaları daha rahat geliştirmesini sağladığını gösteriyor.
- Öte yandan, bir uygulamadaki tek bir satırın çalışması için altta ne kadar çok şey olup bittiğini fark ettiriyor ve hata ayıklamanın neden zor olduğunu da gösteriyor.
- Her programcının, ağırlıklı olarak kullandığı dilin altındaki sistemleri iyi bilmesi gerektiğini düşünüyorum. Her şeyi bilmek şart değil, ama soyutlanan kısmın gerçekte nasıl çalıştığını bilmek önemli.
- Üst düzey bir dil kullanılsa bile bellek yapısı, stack ve heap, sistem çağrıları gibi sistem programlama kavramlarını öğrenmek; hata ayıklama ve performans optimizasyonunda büyük fayda sağlar.
- Uygulama geliştiricilerinin derleyiciye ya da C kütüphanesine doğrudan dokunması pek gerekmeyebilir, ancak yazdıkları programın sonunda sistemi nasıl kullandığını anlamak, iyi bir programcı olmanın vazgeçilmez parçasıdır.
Henüz yorum yok.