Daily Archives: May 22, 2011

C – Libc ve Runtime


ÖNEMLİ NOT: Bu yazı, alıntılara rağmen, kişisel yorum içermektedir. Bu yazıyı bunu dikkate alarak okuyun. Daha önceden bu konuyla ilgili okuduklarınızı ya da bildiklerinizi değerlendirmek için okumanız daha faydalı olacaktır. Böyle önemli bir konuda kimseyi yanıltmak istemem. Yazıyı okuduktan sonra eksik olan ya da yanlış yaptığım kısımlar için yapılacak yorumlara minnettar olacağım.

Stackoverflow‘da, What is the C Runtime sorunda;

Microsoft runtime library, Windows İşletim Sistemi için programlama yapabilmek için, fonksiyonlar sağlar. Bu fonksiyonlar, C ve C++ dilleri tarafından sağlanmayan birçok programlama işlevini otomatize eder(çağrılabilir halde sağlar).

Peki, mesela libcmt.lib nedir? Bu sağlanan fonksiyonlar hangileridir? C standard library‘sini, C Derleyicisinin bir bir parçası olarak biliyorum; o halde libcmt.lib, C standard library’sindeki fonksiyonların Win32 ortamında çalışabilir halde olan bir gerçeklemesi mi?

Öncelikle bu soruda belirtilen ifadelere bakalım. İlk paragrafta, Microsoft Runtime Library ile sağlandığı söylenen fonksiyonlar, işletim sistemi çağrılarıdır.

C Wrapper Fonksiyonları ve Sistem Çağrıları

Libc‘deki birtakım C fonksiyonları(IO fonksiyonları -> fopen(), open(), fdopen(), open64(), ...; Dizin fonksiyonları -> chdir(), rename(), rmdir(), mkdir(), ...; Diğer fonksiyonlar -> ... ) işletim sistemi çağrılarının Wrapper‘larıdır(BSD Bu fonksiyonların kullandıkları sistem çağrıları BSD UNIX için sırasıyla, IO sistem çağrıları için -> open; Dizin sistem çağrıları için; chdir, rename, rmdir, mkdir şeklindedir.).

  1. Wiki – Wrapper Library
  2. Wİki – System Call
  3. BSD UNIX System Calls List #Çok çok önemli 🙂
  4. Dosya Erişimi ve Dizin İşlemleri için Sistem Çağrıları
  5. UNIX Sistem Çağrılarını ve Libc Kütüphane Fonksiyonlarını Kullanmak

Soruya verilen cevaplardan;

libcmt

libcmt, C standart kütüphanesindeki fonksiyonların, Microsoft‘un C derleyicisi tarafından derlenip oluşturulmuş gerçeklemelerinden birisidir.

Microsoft‘un birçok libc gerçeklemesi(implementation) vardır. Bunlar;

  1. Her zaman statically linked olan single threaded gerçekleme, (libc.lib)
  2. Multi-threaded; (NOT:libcmt‘deki mt buradan gelir.)
    1. statically linked,(libcmt.lib)
    2. dynamically linked,(libcmt.dll)
  3. Bütün bu gerçeklemelerin debug ve release versiyonlarıdır.(libcmtd, libcmt)
C Standard Library API
libc(C Standart Kütüphanesi); bu kütüphanenin ister Win32 gerçeklemesinin olsun, ister UNIX gerçeklemesinin olsun ya da başka bir işletim sisteminin ve/veya donanımın(işlemcinin komut kümesi); bu kütüphaneyi gerçeklerken mutlaka gerçeklemeleri gereken standart fonksiyon arayüzleri(interface) kümesi tanımlar. Bu gerçeklemeleri yapanlar bu standart kütüphaneyi gerçeklemenin yanında ekstra fonksiyonlar da sağlar. Ayrıca bu vendor‘lar, sadece kendi Compiler programları tarafından kullanılması için birkaç fonksiyon daha tanımlayabilirler.

libcmt içerisinde gerçeklenmiş fonksiyonların listesini görebilmek için; Visual Studio Komut Satırında;

libcmt.dll nin bulunduğu dizine geçildikten sonra,
lib -list libcmt.lib
yazıldığında, bu kütüphanedeki fonksiyon gerçeklemelerinin bulunduğu dosyaların (object file -> .o) uzun listesini üretir. Bu listedeki isimler, C Standart Kütüphane API’sindeki fonksiyon isimlerine tamı tamına uymamakla birlikte, benzerler. Bir dosyada birden fazla fonksiyon gerçeklemesi olabilir. Bu yüzden, belli bir .o dosyasının içeriğine bakmak için, önce bu dosya açılır:
lib -extract
dosya açıldıktan sonra içindeki herhangi bir fonksiyon gerçeklemesine,
dumpbin /symbols
ile bakılabilir.

