Çağrı Uzlaşımları

Linux

GCC'ye ilintileme

C-asm karışımı projeler üretiyorsanız bu tercih edilen yoldur. GAS açıklamaları olan Linux çekirdeği .s dosyalarından örnekleri ve GCC belgelerini inceleyiniz (as86 ile ilgili olanları).

32 bitlik geridönüş adresi üzerinde, 32 bitlik bağımsız değişkenler yığıta (stack) ters sözdizimsel sırada itilirler (push) (böylece doğru sırada erişilir/çıkarılırlar (pop)). %ebp, %esi, %edi ve %ebx çağrılan tarafından kullanılır, diğer yazmaçlar ise çağırıcı tarafından kullanılır; %eax sonuçları tutmak içindir veya %edx:%eax 64 bitlik sonuçlar için kullanılır.

FP yığıtı (FP stack): Emin değilim ama, sanırım tüm sonuç, saklanmış çağrıcıların tamamı st(0) içindeydi. Eğer daha fazla ayrıntı istiyorsanız http://www.caldera.com/developer/devspecs/ adresindeki SVR4 i386 ABI belirtimi iyi bir başvuru kaynağıdır.

GCC'nin çağrı uzlaşımlarını değiştirmek için, yazmaçları önceden ayırtan, yazmaçlarda bağımsız değişkenler barındırılmasını sağlayan, FPU'yu dikkate almayan, v.b. seçenekleri olduğunu unutmayın. i386 .info sayfalarına bakınız.

Standart GCC çağrı uzlaşımlarını izleyecek bir işlev için cdecl ve regparm(0) bildirimlerini yapmanız gerektiğine dikkat edin. GCC info sayfasından C Extensions::Extended Asm:: kısmına bakınız. Aynı zamanda Linux'un kendi asmlinkage makrosunu da nasıl tanımladığına bakınız.

ELF ve a.out arasındaki sorunlar

Bazı C derleyicileri, diğerleri yapmadığı halde, her sembolden önce bir alt çizgi yerleştirirler.

Özellikle, Linux a.out GCC böylesi bir ön yerleştirmeyi yapar, oysa ki Linux ELF GCC yapmamaktadır.

Eğer her iki davranış biçimiyle de aynı anda uğraşmak isterseniz, varolan paketlerin bunu nasıl yaptıklarına bakınız. Mesela, Elk, qthreads, OCaml... gibi eski bir Linux kaynak ağacı edinin.

Ayrıca, örneğin, foo() işlev çağrısının gerçekte bir makine dili kodu olan bar'ın çağrılmasını sağlaması için şu şekilde bir ek ifade ile örtük C->asm isim değişikliğini zorlayabilirsiniz:

void foo asm("bar") (void);

Binutils paketindeki objcopy uygulamasının a.out nesnelerinizi ELF nesnelerine dönüştürmeyi mümkün kılması gerektiğini unutmayın, hatta bazı durumlarda tam tersine de imkan tanır. Daha genel olarak, pekçok dosya biçimi arasında dönüşüm gerçekleştirir.

Doğrudan Linux sistem çağrıları (syscalls)

Genelde C kütüphanesi (libc) kullanmanın tek yol olduğu ve doğrudan sistem çağrıları yapmanın kötü olduğu söylenir. Bu doğrudur. Bir bakıma... Genel olarak, libc kütüphanesinin kutsal olmadığını bilmelisiniz ve pekçok durumda sadece bazı denetimler yapar, sonra çekirdeğe çağrı yapar ve ardından errno'ya atama yapar. Bunu kendi programınızda da yapabilirsiniz (eğer ihtiyacınız varsa) ve programınız bir düzine kat daha küçük olacaktır, bu da gelişmiş bir başarım artışına sebep olacaktır, bu da sırf paylaşımlı kütüphaneleri kullanmadığınızdan kaynaklanacaktır (durağan (static) kütüphaneler daha hızlıdır). Sembolik makina dili ile programlamada libc kullanımı pratik birşeyden çok zevk/inanış meselesidir. Linux'un POSIX standartlarına uygun olmayı hedeflediğini unutmayın, benzer şekilde libc de. Bu da, hemen her libc "sistem çağrısı" sözdizimin gerçek çekirdek sistemi çağrılarındaki sözdizimiyle örtüşmesi anlamına gelir (ve tam tersi). Buna ek olarak, GNU libc (glibc) sürümden sürüme daha yavaş hale gelmekte ve daha çok bellek tüketmektedir. İlerde siz de kendi, değişik türlerde, libc'ye özel işlevlerinizi (sadece birer sistem çağrısı değil) tanımlayacaksınız (printf() ve şürekası)... ve buna hazırsınız, değil mi? :)

