Saturday 26 January 2008

LINUX Çekirdeğini Anlamak – Süreçler Arası İletişim 3

Ali Rıza SARAL(1)

(1) Daniel P. Bovet, Marco Cesati, Understanding the Linux Kernel ‘den faydalanarak derlenmiştir.


18.1.2 Boru Veri Yapıları (Pipe Data Structures)

Şimdi tekrar sistem çağrısı seviyesinde düşünmeğe başlamalıyız. Bir kere bir boru yaratıldı mı, bir süreç oku() (read( ) ve yaz() (write( )) Sanal Dosya sistemi (VFS) çağrılarını kullanarak ona erişir. Bu yüzden her boru için, çekirdek biri okumak diğeri yazmak için iki tane dosya nesnesi ve bir idüğüm (inode) nesnesi yaratır. Bir süreç bir boruya yazmağa veya onu okumağa başladığında uygun dosya tanımlayıcıyı kullanmalıdır.

Bir idüğüm (inode) nesnesi bir boruya değindiğinde, kendisinin u alanı bir boru_idüğüm_bilgi (pipe_inode_info yapısı)’ndan oluşur, Table 18-1’de görüldüğü gibi.


Tablo 18-1. boru_idüğüm_bilgi yapısı (The pipe_inode_info Structure)
Tip Alan Tanım
char * base Address of kernel buffer
unsigned int start Read position in kernel buffer
unsigned int lock Locking flag for exclusive access
struct wait_queue * wait Pipe/FIFO wait queue
unsigned int readers Flag for (or number of) reading processes
unsigned int writers Flag for (or number of) writing processes
unsigned int rd_openers Used while opening a FIFO for reading
unsigned int wr_openers Used while opening a FIFO for writing


Bir idüğüm ve iki dosya nesnesi dışında, her boru kendi boru arabölgesine (pipe buffer) ‘una sahiptir, yani boruya yazılmış ve hala okunuşu gereken tek bir sayfa çerçevesi (page frame). Bu sayfa çerçevesinin adresi boru_idüğüm_bilgi (pipe_inode_info) yapısının zemin (base) alanına depolanır. idüğüm (inode) nesnesinin i_büyüklük (i_size) alanı henüz okunuşu gerçekleşmemiş boru arabölgesine (buffer) yazılmış byte’ların sayısını depolar; aşağıda o sayıya şu andaki boru büyüklüğü (pipe size) diyoruz.

Boru arabölgesine hem yazan hem de okuyan süreçler tarafından erişilir, bu yüzden çekirdek arabölge içinde iki şuandaki konumun izini takip etmek zorundadır:
• boru_idüğüm_bilgisi (pipe_inode_info) yapısının başla (start) alanı içine depolanan okunacak ilk byte’ın göreli konumu
• başla (start) ve boru Büyüklüğü’nden çıkartılan yazılacak ilk byteın göreli konumu

Boru’nun veri yapıları üzerinde yarışım koşullarından sakınmak için, çekirdek bore arabölgesine eşzamanlı (concurrent) erişimleri yasaklar. Bunu başarmak için, boru_idüğüm_bilgisi (pipe_inode_info veri yapısındaki kilit (lock) alanından faydalanır. Maalesef, kilit (lock) alanı yeterli değildir. Göreceğimiz gibi, POSIX bazı boru işlemlerinin atomik lmasını zorunlu kılar. Dahası, POSIX okuyucuların arabölgeyi boşaltabilmeleri için, boru dolu olduğu zaman yazıcı süreçin askıya alınışına müsaade eder. Bu şartlar, idüğüm nesnesi içinde bulunan ek bir i_atomik_yaz (i_atomic_write) semaforunu kullanarak sağlanır: bu semafor bir başka yazıcı arabölge dolu olduğundan askıya alınmışken bir sürecin yeni bir yazıcı işlem başlatışını engeller.

18.1.3 Bir Boru Yaratmak ve Yok Etmek (Creating and Destroying a Pipe)
Bir boru disk görüntüsü olmayan bir küme SanalDosyaSistemi (VFS) nesnesi olarak gerçekleştirilir. Aşağıdaki tartışmada göreceğimiz gibi, bir boru, en azından bir süreç ona değinen bir dosya tanımlayıcıya sahip olduğu sürece, sistem içinde kalır.

boru() (pipe( )) sistem çağrısına, boru_yap() (do_pipe( )) fonksiyonunu çağıran sis_boru() (sys_pipe( )) fonksiyonu tarafından hizmet edilir. Yeni bir boru yaratmak için, boru_yap() aşağıdaki işlemleri yapar:
1. Borunun okuyucu kanalı için bir dosya nesnesi ve dosya tanımlayıcı ayırır, dosya nesnesinin bayrak (flag) alanını O_YALNIZOKU (O_RDONLY) ’ya ayarlar, ve d_işle (f_op) alanını the roku_boru_dişlem (read_ pipe_fops) tablosunun adresi ile başlangıç-koşullar.
2. Borunun yazıcı kanalı için bir dosya nesnesi ve dosya tanımlayıcı ayırır, dosya nesnesinin bayrak (flag) alanını O_YALNIZYAZ (O_WRONLY) ’ya ayarlar, ve d_işle (f_op) alanını the roku_boru_dişlem (write_ pipe_fops) tablosunun adresi ile başlangıç-koşullar.
3. Boru için bir idüğüm (inode) nesnesini başlangıç koşullayan boru_idüğümünü_edin (get_ pipe_inode( )) fonksiyonunu başlatır. Bu fonksiyon boru arabölgesi için aynı zamanda bir sayfa çerçevesi ayırır ve onun adresini pipe_inode_info yapısının base alanına depolar.
4. dentry nesnesini ayırır ve onu iki dosya nesnesini ve idüğüm (inode) nesnesini birbirine ilişkilendirmek için kullanır.
5. Kullanıcı Üslubundaki sürece iki dosya tanımlayıcıyı geri döndürür.

Yeni bir boruya yazmak veya okumak için erişebilen tek süreç bir boru() (pipe( )) sistem
çağrısı çıkartan süreçtir. Borunun bir okuyucu ve bir yazıcıya gerçekten sahip olduğunu temsil etmek için boru_idüğüm_bilgisi (pipe_inode_info) veri yapısının okuyucular (readers) ve yazıcılar (writers) alanları 1 başlangıç değerlerini alır. Genel olarak, eğer karşı düşen borunun dosya nesnesi hala açık ise bu her iki alana 1 değeri verilir; Eğer karşı düşen dosya nesnesi serbest bırakılmışsa bu alana 0 değeri verilir, çünkü artık başka hiçbir süreçtarafından erişilmez. Yeni bir süreci çatallamak (forking) okuyucular (readers) ve yazıcılar (writers) alanlarının değerlerini arttırmaz, dolayısıyla değerleri hiçbir zaman 1 ‘in üstüne çıkmaz; yine de hala ebeveyn süreç tarafından kullanılan bütün dosya nesnelerinin kullanım sayaçlarının değerlerini arttırır. Böylece, ebeveyn ölse bile nesneler serbest bırakılmaz, ve boru çocukların kullanışı için açık kalacaktır. FIFO’larla ilişkilendirildiklerinde okuyucular (readers) ve yazıcılar (writers) alanları bayrak yerine sayaç olarak kullanılırlar.

Ne zaman bir süreç, bir boru ile alakalı bir dosya tanımlayıcı üzerinde bir kapa() (close( )) sistem çağrısı başlatırsa, çekirdek kullanım sayacını azaltan dkoy() fput( ) fonksiyonunu karşı düşen nesne üzerinde çalıştırır. Eğer sayaç 0 olursa, fonksiyon dosya işlemlerinin serbestbırak (release) metodunu çalıştırır.

Boru_okuyuş_sal() (pipe_read_release( )) ve boru_yazış_sal() (pipe_write_release( )) fonksiyonlarının her ikisi de borunun dosya nesnelerinin serbestbırak (release) metodlarını gerçekleştirilişi için kullanılırlar. boru_idüğüm_bilgisi (pipe_inode_info) veri yapısının okuyucular (readers) ve yazıcılar (writers) alanlarına değer verirler. Daha sonra her fonksiyon boru_serbestbirak() (pipe_release( )) fonksiyonunu başlatır. Bu fonksiyon borunun durumundaki değişiklikten farkına varışları için borunun bekleyiş kuyruğundaki herbir süreci uyandırır. Dahası, fonksiyon okuyucular (readers) ve yazıcılar (writers) alanlarının 0 olup olmadıklarını sınar; eğer böyle ise, boru arabölgesini içeren sayfa çerçevesini serbest bırakır.

Tuesday 15 January 2008

LINUX Çekirdeğini Anlamak – Süreçler Arası İletişim 2

Ali Rıza SARAL(1)


(1) Daniel P. Bovet, Marco Cesati, Understanding the Linux Kernel ‘den faydalanarak derlenmiştir.


18.1 Borular (Pipes)
Borular Unix’in bütün tat-türlerinde (flavor) bulunan bir süreçler arası iletişim mekanizmasıdır. Bir boru süreçler arasında tek yönlü veri akışıdır: bir süreç tarafından boruya yazılan verinin tamamı onu okuyan başka bir sürece çekirdek tarafından yönlendirilir.

Unix komut kabuklarında, borular operatörü ile yaratılır. Örneğin, aşağıdaki ifade kabuğa bir boru ile bağlı iki süreç yaratmağı emreder:

$ ls more

ls programını çalıştıran, birinci sürecin standart çıkışı boruya yönlendirilir; more programını çalıştıran ikinci süreç girdisini borudan okur. Aynı sonuçların iki ayrı komutu çalıştırarak elde edilebileceğini aklınızda tutunuz:

$ ls > temp
$ more < temp

İlk komut ls’in çıktısını alışılagelmiş bir dosyaya yöneltir; daha sonra ikinci komut more ‘u girişini aynı dosyadan okumağa zorlar. Tabii ki, geçici dosyalar yerine boruları kullanmak daha kullanışlı çünkü:
• Kabuk ifadesi çok daha kısa ve basit.
• Sonradan silinişi gereken geçici alışılagelmiş dosyalar yaratmağa ihtiyaç yok.

18.1.1 Using a Pipe
Borular bindirilmiş (mounted) dosya sistemlerinde ilgili hiçbir görüntüsü (image) olmayan
açık dosyalar olarak değerlendirilebilir. Yeni bir boru, bir çift dosya tanımlayıcıyı (file descriptor) geri döndüren boru() (pipe( ) ) sistem çağrısı ile yaratılabilir. Süreç birinci dosya tanımlayıcı ile oku() (read( )) sistem çağrısını kullanarak borudan okuyabilir; benzer şekilde, ikinci dosya tanımlayıcı ile boru() (pipe( ) ) sistem çağrısını kullanarak boruya yazabilir.

POSIX yalnız yarı-karşılıklı (half-duplex) boruları tanımlar, bu yüzden boru() (pipe( ) ) sistem çağrısı iki dosya tanımlayıcıyı geri döndürse bile, her süreç kullanmadan önce bunlardan birini kapatmalıdır. Eğer iki yönlü veri akışı gerekiyorsa, süreçler boru() (pipe( ) ) ‘yu iki defa başlatarak iki farklı boru kullanmalılar.

Sistem V Sürüm 4 gibi, çok sayıda Unix sistemi, tam-çift-yönlü borular gerçekleştirirler ve her iki tanımlayıcının da içine yazılışına ya da okunuşuna izin verirler.
Linux bir başka yaklaşımı benimser: herbir borunun dosya tanımlayıcıları hala tek-yönlüdür, fakat diğerini kullanmadan önce birini kapamak gerekli değildir

Geçtiğimiz örneği kaldığımız yerden ele alalım: komut kabuğu lsmore ifadesini yorumladığında, esas olarak aşağıdaki faaliyetleri yapar:
1. boru() (pipe( ) ) sistem çağrısını çalıştırır; boru() ‘nun 3 nolu dosya tanımlayıcısı(borunun okuyucu kanalı (read channel)) ve 4 noluyu (yazıcı kanalı) döndürdüğünü kabul edelim.
2. çatal() (fork( )) sistem çağrısını iki defa başlatır.
3. 3 ve 4 nolu dosya tanımlayıcıları serbest bırakmak için kapa() (close() ) sistem çağrısını iki defa başlatır.

ls programını çalıştırışı gereken birinci çocuk süreç aşağıdaki işlemleri icra eder:
1. dosya tanımlayıcı 4’ü dosya tanımlayıcıya kopyalamak için çiftle2(4,1) (dup2(4,1)) ‘yi başlatır. Bu andan itibaren dosya tanımlayıcı 1 borunun yazıcı kanalını belirtir.
2. Dosya tanımlayıcı 3 ve 4’ü serbest bırakmak için kapa() (close() ) sistem çağrısını iki kez başlatır.
3. /bin/ls programını çalıştırmak için çalıştırve() (execve( )) sistem çağrısını başlatır. Böyle bir program hiç yoktan(default) çıkışını dosya tanımlayıcı 1’e sahip olan dosyaya yazar (standart çıkış), yani, boruya yazar.

İkinci çocuk süreç more programını çalıştırmak zorunda; buyüzden, aşağıdaki işlemleri icra eder:
1. Dosya tanımlayıcı 3’ü dosya tanımlayıcı 0’a kopyalamak için çiftle2(3,0) (dup2(3,0)) ‘ü başlatır. Bu andan itibaren, dosya tanımlayıcı boru’nun okuyucu kanalını belirtir.
2. Dosya tanımlayıcı 3 ve 4’ü serbest bırakmak için kapa() (close() ) sistem çağrısını iki kez başlatır.
3. /bin/more ‘u çalıştırmak için çalıştırve() (execve( )) sistem çağrısını başlatır. Hiç yoktan, program girişini dosya tanımlayıcıya(standart giriş) sahip olan dosyadan okur; yani, borudan okur.

Bu basit örnekte, boru yalnızca iki süreç tarafından kullanıldı. Gerçekleştiriliş şeklinden dolayı, bir boru istenen sayıda süreç tarafından kullanılabilir. Açıkçası, eğer iki veya daha çok süreç aynı boruya yazar veya okursalar, erişimlerini açık ve doğrudan bir şekilde (explicitly) dosya kilitleyerek (file locking) eşzamanlı kılmalıdırlar veya süreçler arası iletişim (IPC) semaforları kullanmalıdırlar…

Çok sayıda Unix sistemi, boru() (pipe())sistem çağrısından başka, boruları kullanırken genellikle yapılan bütün kirli işleri becermek için kullanılan, paç() (popen( )) ve pkapa (pclose( )) adı verilen iki paketleyici (wrapper) fonksiyon kullanırlar. paç() (popen( )) fonksiyonunu kullanarak bir boru bir kere yaratıldı mı C kütüphanesine dahil edilmiş (fprintf( ), fscanf( ), ve diğer) üst seviye G/Ç fonksiyonları (the high-level I/O functions) ile birlikte kullanılabilirler.

Linux’ta paç() (popen( )) ve pkapa (pclose( )) C kütüphanesine dahil edilmiştir. paç() (popen( )) fonksiyonu iki başlangıç değişkeni(parameter) alır: çalıştırılabilir bir dosyanın dosyaisimi (filename) patika-ismi ve veri naklinin yönünü belirleyen bir tip (type) karakter zinciri. Bir DOSYA (FILE) veri yapısına işaret-ediciyi geri döndürür. paç() (popen( )) fonksiyonu esas olarak aşağıdaki işlemleri icra eder:
1. boru() (pipe())sistem çağrısını kullanarak yeni bir boru yaratır.
2. Kendine sıra gelince aşağıdaki işlemleri çalıştıran, yeni bir süreç çatallar(fork).
a. Eğer tip r ise, borunun yazıcı kanalı ile dosya tanımlayıcı 1 (standart çıkış) olarak ilişkilendirilen dosya tanımlayıcıyı çiftler; aksi takdirde, eğer tip w ise, borunun okuyucu kanalı ile dosya tanımlayıcı (standart giriş) olarak ilişkilendirilen dosya tanımlayıcıyı çiftler.
b. boru() (pipe()) tarafından geri döndürülen dosya tanımlayıcıları kapatır.
c. filename tarafından belirlenen programı çalıştırmak için çalıştırve() (execve( )) sistem çağrısını başlatır.
3. Eğer tip r ise, borunun yazıcı kanalı ile ilişkili dosya tanımlayıcıyı kapatır; aksi takdirde, eğer tip w ise, borunun okuyucu kanalı ile ilişkili dosya tanımlayıcıyı kapatır.
4. Boru için hangi dosya tanımlayıcı açık ise onu belirten DOSYA (FILE) dosya işaret-edicisinin adresini geri döndürür.

paç() (popen( )) başlatılışından sonra, ebeveyn ve çocuk boru içinden bilgi değiş-tokuşu yapabilirler: ebeveyn fonksiyon tarafından geri döndürülen DOSYA (FILE) işaret-edicisini kullanarak verileri ( tip r ise) okuyabilir veya (tip w ise) yazabilir. Veri çocuk süreç tarafından çalıştırılan program tarafından standart çıkışa yazılır ya da standart girişten okunur.

paç() (popen( )) tarafından geri-döndürülen dosya işaret-edicisini alan pkapa (pclose( )) fonksiyonu, yalnızca bekle4() (wait4( )) sistem çağrısını başlatır ve paç() (popen( )) tarafından yaratılmış sürecin sona erişini bekler.

Thursday 3 January 2008

LINUX Çekirdeğini Anlamak – Süreçler Arası İletişim 1

Ali Rıza SARAL(1)


(1) Daniel P. Bovet, Marco Cesati, Understanding the Linux Kernel ‘den faydalanarak derlenmiştir.


Bölüm 18. Süreç İletişimi

Kullanıcı üslubu süreçleri kendilerini eşzamanlı kılmak ve veri değiş-tokuşu için çekirdeğe dayanmak zorundadırlar

Kulllanıcı üslubu süreçleri arasında kaba bir eşzamanlılık boş bir dosya yaratıp uygun VFS sistem çağrıları kullanarak onu kilitlemek(lock) ve açmak (unlock) şeklinde sağlanabilir.
Benzer şekilde, süreçler arasında veri paylaşımı veriyi kilitlerle korunmuş geçici dosyalara koyarak elde edilebilir. Bu yaklaşım disk dosya sistemine erişim gerektirdiğinden pahalıdır. Bu yüzden, bütün Unix çekirdekleri dosya sistemi ile etkileşmeyen ama süreç iletişimini destekleyen bir sistem çağrısı kümesi içerir. Dahası, süreçlerin eşzamanlılık isteklerini çekirdeğe nasıl göndereceklerini yönlendirmek için çok sayıda paketleyici (wrapper) fonksiyon geliştirilmiş ve uygun kütüphanelere eklenmiştir.

Unix sistemlerinin ve özellikle Linux’un süreçler arası iletişim için sunduğu imkanlar aşağıdadır.

BORU ve İLKGELENİLKÇIKAR(İsimli Borular) (Pipes and FIFOs (named pipes))
Süreçler arası üretici/tüketici etkileşiminin gerçekleştirilişine en uygun olanı. Bazı süreçler boruyu veri ile doldururken diğerleri borudan veri çıkartır. Best suited to implement producer/consumer interactions among processes. Some processes fill the pipe with data while others extract data from the pipe.

Semaforlar (Semaphores)
Çekirdek semaforlarının Kullanıcı Üslubu sürümü. Represents, as the name implies, the User Mode version of the kernel semaphores.

Mesajlar (Messages)
Süreçlerin mesajların eşzamanlı olmayan şekilde (kısa veri blokları) değiştirimine izin verir.

Ortak Bellek Bölgeleri (Shared memory regions)
Süreçlerin büyük miktarlarda veriyi verimli bir şekilde paylaşmalarının gerektiği etkileşim modellerine en uygunudur.

Soketler başlangıçta uygulama programları ile ağ arayüzü arasında veri iletişimine imkan sağlamak için sunulmuşlardı. Aynı evsahibi (host) bilgisayar üzerinde yer alan süreçler arasında iletişim aracı olarak ta kullanılabilirler; X Pencereler Sisteminin grafik arayüzü, örneğin, müşteri (client) programlarının X sunucusu (server) ile veri değişimine imkan sağlamak için bir soket kullanır.