UNIX ortamları için kullanılan derleyicilerden birisi GNU C Compiler(gcc) dır. C standart kütüphanesinin bu derleyici ile gerçeklenmiş hali glibc dir.

Linux From Scratch‘ta(Adım-adım işlemlerle tamamen kaynak koddan kendine ait değiştirilmiş bir Linux sistemini oluşturmak.) glibc kısmında, glibc‘nin kurulumundan, ayarlarından, Dynamic Loader‘ın ayarlanmasından ve glibc‘nin içindeki dosyalardan bahsediliyor.

 

Derleyiciler 

  1. GNU C Compiler – GCC – Dökümantasyonu‘nda GCC ve bileşenleri hakkında bilgiler mevcut. GCC dökümantasyonunda Standard Libraries kısmıda, GNU C kütüphanesinin neredeyse tam olarak C99 standardını desteklediği belirtiliyor. Extensions to the C Language Family kısmında, GNU C, ISO Standard C‘de bulunmayan bir takım dil özellikleri sağlar deniliyor.
  2. lcc-Win32 A Compiler System for Windows: Windows için yapılmış, GCC’ye göre daha basit bir derleyici sistemi. Bu sistem bir Code Generator(Compiler, Assembler, Linker, Resource Compiler ve Librarian-Library Manager), IDE(editor, debugger, makefile generation, resource editor etc.) içerir.
  3. Pelles C: Windows ve Windows Mobile için bir complete development kit. Burada özellikler kısmında, C99 standardını desteklediği belirtiliyor. Yani, bu derleyici C99 specification‘unda belirtilen fonksiyonların gerçeklemelerini içeriyor.

Runtime ve Runtime Library

C ve C++ Runtime‘ları, fonksiyonlar kümesidir. .NET Runtime ise IL interpreter, garbage collector ve dahasıdır.
diye söyleniyor. Bu cevap çok açık değil. Hangi fonksiyonlar(C Runtime Library API‘sinde belirtilenler mi ve bunlardan başka hangileri) açıkça belirtilmiyor. Ayrıca, .NET library‘si denmiyor ve bu ifadeden .NET Runtime‘ının NET Runtime Library’sini içine aldığı ima ediyor. Buradan, Runtime‘ın Runtime Library‘i içerdiği sonucuna varabiliriz.

Diğer bir açıklamada Wikipediaruntime library/run-time system‘a atıf yapılarak,

Runtime library, derleyici tarafından kullanılan özel bir program kütüphanesidir. Bir programın çalışması anında, programlama diline yerleşik olan fonksiyonları(built-in) gerçeklemek için kullanılır. Bu fonksiyonlar; IO fonksiyonları ya da  Memory Management fonksiyonları gibi fonksiyonlardır.
diye söyleniyor. Bu cevap çok daha açık bir cevap. Özellikle, derleyici tarafından kullanılan özel bir program kütüphanesi açıklaması. C’de bir kaynak kod, object file‘a dönüştüğünde  gerçeklenmiştir. Bu işlem için libc kulanıldığına göre Runtime Library, Libc‘yi içerir.

NOT: Bununla birlikte, IO işlemleri ve Memory Management fonksiyonları için işletim sistemine yapılan sistem çağrıları kullanılır. Yukarıda, Microsoft Runtime Library‘nin sağladığı, C ve C++’da olmayan, fonksiyonların sistem çağrıları olduğunu söylemiştim.

Runtime-system(kısaca runtime da denir), belli bir programlama diliyle yazılmış programların çalışmasını desteklemek için tasarlanmış yazılımdır.

Runtime System, temel alt-seviye komutların gerçeklemelerini ayrıca yüksek seviye komutların gerçeklemelerini yapar ve type checkingdebugging, kod üretme ve optimizasyon sağlar. Runtime System‘in bazı servisleri programlama dilinin API‘si ile programcıyı sağlanır ama diğer servisler(task scheduling ve kaynak yönetimi gibi.)

Bu açıklama diğerlerine göre daha net bir açıklama. Çünkü;
Runtime‘ın o dilin derleyici yazılımını içine aldığı, çalıştırılabilir dosyayı çalıştırabilmek için işletim sisteminin birtakım araçlarının bir şekilde kullanıldığı belirtiliyor.

Bu noktadan sonra şu sonuçlara varabiliriz:

  1. Runtime = Runtime Library = Runtime System
  2. Runtime, İşletim Sistemi Çağrılarının bulunduğu işletim sistemi API‘si + Derleyicinin Kendi işlevleri için kullandığı kütüphane + Derleyici Programın kendi amacını yerine getirmek için kullandığı fonksiyonlar + Libc(Standart C Kütüphanesi) fonksiyonlarıdır.
  3. Yukarıdaki cümleden ve yazının genelindekilerden şunu çıkarabiliriz: Derleyici; kendi kütüphanesini, Libc‘yi, işletim sistemi API‘sini kullanarak çalıştırılabilir dosya oluşturur. Bu çalıştırılabilir dosya, işletim sisteminin Loader‘ı tarafından belleğe yüklenir ve işletim sistemi tarafından Process oluşturulur.

Çalıştırılabilir Dosya ve İşletim Sistemi

Stackoverflow‘da Windows ve Linux tarafından üretilmiş olan çalıştırılabilir dosyaların farklı yanları nelerdir? diye soruluyor. Soruda,

Linux‘ta oluşmuş olan bir çalıştırılabilir dosya neden Windows‘ta çalıştırılamıyor? Windows ve Linux‘ta ortak olan işletim sistemi çağrılarını kullanan bir C programı yaparsak, derleyici, Linux ve Windows için farklı binary‘ler mi üretir?

diye soruluyor. Sorunun cevabına geçmeden önce şunu söylemek isterim: Soruda zaten bir çalıştırılabilir dosyanın işletim sistemi tarafından üretildiği söyleniyor. Eğer bir çalıştırılabilir dosya farklı işletim sistemi tarafından üretilmişse, düz bir mantıkla düşünürsek, oluşan çalıştırılabilir dosyalar da farklı olacaktır. Bu ima, tam olarak doğru değil ki zaten sorunun açıklamasında, bunun derleyici tarafından oluşturulduğu belirtiliyor. Ancak, işletim sistemi tarafından  oluşturulmuş diye biraz genel bir tabir kullanılmasının sebebi, işletim sistemi çağrıları kullanılarak oluşturulmuş denmek istenmesidir. Farklı işletim sistemi denerek de, her işletim sistemine özgü farklı işletim sistemi çağrıları denmek isteniyor.

Sorunun cevaplarında,

En büyük problem, işletim sistemi API’leridir. Bir Windows çalıştırılabilir dosyasının process’i oluşturulurken, Windows API‘ı çağrılır. API‘deki fonksiyonlarla işletim sistemine yapılan çağrılar vasıtasıyla; stack ayarlanır, belli bir hafıza bölgesi bu process‘e tahsis edilir. Bir işletim sisteminin API’sindeki çağrılar da o işletim sistemine özgü olduğu için başka işletim sistemlerinde farklıdır. Bu yüzden, bir çalıştırılabilir dosya hem Windows’ta hem de Linux’ta çalışmaz.

Farklı Container formatları kullanırlar; çoğu UNIX çalıştırılabilir dosyası  ELF dosyalarıdır. Windows çalıştırılabilir dosyaları ve DLL‘ler ise PE dosyalarıdır.

diye söyleniyor. Bu işlemler Loader ile ilgilidir. Loader mevzusunu sonraki yazılarımdan birinde ele alacağım.

Güncelleme – Başlangıç: 31.05.2011

İşletim sistemi API’sinden bahsetmişken;

Linux From Scratch – Linux API Headers yazısında Linux API‘sini belirten Header dosyalarının; kısa özetleri, dahil oldukları mantıksal gruplar, bu grupların yerleri anlatılıyor.

Linux API Header‘ları,  glibc‘nin görebilmesini Linux Kernel‘ının API‘sini görebilmesini sağlar. Linux Kernel’ı, sistemin C kütüphanesi(glibc)nin kullanabilmesi için bir API sunar. Bunu, Linux Kernel kaynak kodundaki çeşitli Header dosyaları ile yapar.

