Cuma, Ocak 21, 2005

Uygulamalar Çekirdekle Nasıl İletişim Kurar

Uygulamalar ve çekirdek arasıdaki iletişim sistem çağrıları ile sağlanır. Çekirdek çağrı isteyenin bu çağrıdan yararlanıp yararlanamayacağına karar verir. Örneğin her isteyenin sabit diske veri yazmaması sağlanır.

Linux’ta strace komutu ile, çalıştırılan programların kullandığı sistem çağrılarını görebilirsiniz.

void main()
{
printf("Denemen");
}

Derleyip, strace ile inceleyelim.

siseci:~/kernel# make 1

siseci:~/kernel# strace ./1

execve("./1", ["./1"], [/* 32 vars */]) = 0

...

...

write(1, "Denemen", 7)= 7

....



execve ile yeni bir process başlatılıyor. write ile 1 ile gösterilen dosyaya 7 bayt uzunluğunda "denemen" yazılıyor.

Şimdi yukarıda anlatılanları burada somut olarak görmekteyiz. Uygulamamız çalıştığında write çağrısını kullanıyor. Çekirdek ise burada programın isteğini değerlendiriyor ve eğer uygunsa izin veriyor.

Burada bir noktayı hatırlatmak istiyorum. Biz direk makine koduyla (assambler) çalışıyor dahi olsak. Uygulamamız ve işlemcinin arasında çekirdek bulunduğundan sistem çağrılarını kullanmamız gerekir. Biraz daha açmak gerekirse çekirdeğin yüklenmiş olması işlemcinin artık meşhur protected mode safhasına geçmiş olması demektir. Bu noktadan sonra bizim uygulamamızın çekirdeğin izini olmadan aygıtlara direk erişimi söz konusu değildir.

Konunun daha açıklayıcı olması açısından sistemin başlama anına geri dönelim ve neler olduğuna bir bakalım:

Linux çekirdeği sadece standart bir C programıdır aslında. Yalnızca iki önemli farklılık vardır. C dilinde
yazılan programların başlangıç noktası main(int argc,char **argv) yordamıdır. Linux çekirdeği start_kernel(void) kullanır. Sistem baslarken ve çekirdek yüklenecekken daha program çevresi mevcut değildir. Yani ilk C yordamı çağrılmadan önce bir kaç şey daha yapılmalıdır. Bu isi gerçekleştiren donanım kodu arch/i386/asm/ dizini altında yer alır.


Assembler dili yordamı çekirdeği tam olarak 0x100000 (1 Mbyte) bellek adresine yükler, ardından da başlatma süreci esnasında dışlamalı olarak kullanılan kesme hizmet yordamlarını (interrupt servicing routines), genel dosya tanımlayıcı çizelgeleri (global file descriptor tables) ve kesme tanımlayıcı çizelgeleri (interrupt descriptor tables) yükler. Bu noktada işlemci korunurlu moda girer. Çekirdeği başlatmak için gereksiniminiz olan her şey init& dizini altındadır. Burası çekirdeği doğru düzgün başlatmakla görevlendirilmiş, geçirilen tüm açılış (boot) parametrelerini dikkate alan start_kernel() yordamıdır. İlk süreç sistem çağrıları kullanılmadan oluşturulur (daha henüz sistemin kendisi yüklenmemiştir).


Ayrıca Aşağıdaki tablo'da işlerin genel olarak nasıl yürüdüğünü anlamamıza yardımcı olacaktır. [En kısa zamanda türkçeleştireceğime söz :) ].

Buradaki gösterim basit ve anlaşılabilir olması için yalnızca temel öğeleri içermektedir. Ancak temel yapıyı kavramak için oldukça yeterlidir.




Yukarıda printf fonksiyonun sebep olduğu write çağrısını basitçe gördük. Ancak iş sistem çağrısı gerçekleştiğinde bitmiyor.

Aşağıda read çağrısı ve ardından gerçekleşen olaylar gösterilmiştir.




Şimdi burada neler olduğunu adım adım inceleyelim.

read çağrısın işlevi basitçe herhangi bir veriyi kaynağından alıp, onu isteyen programın bu iş için ayırdığı belleğe kopyalamaktır.