Doğrudan sistem çağrıları yapmanın artı ve eksileri şu şekilde özetlenebilir:

Artılar

  • olası en küçük boyut; son baytı sistem dışında bırakmak
  • olası en yüksek hız; favori karşılaştırmalı değerlendirme (benchmark) dışı döngüleri bunun dışında bırakmak
  • tam denetim: program/kütüphanenizi size özgü dile veya bellek gereksinimlerine veya herhangi bir şeye uydurabilirsiniz.
  • libc çerçöplerinin yol açacağı bir kirlilik olmaz
  • C çağrı uzlaşımlarının yol açacağı bir kirlilik olmaz (eğer kendi dilinizi veya ortamınızı tasarlıyorsanız)
  • durağan kütüphaneler libc yükseltmelerinden ve çökmelerinden veya yorumlayıcıya #! yolu ile asılmanızdan sizi bağımsız kılar. (ve daha hızlıdır)
  • biraz da eğlence içindir (sembolik makina dili dışında heyecanlanmaz mısınız?)

Eksiler

  • Eğer bilgisayarınızda bir başka program da libc kullanıyorsa, libc kodunun yinelenmesi otomatik olarak belleğin, korunması yerine, boşa harcanmasına sebep olacaktır.

  • Pekçok durağan ikilikte (static binary) gereksiz yere tanımlanan servisler bellek israfıdır. Fakat kendi libc yerdeğiştirmenizin bir paylaşımlı kütüphane olmasını sağlayabilirsiniz. (NBB: Bu yukarıdaki iddiasını yalanlamıyor mu? ;-))

  • Herşeyi sembolik makina dili ile yazmak yerine, bir çeşit bayt kodu, sözcük kodu veya yapısal yorumlayıcıya sahip olmakla, boyut çok daha iyi korunur. (derleyicinin kendisi C veya sembolik makina dilinde yazılabilir). İkilik çeşitliliğini küçük tutmanın en iyi yolu, çoklu ikilikler yapmamaktır, yerine #! önekiyle başlayan yorumlanan işlem dosyaları kullanmaktır. Bu, OCaml'ın sözcük kodu kipinde çalıştığındaki durumdur (eniyilenmiş doğal kod kipine karşın) ve de libc kullanımıyla uyumludur. Bu aynı zamanda unix araçlarının yeniden gerçeklenimi olan Tom Christiansen'in Perl Güç Araçları (Perl PowerTools)'nın nasıl çalıştığının açıklamasıdır. Son olarak, bunları küçük tutmanın bir yolu da, tam olarak yolu kodlanmış harici bir dosyaya bağımlı olmamaktır, bu da kütüphane veya yorumlayıcı olsun, tek bir ikilik dosyaya sahip olmak ve buna sabit veya sembolik bağlar yapmaktır: aynı ikilik size en makul alanda, alt yordamların gereksiz kullanımı veya gereksiz ikilik başlıkları olmadan, ihtiyacınız olan herşeyi sunacaktır; kendine özel davranışı argv[0] değerine bakarak yönlendirecektir; bu durumda tanınan ismiyle çağrılmaz, bir kabuğa öntanımlı olabilir ve muhtemelen bir yorumlayıcı olarak da kullanışlı olmuş olur!

  • Nadir linux sistem çağrıları yanında libc'nin sunduğu pekçok işlevsellikten faydalanamazsınız: malloc, thread, locale, password, yüksek-seviyeli ağ yönetimi, v.b. işlevsellikler, kılavuz sayfalarının 2. bölümünde değil, 3. bölümünde yer alır.

  • Bu yüzden, libc'nin, printf()'den malloc() ve gethostbyname'e uzanan çok sayıda parçalasını yeniden gerçeklemek zorunda kalabilirsiniz. libc varken bu gereksizdir, hatta oldukça sıkıcı olabilir. Bazılarının libc'nin bazı kısımları için "hafif" (ligth) yerdeğiştirmeler yazdıklalarına dikkat ediniz - bunları inceleyiniz! (Redhat'in minilibc'si, Rick Hohensee'nin libsys'si, Felix von Leitner'in dietlibc'si, Christian Fowelin'in libASM'si, asmutils projesi de tamamen saf sembolik makina libc'si ile çalışmaktadır)

  • Durağan kütüphaneler sizi, libc güncellemelerinden ve aynı zamanda, gzip-sıkıştırılmış dosyalarını düzgün şekilde ihtiyaç olduğunca açmanızı sağlayan, zlib paketi gibi libc eklentilerinden faydalanmaktan alıkoyar.

  • libc tarafından eklenen çok az sayıdaki komutun sistem çağrılarının maliyetine kıyasla küçük bir hız yükü olabilir. Eğer hız gözönüne alınırsa, temel sorununuz, onların sarmalayıcı işlevlerini değil, kendi sistem çağrılarınızın kullanımıdır.

  • Kendi çağrı uzlaşımları olan ve standart uzlaşım kullanımında kural dönüşüm yüküne (high convention-translation overhead) büyük önem veren L4Linux gibi Linux'un mikro çekirdek sürümleri çalıştırılırken, sistem çağrıları için standart sembolik makina dili API'leri kullanmak, libc API'leri kullanmaktan daha yavaştır (L4Linux, sistem çağrı API'leri ile yeniden derlenmiş şekilde gelir; elbette kendi kodunuzu onun API'leriyle tekrar derleyebilirsiniz).

  • Genel hız eniyileme konularıyla ilgili olarak önceki konulara bakınız.

  • Eğer sistem çağrıları size göre çok yavaşsa, kullanıcı adasında kalmak yerine çekirdek kaynak kodlarını (C dilindeki) elden geçirmeyi isteyebilirsiniz.

Eğer yukarıdaki artı ve eksileri zihninizde iyice ölçüp tarttıysanız ve hala doğrudan sistem çağrılarını kullanmak istiyorsanız, size bazı önerilerim olacak:

  • Sistem çağrı işlevlerinizi taşınabilir bir şekilde, C dilinde (sembolik makina dilinin taşınamaz özelliğine karşın) asm/unistd.h ile sunulan makroları kullanarak tanımlayabilirsiniz.

  • Sistem çağrısı işlevlerini değiştirmeyi deneyecekseniz, libc'den kaynak kodunu alıp inceleyin. (Ve daha iyisini yapabileceğinizi düşünüyorsanız, bunu yazarlara bildirin!)

  • İstediğiniz her işi yapan bir sembolik makina kodu için Özkaynaklarına bakınız.

Temelde, eax içerisine __NR_sistemçağrı_ismi numarası (asm/unistd.h dosyasındadırlar) ile int 0x80 değeri ve parametreleri de sırasıyla (altıya kadar) ebx, ecx, edx, esi, edi, ebp içine konur.

Sonuç eax içerisinde döndürülür, negatif sonuçlarda hata ile döner, bunun karşılığı da libc içerisinde errno'dur. Kullanıcı yığıtına dokunulmaz, dolayısiyle bir sistem çağrısı yaparken geçerli bir kullanıcı yığıtına ihtiyacınız yoktur.

Not

ebp'ye 6 parametre aktarımı Linux 2.4 sürümünde mümkün olmuştur, daha önceki Linux sürümleri yazmaçlarda sadece 5 parametreye bakıyordu.

Linux Çekirdeğinin Dahili Yapısı (Linux Kernel Internals) belgesi ve özellikle i386 Mimarisinde Sistem Çağrıları Nasıl Gerçeklenir? (How System Calls Are Implemented on i386 Architecture?) bölümü çok daha sağlıklı bilgi verecektir.

Başlatırken bir sürece parametrelerin aktarılmasında olduğu gibi, genel prensip, yığıtın orjinal olarak argüman sayısını (argc) ve ardından *argv'ler halinde argüman göstericilerini, bundan sonra da environ (ortam) için boş gösterici ile sonlandırılmış boş karakter sonlandırmalı isim=deger dizgelerini içereceğidir. Daha ayrıntılı bilgi için, Özkaynaklar bölümünü okuyunuz, libc'nizdeki C başlatma (crt0.S veya crt1.S) kodlarını veya bunların Linux çekirdeğinde olanlarını (exec.c ve linux/fs içindeki binfmt_*.c) inceleyiniz.

Linux altında donanımsal G/Ç

Eğer Linux altında doğrudan port erişimi ile G/Ç işlemleri gerçekleştirmek istiyorsanız, bu ya işletim sisteminde bir değişiklik gerektirmeyen basit bir iştir ve bununla ilgili G/Ç portları ve programlama (IO-Port-Programming) küçük nasıl belgesini okumanız yeterli olur ya da bir çekirdek aygıt sürücüsü gereklidir ve çekirdek kaynak kodlarını elden geçirme, aygıt sürücüsü geliştirme, çekirdek modülleri, v.b. ile ilgili daha fazla bilgi edinmeniz gerekir. Bunlarla ilgili pek çok belge ve NASILlar LDP sayfalarında bulunmaktadır.

Belki de, grafik programlama yapmak istersiniz, o zaman GGI veya XFree86 projelerinden birine katılın.

Bazıları daha iyisini bile yapabilir, yorumlanmış belli bir alana özgü bir dilde, GAL, küçük ve güçlü XFree86 sürücüleri yazabilirler, bazı değerlendirmelerden sonra da C ile yazılmış sürücülerin verimini arttırabilirler (sürücüler ne sadece asm'dir ne de sadece C!). Burada sorun, verimi arttırmak için kullanılacak bazı değerlendiricilerin özgür olmamasıdır. Bunların özgür sürümlerini gerçekleştirecek olan var mı?

Herneyse, tüm bu durumlarda, herşeyi sembolik makina kodu ile yazmak yerine GCC satıriçi sembolik makina dilini linux/asm/*.h dosyalarındaki makrolarla kullanmak daha iyi olacaktır.

Linux/i386'daki 16 bitlik sürücülere erişim

Böyle bir şey teorik olarak doğrudur (kanıt: DOSEMU'nun programlara seçici bir şekilde port atamalarını nasıl yaptığını inceleyiniz) ve ben de bir yerlerde birilerinin bunu yaptığı söylentilerini de duydum (bir PCI sürücüsü mü? bir VESA erişim aracı mı? ISA PnP mi? bilmiyorum). Eğer bunun hakkında çok net bilginiz varsa, o zaman çok daha memnun olacaksınız. Herneyse, daha ayrıntılı bilgi için bakılması gereken kaynaklar Linux çekirdeğinin kaynak kodları, DOSEMU kaynakları (ve DOSEMU deposundaki diğer programlar) ve de Linux altında pekçok düşük seviyeli programın kaynaklarıdır ... (belki CGI'da olabilir, eğer VESA desteği varsa).

Temel olarak ya 16 bitlik korumalı kipi veya vm86 kipini kullanmalısınız.

İlkinin yapılandırılması daha basittir, fakat sadece segman aritmetiği veya mutlak segman adreslemesi (özellikle 0. segmanı adreslerken) ile ilgili işlemler yapmayacak iyi davranışlı kodla çalışır, fakat şans eseri tüm segmanlar kullanılırsa, LDT ile ileri düzey ayarlama yapılabilir.

İkincisi ise harcıalem 16 bitlik ortamlarla daha bir uyumluluk sağlar, fakat idaresi daha karmaşıktır.

Her iki durumda da 16 bitlik koda geçmeden önce, şunları yapmalısınız:

  • 16 bitlik kod içerisinde kullanılan herhangi bir mutlak adresi (ROM, video tamponları, DMA hedefleri ve bellek eşlemli G/Ç gibi) /dev/mem'den sürecinizin adres uzayına mmap'leyin.
  • LDT ve/veya vm86 kipi gözlemleyici ayarlayın
  • çekirdekten uygun G/Ç izinlerini kapın (üst bölümlere bakınız)

Tekrar, DOSEMU projesiyle sunulan belgeleri dikkatlice okuyunuz, özellikle Linux/i386 altında ELKS ve/veya .COM programlarını çalıştırmak için kullanılan küçük emülatörlerle ilgili kısımları.

DOS ve Windows

Pekçok DOS çoğaltıcıları (extenders) DOS servisleri için bazı servislerle beraber gelir. Bununla ilgili belgeleri okuyunuz, fakat genelde, int 0x21 ve benzerine benzetim yaparlar (simulate) ve siz de sanki gerçek kipteymiş gibi çalışırsınız (Az gelişmişlik dışında birşeyleri olduğundan ve işlemleri 32 bitlik terimlerle çalışır hale getirdiklerinden şüpheliyim, daha çok gelen kesmeleri gerçek kip veya vm86 eylemcisine yansıtıyor gibiler.)

DPMI hakkındaki belgeler (ve fazlası) ftp://x2ftp.oulu.fi/pub/msdos/programming/ adresinde bulunabilir (yine, asıl x2ftp sitesi kapanıyor (kapandı?), onun için yansıyı kullanın).

DJGPP kendi (sınırlı) glibc türev/altküme/yerdeğiştirmeleri v.b.leri ile gelmektedir.

Linux'tan DOS'a çapraz-derleme (cross-compile) yapmak mümkündür, metalab.unc.edu için olan yerel FTP yansınızın devel/msdos/ dizinine bakınız; Aynı zamanda Utah üniversitesindeki Flux Projesi'nden MOSS DOS-extender (DOS-genişletici)'ye de bakınız.

Diğer belgeler ve SSS, DOS merkezlidir; biz DOS gelişimini tavsiye etmiyoruz.


Windows ve Şürekası
Bu belge Windows programlama hakkında değildir, bununla ilgili pekçok belgeyi heryerde bulabilirsiniz... Bilmeniz gereken, GNU programlarının Win32 altında çalışması için, cygwin32.dll kütüphanesini olduğudur, böylece sizler GCC, GAS ve tüm GNU araçları ile pekçok diğer Unix uygulamasını kullanabilmektesiniz.

Kendi işletim sisteminiz

Denetim duygusu pekçok işletim sistemi geliştiricisini sembolik makina diline çeken şeydir, bu da genelde sembolik makina dili kodları elden geçirmeye yol açmakta veya ondan kaynaklanmaktadır. Her ne kadar temelini oluturan bir sistemin tepesinde çalışabiliyor olsa da (Mac üzerindeki Linux veya Unix üzerindeki OpenGenera), kendi kendine gelişime izin veren bir sistem ancak işletim sistemi olarak isimlendirilebilir.

Böylece, kolay hata ayıklama amaçları için, ilk başlarda kendi işletim sisteminizi Linux üzerinde çalışır şekilde tasarlayabilirsiniz (yavaşlığına rağmen), daha sonra Flux OS aracını kullanarak (kendi işletim sisteminizde Linux ve BSD sürücülerinin kullanımını garanti eder), onu kendi başına çalışır hale getirebilirsiniz. İşletim sisteminiz kararlı olduğunda, artık gerçekten sevdiyseniz, kendi donanım sürücülerinizi yazmanın vaktidir.

Bu NASIL belgesi önyükleyici (bootloader) kodlarını, 32 bitlik kipe geçmeyi, kesmelerle işlem yapmayı, Intel'in temel güvenli kipini veya V86/R86 beyinölümlülüğünü (braindeadness), nesne biçiminizi tanımlamayı ve çağrı uzlaşımlarını kapsaMAmaktadır.

Tüm bunlar için güvenli bilgi bulabileceğiniz yer halihazırdaki işletim sisteminin veya önyükleyicinin kaynak kodlarıdır. Pekçok konu şu adreste mevcuttur: http://www.tunes.org/Review/OSes.html