Şimdi Linux kaynak kodundan, kullanıcının-görebildiği(user-visibleHeader dosyalarını çıkaralım ve test edelim. Bunlar, kaynak kodda bir yerel alt dizinde bulunur ve kurulumda olması gereken yere kopyalanırlar. Bu işlem esnasında kopyalamanın yapılacağı hedef dizinde önceden var olan dosyalar silinir. Ayrıca, burada kernel geliştiricilerinin kullandığı bazı gizli dosyalar vardır. Kopyalamanın yapılacağı kaynak dizinde, kopyalamadan önce bu dosyalar da silinir.

make headers_check 
make INSTALL_HDR_PATH=dest headers_install 
find dest/include \( -name .install -o -name ..install.cmd \) -delete 
cp -rv dest/include/* /usr/include 

Yukarıdaki son işlemde, hedef dizin

/usr/include

dir. Yani, Linux API Header dosyaları burada bulunur. Ancak, burada belli klasörler içinde mantıksal olarak gruplanmış halde bulunurlar.

Yüklenen Header dosyaları ve bulundukları dizinler:

/usr/include/asm/*.h, 
/usr/include/asm-generic/*.h, 
/usr/include/drm/*.h, 
/usr/include/linux/*.h, 
/usr/include/mtd/*.h, 
/usr/include/rdma/*.h, 
/usr/include/scsi/*.h, 
/usr/include/sound/*.h, 
/usr/include/video/*.h, 
/usr/include/xen/*.h

Yukarıdaki dizin yapısı,

/usr/include/linux/*.h The Linux API Linux Headers

gibidir.

Güncelleme – Bitiş: 31.05.2011

Stackoverflow‘da, Belirli bir işletim sistemine göre değil de belirli işlemci mimarisine göre, bir çalıştırılabilir dosyayı nasıl oluşturabilirim? diye soruluyor. Soruya verilen cevap, Container ve Loader kavramlarına çok iyi bir şekilde değiniyor:

Run” ne demek onun hakkında bir düşünelim… Oluşturulan binary kodları bir şey belleğe yüklemeli. Bu bir İşletim Sistemi özelliği. .exe ya da binary executable file(.exe‘nin UNIX eşdeğeri) ya da bundle ya da her neyse; bunların birçok özelliği işletim sistemine göre biçimlendirilmiştir ki bu sayede işletim sistemi onu belleğe yükleyebilir. I/O rutinleri, OS API‘sini encapsulate eden fonksiyonlardır. Yani OS her yerde.

Burada bahsedilen, çalıştırılabilir dosyaların işletim sistemine göre hazırlanmış formatları ELF ve PE‘dir. “Bu işletim sisteminin bir özelliği” denirken Loader kastediliyor. Bu formatları taslak yapılar olarak, çalıştırılabilir dosyaları da bunların içine koyulan yapılar olarak düşünürsek; bu formatlara Container diyebiliriz.

Eski günlerde, çalıştığımız bilgisayarlarda OS yoktu, C de yoktu. Assembler ve Linker’ları kullanarak büyük binary image‘lar oluşturuyorduk. Bu şekilde bunları makineye yükleyebiliyorduk.

Burada binary image kavramı, çalıştırılabilir dosya kavramının bir başka karşılığı. Container‘a binary image‘ı koyuyoruz diyebiliriz.

Kodu belleğe yükleyebilmek için front panel key‘leri kullanıyorduk ve yükleyeceğimiz kodu okurken punched paper-tape reader a benzeyen kullanışlı bir cihaz kullanıyorduk. Bu, boot linking loader yazılımını yüklerdi. Sonra, bu linking loader bellekte olduğunda tape‘e assembler ile veri yazabilirdik. Kendi device driver’larımızı yazdık ya da kaynak kod biçiminde, punched on paper tapes, kütüphane fonksiyonlarını kullandık. … Sonra, basit API’lere sahip olan basit OS’ler yazdık. Bu basit API‘ler device driver‘lar ve birkaç basit araç olan bir dosya sistemi(file system), bir editör ve bir derleyici yazdık. Bu derleyici Jovial isimli bir dil içindi.

Daha sonra işletim sistemin kullanmadan C++ kodu yazılabileceği belirtiliyor ve bunun için yapılması gerekenler sıralanıyor:
  1. İşlemci chipset‘inin parçaları olan BIOS ve BIOS‘a benzer yapılar hakkında bilgiler öğren. BIOS, ROM üzerine yazılmış olan POST(power-on self-test) işlemini yapan basit sürücüleri yükleyen ve boot block‘u yerleştiren küçük bir işletim sistemidir.
  2. Kendi boot block‘unu yazmayı öğren. Bu, POST işlemi yapıldıktan sonra yüklenecek olan ilk bildiğimiz anlamdaki yazılımdır. Bu şekilde donanım üzerinde kontrolü, işletim sistemine bırakmadan, ele alabilmiş olursunuz.
  3. GRUB, LILO ya da BootCamp‘ın işletim sistemini nasıl başlattığını öğren. Bu programlar yüklendiklerinde artık, kendi programını yükleyebilirsin. Bu durumda donanım üzerinde işletim sistemi değil senin programın çalışıyor olur.
  4. ELF hakkında oku.
  5. Device Driver‘ların nasıl yazıldığını öğren. Eğer işletim sistemi kullanmayacaksan, Device Driver yazmak zorunda kalacaksın.
Bütün bunlarla birlikte, programlama diline yerleşik herhangi bir fonksiyonu kullanmadan, sadece programlama dilinin yazım kuralında belirtilen ifadeleri kullanarak, C ile Standart Kütüphane olan LIBC’yi kullanmadan Program Yazmak yazısında belirttiğim gibi, program yazılabilir. Burada, libc ve işletim sistemi çağrıları kullanılmaz.

Leave a comment

Filed under C