Bir sistem çağrısının çekirdeğe iletilmesi belirli bir "yazılım kesmesi" tarafından gerçekleştirilir. Bu kesme (aynı zaman da "kapı" da denir) x86 işlemcili Linux sistemler için 0x80 kesmesidir.

0x80 kesmesi hakkında not: Programcının işletim sistemi çekirdeğinden sistem servisi alması için kullanılır. Dosya açma, dosya kapama, aygıtlara erişim, terminal'e çıktı gönderme, yeni süreç yaratma gibi işlemleri yaparken çekirdek bize servis sağlar. Bu yapı, her programcının aygıtlar için kendi erişim kodunu tekrar tekrar yazması gibi bir zorluktan kurtarır bizi (ve tabi zaman kazandırır).Birçok programcı 0x80 kesmesini kullanmaz, hatta çoğu hayatında bir yada birkaç kez sadece ismini duymuş olabilir. Bunun nedeni ise bu tür servislerin üst seviye dillerde (HLL) programcının kolay kullanımı için önceden yazılmış fonksiyonlar tarafından yapılıyor olmasıdır. 0x80 kesmesinin direkt olarak kullanılmak zorunda olduğu tek dil Assembly Programlama dilidir.

İstediğiniz sistem çağrısını "EAX" yazmacına (yazmaç=register) atarsınız. Çağrının kabul ettiği parametreleri de geriye kalan yazmaçlara (EBX, ECX gibi) atayarak "0x80" kesmesini çağırırsınız. Şekilde direk olarak görünmese de aslında atanan __NR_read değil buna karşılık gelen çağrı kodudur. Söz konusu çağrı kodu ve __NR_read in alabileceği diğer parametreler /usr/include/asm/unistd.h dosyasında tanımlanmıştır.Ayrıca read ile ilgili man sayfasına "man 2 read" komutuyla ulaşabilirsiniz.

Buraya kadar yapılan işlemler "user space" de (user mode) da gerçekleşti. Basitçe read adlı sistem çağrısını 0x80 kapısını kullanarak "kernel space" e aktardık.
Şimdi bundan sonra kernel space'de (kernel mode da) olanlara bir bakalım.

Çekirdek ilk olarak bütün yazmaçları kaydeder (SAVE_ALL). Bir sonraki adımda EAX yazmacı tarafından yapılan isteğin bellek sınırlarını aşıp aşamdığı kontrol edilir (Check Limit of EAX) [1]. Daha sonra system çağrıları tablosuna bakıp (EAX yazmacındaki isteği kullanarak) uygun fonksiyonu çaliştırır. ( syscall_tab[EAX]() ). Burada çalıştırılacak fonksiyon sys_read fonksiyonudur.

Bir sonraki aşamada dosyaya ilişkin parametreler ve izinler kontrol edilir, eğer herşey uygunsa dosyadaki veri alınır (kopyalanır). Sonuç uygulamanın istediği belleğe kopyalanır ve sonuca dair onay değeri uygulamaya gönderilir.

Bu aşamadan sonra işlem tekrar user mode da devam eder. Uygulama kontrol değerine bakar hatasız ise işlemine devam eder. Artık uygulama istediği verinin hatasız biçimde geldiğini ve hafızadaki yerini bilmektedir.

Bu yazıda sizlere bir uygulamanın çekirdekle olan iletişimini elimden geldiğince sade ve basit bir biçimde anlatmaya çalıştım.
Umarım faydalı olmuştur.


Serbülent ÜNSAL
serbulentunsal (et) meds.ktu.edu.tr

Not: Bu belgedeki bilgiler GNU/Linux sistemler için olmasına karşın genel olarak diğer pek çok sistem içinde doğrudur.

[1]:İşlemcilerdeki hafıza oruma mekanizması ile ilgili daha ayrıntılı bilgi için (http://www.cs.ucsb.edu/~cs170/notes/memory/)

Kaynaklar:

http://www.acikkod.org/yayingoster.php?id=30
http://www.linuxfocus.org/Turkce/January1998/article19.html
http://www.core.gen.tr/Security/Documents/12.html
http://www.freebsd.org/doc/en_US.ISO8859-1/books/design-44bsd/overview-kernel-service.html
http://plg.uwaterloo.ca/~itbowman/CS746G/a1/
http://www.linux-mag.com/2000-11/gear_01.html

Hiç yorum yok: