|
![]() |
|
![]() tarafýndan Leonardo Giordani <leo.giordani(at)libero.it> Yazar hakkýnda: Yazar, Milan Politecnico Üniversitesinin Telekomunikasyon Mühendisliðinde
okumakta ve bilgisayar aðlarý yöneticisi olarak çalýþmaktadýr. Daha çok
Assemble ve C/C++ olmak üzere, programlamaya ilgi duymaktadýr. 1999 yýlýndan beri
neredeyse tamamen Linux/Unix ile çalýþmaktadýr.
|
Özet:
Bu yazý dizisinin amacý, okuyculara Linux iþletim sistemindeki çokluiþlem (multitasking) ve uygulamasýna giriþ oluþturmaktadýr. Çokluiþlem teorisinin temellerinden baþlayarak, iþlemler veya süreçler (processes) arasýndaki haberleþmeyi, basit ve bir o kadar da etkili bir protokol kullanarak gösteren, bir program yazarak konuyu tamamlayacaðýz. Yazýyý anlayabilmek için gerekli önbilgiler þunlardýr:
Birden fazla programý ayný anda çalýþtýrabilmek için iþletim sisteminin yapmasý gereken karmaþýk iþlemlere gereksinimi vardýr. Çalýþan programlar arasýnda oluþabilecek karýþýklýðý önlemek için, programlarýn çalýþtýrýlmalarý ile ilgili gerekli tüm bilginin bir yerde saklanmasý kaçýnýlmazdýr.
Linux bilgisayarýmýzda ne olup bittiðine geçmeden önce biraz genel kültür bilgisi verelim: çalýþan bir PROGRAM verildiðinde, programý oluþturan komutlar kümesine CODE(Program kodu), program verilerin bilgisayar belleðinde kapladýðý alana MEMORY SPACE(Bellek alaný) ve mikroiþlemcinin parametre deðerleri, çeþitli bayrak deðerleri veya program sayacýna (Bir sonra çalýþtýrýlacak olan komutun adres deðeri.), PROCESSOR STATUS (Ýlemci durumu) denilmektedir.
RUNNING PROGRAM (Çalýþan program) kavramýný, Program kodu, Bellek alaný ve Ýþlemci durumu nesnelerinden oluþan bir bütün olarak tanýmlýyoruz. Eðer, herhangi bir zaman diliminde, bu bilgiyi kaydedip, baþka programýn bilgileri ile deðiþtirdiðimizde, kaydettiðimiz programa tekrar dönüldüðünde, program kaldýðý yerden çalýþmasýna devam edebilmedir: Bu çalýþma yöntemi birden fazla programýn ayný anda ve birbirleri ile çakýþmadan çalýþýyor gibi görünmesini saðlamaktadýr. PROCESS veya TASK (Süreç veya iþlem) kelimesi böyle çalýþan bir programý tanýmlamak için kullanýlmaktadýr.
Þimdi, yazýnýn baþýnda sözünü ettiðimiz Linux bilgisayarýnda neler olduðuna bir göz atalým. Herhangi bir anda, bilgisayarda sadece bir adet iþlem çalýþtýrýlabilmektedir (Sistemde sadece bir mikroiþlemci vardýr ve bu iþlemci ayný anda iki iþlemi yerine getirememektedir.). Çalýþan programýn da saedece belli bir kýsmý iþlem görmektedir. Belli bir süre sonra, buna QUANTUM denmektedir, çalýþan iþlem durdurulmakta, daha sonra kaldýðý yerden devam etmek üzere iþlem ile ilgili bilgiler bir yerde ve beklemekte olan baþka bir iþlemin bilgileri ile deðiþtirilerek, bir quantum süresi boyunca çalýþtýrýlmaktadýr. Ayný anda çalýþýyor gibi izlenim veren iþlemler böylece çalýþtýrýlmaktadýr. Buna biz multitasking (çokluiþlem) diyoruz.
Daha önce de söylediðim gibi, çokluiþlem kullanýlýyor olmasý bazý sorunlarý da beraberinde getirmektedir. Sözgelimi, kuyrukta bekleyen iþlemlerim yönetimi (SCHEDULING). Herneyse, bunlar iþletim sisteminin yapýsý ile ilgili konular. Bu belkide baþka bir yazý yazýmý için uygun bir konu olabilir. Linux çekirdeðini tanýtýcý bir yazý yazýlabilir.
Bilgisayarýmýzda çalýþan iþlemler ile ilgili biraz bilgi edinelim. Bu bilgiyi saðlayan komutun adý ps(1) dir ve "process status" (iþlem durumu) sözcüklerin kýsaltmasýdýr. Bir terminal açýp, ps komutunu çalýþtrýrsanýz, aþaðýdakine benzer bir çýktý elde edersiniz.
PID TTY TIME CMD 2241 ttyp4 00:00:00 bash 2346 ttyp4 00:00:00 ps
Daha önce de belirtiðim gibi bu çýktý tam deðil, ancak þimdilik buna odaklanalým: ps komutu bize terminalde çalýþan iþlemlerin listesini vermektedir. Çýktýnýn son sütünunda, iþlemin çalýþtýrýldýðý komutun adýný görmekteyiz. Sözgelimi, Mozilla sanaldoku gezgini için mozilla, GNU derleyicisi için gcc vs. Çalýþmakta olan iþlemlerin listesi ekranda gösterilirken ps iþlemi de çalýþtýðýndan, kendisin de bu listede görünmesi çok doðal. Listede görünen diðer iþlemler þunlardýr: Benim terminalde çalýþan Bourne Again Shell (kabuk ortamý) dir.
Bir an için TIME ve TTY bilgilerini gözardý edelim ve PID (iþlem numarasý) bilgisine bakalým. Pid, her iþleme tekil olarak verilen, sýfýrdan farklý pozitif bir sayýdýr. Ýþlem sona erdiðinde, iþlem numarasý tekrar kullanýlabilir. Ancak, iþlem çalýþtýðý sürece, bu numaranýn deyiþmeyeceði garantilenmektedir. Sizin ps komutundan elde edeceðiniz çýktý, yukarýdakinden farklý olacaktýr. Söylediklerimin doðruluðunu sýnamak için, baþka bir terminal açýp orada ps komutunu çalýþtýrdýðýnýzda ayný listeyi elde edeceksiniz, ancak bu sefer iþlem numaralarý farklý olacaktýr. Bu da, çalýþan iþlemlerin birbirlerinden farklý olduklarýný göstermektedir.
Linux makinamýzda çalýþan tüm iþlemlerin listesini elde etmek de mümkündür. ps komutunun man sayfasýna göre, -e parametresi, tüm iþlemleri seçmek için kullanýlmaktadýr. Þimdi bir terminalde "ps -e" komutunu çalýþtýralým. Elde edeceðimiz liste, yukarýdaki biçimde, ancak çok daha uzun olacaktýr. Listeyi daha rahat bir þekilde incelebilmek için ps komutunun çýktýsýný ps.log dosyasýna yönlendirelim:
ps -e > ps.log
Oluþan dosyayý, tercih ettiðiniz herhangi bir kelime iþlemci veya en basitinden less komutu yardýmýyla inceleyebilirsiniz. Yazýnýn baþýnda da sözünü ettiðim gibi, çalýþan iþlem sayýsý, tahmin ettiðmizden daha fazladýr. Ayrýca, listede komut satýrýndan veya grafik ortamdan çalýþtýrdýðýmýz programlarýn dýþýnda da iþlemler çalþtýðýný fark ediyoruz. Bu iþlemlerden bazýlarýnýn isimleri de biraz garip. Sistemizde çalýþan iþlemler ve sayýlarý, sisteminizin yapýlandýrýlmasýna baðlýdýr. Ancak, bazý ortak iþlemler de vardýr. Sistemde yaptýðýnýz ayarlar ne olursa olsun, iþlem numarasý 1 ve tüm iþlemlerin babasý olan iþlemin adý hep "init" tir. Ýþlem numarasýnýn 1 olmasý, bu programýn iþletim sistemi tarafýndan hep ilk sýrada çalýþtýrýlmasýndandýr. Fark edeceðimiz baþka bir konu da, bazý isimlerin sonlarýnýn hep "d" karakteri olmasýdýr. Bunlara verilen genel isim "daemons" dur ve sistemin en önemli iþlemlerindendirler. init ve daemon larý daha sonraki bir yazýda ayrýntýlý olarak ele alacaðýz.
Ýþlem kavramýný ve onun iþletim sistemimiz için olan önemini kavradýðýmýza göre, daha ileriye giderek çokluiþlem yapan programlar yazmaya baþlayacaðýz. Aþýkar olan ayný anda birden fazla iþlemin birden çalýþtýrýlmasýndan, daha yeni bir problem olan, iþlemler arasý haberleþme ve zamanlamasýna (senkronizasyon) geçeceðiz. Bu problemi iki güzel yöntem olan mesajlar (messages) ve sayaçlar (semaphors) ile çözeceðiz. Bunlar süreçler (threads) konusunu iþleyeceðimiz sonraki bir yazýda ayrýntýlý olarak anlatýlacaktýr. Daha sonra da, tüm anlatýlan yöntemleri kullanarak, kendi uygulamalarýmýzý yazma zamaný gelecektir.
Standart C kütüphanesi (Linux,taki libc, glibc tarafýndan uyarlanmýþtýr.) Unix System V'in çokluiþlem yöntemlerini kullanmaktadýr. Unix System V (Bundan sonra SysV diyeceðiz.) ticari bir Unix uyarlamasý olup, BSD Unix ailesi gibi SysV Unix ailesinin de yaratýcýsýdýr.
libc'de iþlem numarasýnýn deðerini tutmak üzere, tamsayý (integer) þeklinde, pid_t deðiþken tipi tanýmlanmýþtýr. Bunadan böyle, pid_t deðiþken tipini iþlem numaralarý için kullanacaðýz. Sýrf daha basit olmasý açýsýndan tamsayý deðiþken tipini kullanmak da mümkündür.
Programýmýzýn iþlem numarasýný elde etmeye yarayan fonksiyona bir gözatalým.
pid_t getpid (void)
Bu fonksiyon, pid_tile birlikte unistd.h ve sys/types.h baþlýk dosyalarýnda tanýmlanmýþtýr. Þimdi, amacý kendi iþlem numarasýný bildiren bir program yazalým. Tercih ettiðiniz bir kelime iþlemcisi yardýmý ile aþaðýdaki programý yazýnýz.
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main() { pid_t pid; pid = getpid(); printf("The pid assigned to the process is %d\n", pid); return 0; }Programý print_pid.c adýnda kaydedin ve aþaðýdaki komutla derleyin:
gcc -Wall -o print_pid print_pid.cDerleme sonucunda, print_pid adlý çalýþtýrýlabilir bir program elde etmiþ olacaksýnýz. Eðer, bulunduðunuz dizin yoltanýmýnda yoksa, programý ancak "./print_pid" olarak çalýþtýrabilirsiniz. Programý çalýþtýrdýðýnýzda çok büyük bir sürpriz ile karþýlaþmayacaksýnýz: Ekrana pozitif bir sayý görüntileyecektir ve eðer birden fazla çalýþtýrýlýrsa da, sayýlar hep birbirinden farklý ve birer birer artan sayýlar olacaktýr. Ardýþýk sayýlar elde edilmesi herzaman mümkün olmayabilir. Bu zaten olmasý gereken bir durum da deðil. Çünkü, print_pid programýnýn ardýþýk çalýþtýrýlmasý arasýnda baþka iþlemler sistem tarafýndan çalýþtýrýlmýþ olabilir. Ardýþýk çalýþtýrma arasýnda sözgelimi, ps komutunu çaþtýrýn.
Þimdi bir iþlemin nasýl yaratýlacaðýný öðrenme zamaný geldi. Ancak, daha önce, bu iþlem sýrasýnda gerçekte neler olduðunu açýklamak gerek. Bir A iþlemi içerisinden B iþlemi yaratýldýðýnda, iki iþlem benzerdir, yani ikisi de ayný koda sahip, bellek alaný ayný veriler ile dolu (Bellek alaný ayný deðil.) ve ayný iþlemci durumuna sahiptir. Bu aþamadan sonra iþlemler iki farklý þekilde devam edebilir: Kullanýcýnýn vereceði giriþ bilgisine veya rastgele olan bir veriye göre. A iþlemine baba "father", B iþlemine de oðul (son) adý verilmektedir. init iþleminin tüm iþlemlerin babasý deyimi þimdi daha iyi anlaþýlýyor olmasý gerekir. Yeni iþlem yaratan fonksiyonun adý:
pid_t fork(void)dýr. Fonksiyonun dönüþ deðeri iþlem numarasýdýr. Daha önce de söylediðimiz gibi, iþlem kendisini baba ve oðul olarak, daha sonralarý farklý iþler yerine getirmek için, ikiye ayýrmaktadýr (kopyalamaktadýr). Ancak, bu iþlemden hemen sonra, hangisi önce çalýþacaktýr? Baba mý, oðul mu? Cevap çok basit: Ýkisinden biri. Hangi iþlemin çalýþtýrýlmasý gerektiði iþletim sistemin zamanlayýcý(scheduler) adý verilen bir kýsmý tarafýndan denetlenmektedir ve bu kýsým, iþlemin baba mý, oðul mu olup olmadýðýna bakmaksýzýn, baþka parametreleri göze alarak belli bir algoritmaya göre karar vermektedir.
Herneyse, ancak hangi iþlemin hangisi olduðunu bilmek önemlidir, çünkü programýn kodu aynýdýr. Her iki iþlem de hem baba iþlemin hem de oðul iþlemin kodunu içerecektir. Önemli olan herbirinin kendine düþen payý çalýþtýrmalarýdýr. Konuyu daha anlaþýlýr yapmak için, programlama ötesi bir dil gibi gözüken aþaðýdaki algoritmaya bir gözatalým:
- FORK - EÐER OÐUL ÝSEN BUNU ÇALIÞTIR (...) - EÐER BABA ÝSEN BUNU ÇALIÞTIR (...)fork fonksiyonu dönüþ deðeri olarak, oðul iþleme '0' iþlem numarasýný, baba iþleme de oðul iþlemin iþlem numarasýný üretmektedir. Dolayýsýyla dönüþ deðerine bakarak hangi iþlemde olduðumuzu öðrenmemiz mümkündür. C programlama dilindeki karþýlýðý aþaðýdaki gibi olacaktýr.
int main() { pid_t pid; pid = fork(); if (pid == 0) { OÐUL ÝÞLEMÝN KODU } BABA ÝÞLEMÝN KODU }Çokluiþlem olarak çalýþan gerçek bir program yazma zamaný geldi artýk. Aþaðýdaki, programý fork_demo.c dosyasý olarak kayýt edip, yukarýdaki komutlara benzer þekilde derleyebilirsiniz. Program kendi kopyasýný yaratacak ve hem baba ve hem de oðul iþlemler birþey yazacaktýr. Herþey yolunda giderse, elde edeceðiniz çýktý ikisinin bir karýþýmý olacaktýr.
(01) #include <unistd.h> (02) #include <sys/types.h> (03) #include <stdio.h> (04) int main() (05) { (05) pid_t pid; (06) int i; (07) pid = fork(); (08) if (pid == 0){ (09) for (i = 0; i < 8; i++){ (10) printf("-SON-\n"); (11) } (12) return(0); (13) } (14) for (i = 0; i < 8; i++){ (15) printf("+FATHER+\n"); (16) } (17) return(0); (18) }
(01)-(03) satýrlarý gerekli baþlýk kütüphaneleri (standart Giriþ/Çýkýs I/O,
çokluiþlem) programa dahil etmektedir.
GNU ortamlarýnda olduðu gibi, main programý bir tamsayýyý,
program hatasýz bir þekilde sona erdiðinde sýfýr, hatalý olarak sona erirse
hata numarasýný, dönüþ deðeri olarak üretmektedir.
Þu an için herþeyin hatasýz sonuçlanacaðýný varsayalým. Temel kavramlar
anlaþýldýkça programa, hata denetimleri de ekleyeceðiz.
(05) satýrýnda, iþlem numarasýný tutacak deðiþkeni tanýmlýyoruz.
(06) satýrda, döngülerde kullanacaðýmýz bir tamsayý deðiþkeni tanýmlýyoruz.
Daha öncede açýklandýðý gibi tamsayý ve pid_t tipleri eþdeðerdir. Biz
burada sýrf daha anlaþýlýr olsun diye böyle kullanýyoruz.
(07) satýrýnda, oðul iþlemine sýfýr ve baba iþlemine oðul iþlemin
iþlem numarasýný geri gönderecek olan fork fonksiyonunu çaðýrýyoruz.
Hangisinin hangisi olduðu denetim satýrý (08) dir.
(09)-(13) satýrlarý oðul, geriye kalan (14)-(16) satýrlarý da
baba iþleminde çalýþtýrýlacaktýr.
Hangi iþlemin çalýþtýðýna baðlý olarak, kýsýmlarýn yaptýðý tek
þey 8'er defa ekrana "-SON-" veya "+FATHER+" yazýp 0 deðeri ile
programý bitirmektedir. Bu çok önemlidir, çünkü "return" olmadan,
oðul iþleminin komutlarýný yerine getrdikten sonra programýn akýþý
gereði baba iþlemlerine geçerek devam edebilir. Ýsterseniz bir deneyin.
Bilgisayarýnýza zarar vermeyecektir, ancak istediðimizi de tama olarak
yerine getirmemiþ olacaktýr. Bu þekildeki hatalarýn ayýklanmasý
zor olmaktadýr, çünkü özellikle karmaþýk çokluiþlem programlarýn
çalýþtýrýlmasý sonucunda farklý sonuçlar elde edilecek
ve böylece hata ayýklanmasý imkansýzlaþacaktýr.
Programý çalýþtýrdýðýnýzda belkide sonuçtan memnun kalmayacaksýnýz. Nedenine gelince, elde edilen sonuçlarýn "+FATHER+" ve "-SON-" bir karýþýmýndan ziyade, önce "+FATHER+" satýrlarýnýn ardýndan "-SON-" gelecek veya tam tersi de olabilir. Ancak, programý birden fazla defa çalýþtýrýn, belki o zaman sonuç deðiþebilir.
Herbir printf'ten önce rastgele bekleme süresi eklersek, daha fazla çokluiþlem hissi elde etmiþ oluruz. Bunu yapabilmek için sleep ve rand fonksiyonlarýný kullanýyoruz.
sleep(rand()%4)Bu komut sayesinde program, 0 ile 3 arasýnda (% tamsayý bölmede kalan kýsmýný üretmektedir.) rastgele bir sayý kadar bekleme yapacaktýr. Þimdi program aþaðýdaki gibi deðiþmiþtir:
(09) for (i = 0; i < 8; i++){ (->) sleep (rand()%4); (10) printf("-FIGLIO-\n"); (11) }Ayný þeyi baba iþlemin program parçasý için de yapmak gerek. Deðiþtirilen programý fork_demo2.c adý altýnda saklayýn ve derledikten sonra bir çalýþtýrýn. Þimdi daha yavaþ çalýþmaktadýr, ancak çýktýdaki sýra farklýlýðýný daha iyi görme fýrsatýmýz oldu:
[leo@mobile ipc2]$ ./fork_demo2 -SON- +FATHER+ +FATHER+ -SON- -SON- +FATHER+ +FATHER+ -SON- -FIGLIO- +FATHER+ +FATHER+ -SON- -SON- -SON- +FATHER+ +FATHER+ [leo@mobile ipc2]$
Þimdi karþýmýza çýkacak problemlere bir göz gezdirelim: Çokluiþlem ortamýnda baba iþleminden farklý iþler yapabilecek birden fazla oðul iþlemi yaratmamýz olasýdýr. Doðru zamanda doðru iþi yapabilmek için, baba ilþlem, oðul iþlemler ile haberleþmek zorundadýr. En azýndan bir zaman ayarlanmasý (synchronize) yapýlmasý gerekmektedir. Böyle bir zaman ayarlanmasýný olasý kýlan ilk yöntem wait fonksiyonunu kullanmaktýr.
pid_t waitpid (pid_t PID, int *STATUS_PTR, int OPTIONS)Buradaki PID, sonuçlanmasýný beklediðimiz iþlemin iþlem numarasý, STATUS_PTR oðul iþlemin sonucunun saklandýðý tamsayý tipinde bir iþaretçi (Eðer, bilgi gerekmiyorsa, bu iþaretçinin deðeri NULL olmaktadýr.) ve OPTIONS çeþitli seçeneklerin seçilebileceði bir deðeri, ancak biz þimdilik onunla ilgilenmiyoruz, göstermektedir. Bu, baba iþlemin oðul iþlemin sona ermesini beklediði ve daha sonra programý sona erdirdiði bir örnektir.
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main() { pid_t pid; int i; pid = fork(); if (pid == 0){ for (i = 0; i < 14; i++){ sleep (rand()%4); printf("-SON-\n"); } return 0; } sleep (rand()%4); printf("+FATHER+ Waiting for son's termination...\n"); waitpid (pid, NULL, 0); printf("+FATHER+ ...ended\n"); return 0; }Baba iþleminin kýsmýna sleep fonksiyonu, farklý çalýþtýrma durumlarýný ortaya koymak için eklenmiþtir. Programý fork_demo3.c olarak kayýt edelim ve derledikten sonra da çalýþtýralým. Ýþte þimdi zaman ayarlamasý yapýlmýþ ilk programýmýzý yazmýþ olduk!
Bir sonraki yazýda zamanlama ayarlamasý ve iþlemler arasý iletiþim konusu hakkýnda daha fazla bilgi edineceðiz. Þu ana kadar anlatýlan fonksiyonlarý kullanarak kendi programlarýnýzý yazýn ve açýklamalarý ile birlikte .c dosyasý halinde bana gönderin. Böylece ben aralarýndan iyi olan çözümler ile kötüleri gösterme þansý elde etmiþ olurum. Adýnýzý ve e-posta adresinizi yazmayý unutmaýn. Ýyi çalýþmalar!
|
Görselyöre sayfalarýnýn bakýmý, LinuxFocus Editörleri tarafýndan yapýlmaktadýr
© Leonardo Giordani, FDL LinuxFocus.org |
Çeviri bilgisi:
|
2002-11-28, generated by lfparser version 2.31