İşletim Sistemleri İşletim Sistemleri Dr. Binnur Kurt [email protected] Omega Eğitim ve Danışmanlık http://www.omegaegitim.com Bölüm 3 Prosesler 1|Sayfa İşletim Sistemleri İÇİNDEKİLER 1. İşletim Sistemi 2. Kabuk 3. Prosesler 4. İplikler 5. Prosesler Arası İletişim 6. İş Sıralama 7. Ölümcül Kilitlenme 8. Çok İplikli Programlama 9. Bellek Yönetimi 10. Dosya Sistemi 11. Soket Haberleşme Bölüm 3 Prosesler 2|Sayfa İşletim Sistemleri BÖLÜM 3 Prosesler Bölümün Amacı Bölüm sonunda aşağıdaki konular öğrenilmiş olacaktır: ► Proses ve prosesin durumları ► Linux’da proses yönetimi ► Prosesler arasında ebeveyn-çocuk ilişkisi ► Linux’da çok prosesli uygulama geliştirme Bölüm 3 Prosesler 3|Sayfa İşletim Sistemleri 3.1 Giriş İşletim sisteminin temel görevinin başta işlemci olmak üzere sistem kaynaklarını paylaştırmak olduğunu söylemiştik. İşletim sistemi tasarlanırken bir dizi genel tasarım hedefleri gözetilir. Bu isterlerin başında çok görevlilik gelir. İşletim sisteminin birden fazla görevi çalıştırması istenir. İşletim sistemi üzerinde çok sayıda uygulamanın çalışması istenir. Bu çalışan uygulamalar, işletim sisteminde, proses olarak adlandırılır. Burada işletim sistemi, tek ya da çok çekirdekli ya da işlemcili bir sistem üzerinde çalışıyor olabilir. Eğer tek işlemci yada çekirdek varsa aynı anda birden fazla proses çalışamaz. Çekirdek içinde proses sıralayıcı tarafından yönetilen bir önceliklendirilmiş kuyruk bulunur. Her proses belirli bir süre işlemciyi kullanır ve süresi dolduğunda işlemciyi terk eder. Bazen bir prosesin zamanı dolmasa da işlemciyi terk etmesi gerekebilir. Bu konuyu daha sonra daha detaylı inceleyeceğiz. Kuyruktaki proseslerden biri, işlemciyi bir sonra kullanacak proses olarak seçilir ve işlemci ona verilir. İşlemciyi terk eden proses bu önceliklendirilmiş kuyruktaki yerini alır ve sıra tekrar kendisine gelen kadar bekler. Böylelikle prosesler işlemciyi zamanda paylaşarak ve vakit buldukça çalışarak ellerindeki işleri tamamlamaya çalışırlar. Birden fazla çekirdek yada işlemcinin olduğu sistemlerde, çekirdek sayısı kadar kuyruk bulunur ve çekirdek sayısı kadar proses paralel olarak çalışabilir. 3.2 Proses Modeli Her bir uygulamanın çalıştırılması için bir prosesin yaratılması gerektirir. Ama bir uygulama çok prosesli olabilir. Bir proses bir ya da daha fazla çocuk proses yaratabilir. Böylelikle prosesler arasında ebeveyn–çocuk ilişkisi yaratılmış olunur. Proses yaratıldığında bellekte proses için yer ayrılır. Bunun dışında çekirdek her bir proses için proses ile ilgili bilgileri sakladığı bir tablo oluşturur. Bu tablo proses tablosu olarak adlandırılır. Prosesin bellek modeli bir işlemciden diğerine değişmekle birlikte genel olarak Şekil-3.1’de verilen bir modele sahiptir. Burada Yığın (=Stack) yerel değişkenlerin, fonksiyon çağrılarında parametre aktarımı, dönüş adresinin ve dönüş değerinin saklanması için kullanılır. Yığının çalışması otomatik olarak gerçekleşir. Yerel değişkenler erimi başladığı yerde otomatik olarak yığında yaratılır, erimi dışına çıkıldığında ise yine otomatik olarak yok edilir. Bu yüzden bu tür değişkenler bazen geçici ya da otomatik değişken olarak da adlandırılır. Heap, dinamik bellek kullanımı için kullanılır. Bu alanın yönetiminden programcı sorumludur. Programcı, C’de malloc() ve free() fonksiyonlarını, C++’da ise new ve delete operatörlerini kullanarak ihtiyacı kadar alanı alır ve kullanımı bitince ise aldığı alanı geri vermekle sorumludur. Bu söylemesi kolay ancak gerçeklemesi zor bir sorumluluktur. Bu nedenle C/C++ ile geliştirilen uygulamalarda dikkat edilmez ise bellek kaçağı oluşabilir. Bu özellikle servis tipi hiç sonlamayacak biçimde tasarlanan yazılımlar için büyük sorun oluşturur. Birkaç sekizlik bir kaçak, uzun dönemde yetersiz bellek hatası alınmasına neden olabilir. Text alanda fonksiyonlar yer alır. Data bölmesinde ise global değişkenler ve fonksiyonlarda static olarak tanımlanan değişkenler saklanır. Bölüm 3 Prosesler 4|Sayfa İşletim Sistemleri 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int x; static int b = 4; Stack foo() p main() { int y; static int z; y = b; z = foo(y); } foo( int p ) { int a; a = p + 2; return(a); } main() y a Heap Data Text x b main() {} z foo() {} Şekil-3.1 Proses Bellek Modeli Prosesler uygulamaların görevlerini kodlama için ağır sıklet elemanlardır. Bir proses fork() sistem çağrısını kullanarak çocuk proses yaratabilir. fork sistem çağrısı prosesin bellek görüntüsünün bire bir kopyasını çıkarır. Bu nedenle fork ile proses yaratmak maliyetlidir. Diğer bir maliyet bağlam anahtarlamada karşılaşılır. İşlemciyi kullanan proses süresi dolup işlemciyi terk ederken sıra tekrar kendisine geldiğinde kaldığı yerden devam edebilmesi için bağlamının saklanması gerekir. Bağlam, saklayıcılar, yığın göstergesi, program sayacı gibi işlemci içindeki elemanlardan oluşur. Bunlar bellekte saklanır. Böylelikle sıra tekrar kendisinde geldiğinde, prosesin bellekte saklanan bu bağlamı işlemciye geri yüklenerek, prosesin kaldığı yerden devam etmesi sağlanır. Prosesin bağlamı kalabalık olduğu için anahtarlama maliyeti de yüksektir. Bu yüzden çok prosesli yapıya farklı bir seçenek olarak çok lifli (=thread) yapılar kullanılabilir. Lifler proseslere göre hafif sıklet elemanlardır. Bir lif yaratıldığında onun için sadece yeni bir yığın yaratılır. Bir lif prosesin heap, text ve data alanlarını paylaşır. Her lifin ise kendi yığını vardır. Linux’da yaratılabilecek proses sayısının ve yığın büyüklüğünün bir değeri ve sınırı vardır. Bu sınırları ulimit komutunu kullanarak öğrenebilir ve yine bu komutu kullanarak değiştirebiliriz: [student@server1 ~]$ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 15781 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 1024 Bölüm 3 Prosesler 5|Sayfa İşletim Sistemleri pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 1024 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited Bu değerleri değiştirmek için komutun çıktısında listenen – ile başlayan parantez içinde yazılı olan seçenek kullanılır. Örneğin bir kullanıcının yaratabileceği proses sayısı yukarıdaki listeden 1024 olarak okuyoruz. Bunu 2048 olarak değiştirmek için ulimit –u 2048 komutunu çalıştırıyoruz: [student@server1 ~]$ ulimit -u 1024 [student@server1 ~]$ ulimit -u 2048 [student@server1 ~]$ ulimit -u 2048 Her bir lif yaratıldığında kendi yığını ile yaratılıyordu. Bu yığının boyutunu yukarıdaki komut çıktısından 8M olarak okuyoruz. Bunu 1M olarak değiştirmek için ulimit – s 1024 komutunu çalıştırıyoruz: [student@server1 ~]$ ulimit -s 8192 [student@server1 ~]$ ulimit -s 1024 [student@server1 ~]$ ulimit -s 1024 Linux’da uygulama geliştirmek için aşağıdaki modellerden biri kullanılır. i. Çok Prosesli Programlama Modeli ii. Tek Prosesli Çok Lifli Programlama Modeli iii. Çok Prosesli Çok Lifli Programlama Modeli Bu modellerin biri birlerine göre kazanım ve yitimleri vardır. Çok prosesli modelin yitimi proses yaratma ve prosesler arasında bağlam anahtarlama maliyetidir. Çok lifli programlamada lifler işlemciyi daha verimli kullanır. Üstelik lif yaratmak daha düşük maliyeti vardır. Buna karşılık liflerden biri başarısız olursa uygulama da başarısız olur ve sonlanması gerekir. Bu nedenle genellikle (iii) ile verilen modeli tercih edilir. Kritik görevler ayrı prosesler olarak kodlanır. Her bir proses liflerden oluşur. Örneğin Oracle veritabanı, chrome ve firefox web tarayıcıları bu modele göre gerçeklenmişlerdir. 3.3 Linux’da Proseslerle Çalışmak Sistemde çalışan tüm proseslerin listesini almak için ps komutunu –fe seçeneği ile birlikte kullanıyoruz: student@server1 ~]$ ps -fe | more UID root root root . . . root root Bölüm 3 Prosesler PID 1 2 3 PPID 0 0 2 17 18 2 2 C 0 0 0 STIME 10:22 10:22 10:22 TTY ? ? ? 0 10:22 ? 0 10:22 ? TIME 00:00:02 00:00:00 00:00:00 CMD /sbin/init [kthreadd] [ksoftirqd/0] 00:00:00 [watchdog/2] 00:00:00 [ksoftirqd/2] 6|Sayfa İşletim Sistemleri root . . . 19 2 0 10:22 ? 00:00:00 [migration/2] pgrep komutu ile prosesler arasında arama işlemi yapılabilir. Proseslerin bir kısmı arka planda çalışan proseslerdir. Bu proseslerin ismi d ile biter. Sonu d ile biten proseslerin listesini almak için pgrep’i kullanabiliriz: [student@server1 ~]$ pgrep -l "d$" 2 kthreadd 10 rcu_sched 32 kintegrityd 33 kblockd . . . 3493 gvfsd 3531 restorecond 3535 vmtoolsd Burada $ özel bir semboldür ve katarın sonunu ifade eder. Komutun çıktısında ilk sütun proses kimlik numarasını ve ikinci sütun ise prosesin adını göstermektedir. Prosesleri sonlandırmak için kill komutundan yararlanılır. kill komutu bir proses sinyal göndermek için kullanılır. Bir proses gönderilebilecek sinyallerin listesini almak için kill –l komutunu kullanıyoruz: Bu sinyallerden 9 (SIGKILL) sinyalini alan bir proses doğrudan sonlanır: [student@server1 ~]$ for i in 1 2 3 ; do gcalctool & done [1] 12358 [2] 12359 [3] 12360 [student@server1 ~]$ kill -9 12358 [student@server1 ~]$ kill -9 12359 [1] Killed gcalctool [student@server1 ~]$ kill -9 12360 [2]- Killed gcalctool [student@server1 ~]$ [3]+ Killed gcalctool Prosesleri proses ismi ile sonlandırmanın yolu pkill komutunu kullanmaktır: Bölüm 3 Prosesler 7|Sayfa İşletim Sistemleri [student@server1 ~]$ for i in 1 2 3 ; do gcalctool & done [1] 12374 [2] 12375 [3] 12376 [student@server1 ~]$ pkill gcalctool [student@server1 ~]$ [1] Terminated gcalctool [2]- Terminated gcalctool [3]+ Terminated gcalctool [student@server1 ~]$ Prosesler arasında kimlik numarası 1 olan proses özel bir prosestir. init proses olarak adlandırılır. Tüm başlangıç işlemlerinden ve yapılandırmadan sorumludur. Başlangıç işlerinin yapılandırılmasından sorumlu dosya ise /etc/inittab dosyasıdır. Burada çalışma düzeyleri (=run level) tanımları yer alır. Her bir servisin hangi çalışma düzeyinde çalışacağı ise chkconfig komutu ile listelenebilir ve düzenlenebilir. Aşağıdaki örnekte mysql servisinin çalışma düzeyleri listeleniyor ve daha sonra tüm düzeylerde kapatılıyor. [student@server1 ~]$ chkconfig --list mysql mysql 0:off 1:off 2:on 3:on 4:on 5:on 6:off [student@server1 ~]$chkconfig --level 2345 mysql off [student@server1 ~]$ chkconfig --list mysql mysql 0:off 1:off 2:off 3:off 4:off 5:off 6:off Çalışma düzeyleri arasında geçiş için ise init komutundan yararlanılır. Klasik Unix sistemlerinde 5 çalışma düzeyi yer alır. 1’den 5’e doğru yeni servisler açılır. 5’den 1’e doğru servisler kapatılır. 1 düzeyinde tüm bilgisayar ağı kapalıdır ve çekirdek tek kullanıcılı olarak çalışır. Sisteme saldırı olduğunda ya da yeni bir çekirdek kurulması gerektiğinde işletim sistemi 1 düzeyine init 1 komutu ile indirilir. 1 düzeyinden 3 düzeyine çıkmak için init 3 komutu çalıştırılır. init 0 sistemi kapatırken init 6 işletim sistemini yeniden başlatır. 2 düzeyinde sistem çok kullanıcılı olur ve ağ erişilebilir. 3 düzeyinde NFS yeteneği gelir. 5 düzeyinde ise X11 sunucusu çalışır. X11 sunucusu grafik arayüzün çalışmasını sağlar. 3.4 Linux’da Proses Yaratılması Linux’da proses yaratmak için fork sistem çağrısını kullanıyoruz. fork sistem çağrısı yapıldığında çekirdeğin yürüttüğü işlemler: Proses tablosunda yer ayırılır Çocuk prosese sistemde tekil yeni bir kimlik numarası atanır Anne prosesin bağlamının kopyası çıkarılır. Dosya erişimi ile ilgili sayaçları düzenlenir fork() sistem çağrısı, anneye çocuğun kimliğini, çocuğa da 0 değerini döndürür. fork() sistem çağrısı, eğer proses yaratılırken hata durumu oluşursa, -1 değerini döndürür. fork() sistem çağrısının kullanıldığı basit bir örnek Kod 3.1’de verilmiştir. Bölüm 3 Prosesler 8|Sayfa İşletim Sistemleri Kod 3.1: #include <unistd.h> #include <stdio.h> #include <stdlib.h> int f; int main (void){ printf("Program çalışıyor: Kimliğim= %d\n", getpid()); f=fork(); if (f==0) /*çocuk*/ { sleep(1); printf("\nBen çocuk. Kimliğim= %d", getpid()); printf("\nAnnemin kimliği= %d\n", getppid()); exit(0); } else if (f>0)/* anne */ { printf("\nBen anne. Kimliğim= %d", getpid()); printf("\nAnnemin kimliği= %d", getppid()); printf("\nÇocuğumun kimliği= %d\n", f); sleep(2); printf("\nBitti.\n"); exit(0); } return(0); } 3.5 Linux’da Proses Sonlandırılması Bazen bir proses elindeki işi bitirmeden sonlanması gerekir. Genellikle bu durum kritik bir hata oluştuğunda gerçekleştirilir, hata iletisini kayda alıp proses sonlandırılır. Bu gibi durumlarda exit, _exit, atexit ve abort çağrılarını kullanıyoruz: i. exit() stdio’nun tampon bellek alanlarını giriş/çıkış cihazlarına yazar ve uygulamayı _exit() çağrısı ile sonlandırır. #include <stdlib.h> void exit(int status); ii. _exit() Tüm açık dosyaları kapatır ve prosesi sonlandırır. Ebeveyn prosese SIGCHLD sinyali gönderir. Sinyaller prosesler arasında haberleşme için kullanılan tekniklerden biridir. Unix işletim sistemin bir prosese gönderilebilecek Bölüm 3 Prosesler 9|Sayfa İşletim Sistemleri proseslerin listesini almak için kill –l komutu kullanıldığını daha önce çalışmıştık. Burada her bir sinyalin önceden tanımlı bir anlamı bulunmaktadır. #include <stdlib.h> void _exit(int status); iii. atexit() Proses sonlanırken çalıştırılmak üzere bir fonksiyonu kaydeder. Bu fonksiyon genellikle alınan sistem kaynaklarını serbest bırakacak kodu içerir. Kaynağa örnek olarak veritabanı bağlantısı ve soket verilebilir. Kod 3.2’de atexit çağrısının kullanımına ilişkin örnek bir uygulama verilmiştir. #include <stdlib.h> int atexit(void (*func)(void)); iv. abort() Tüm dosyaları kapatır, prosesi sonlandırır ve hata ayıklamada kullanılmak üzere prosesin bellek dökümünü bir dosyaya alır. Bu dosya daha sonra bir hata ayıklama aracı ile açılarak, hatanın kaynağı bulunmaya çalışılır. Dolayısı ile abort çağrısı sadece hata ayıklama amacıyla kullanılmalıdır. #include <stdlib.h> void abort(void); Kod 3.2: #include <stdlib.h> #include <string.h> #include <unistd.h> void cleanup() { char *message = "cleanup invoked\n"; write(STDOUT_FILENO, message, strlen(message)); } main() { atexit(cleanup); exit(0); } 3.6 Proses Kimliğine Ulaşmak Bazen çalışmakta olan prosesin kimlik bilgisine erişmek gerekebilir. Bu durumda getpid, getppid, getpgrp ve getpgid fonksiyonları kullanılır. Şimdi bu fonksiyonların işlevlerine bir bakalım: 1. getpid() Çalışmakta olan prosesin kimlik numarasını verir. Bölüm 3 Prosesler 10 | S a y f a İşletim Sistemleri #include <stdlib.h> pid_t getpid(void); 2. getppid() Çalışmakta olan prosesin ebeveyn prosesinin kimlik numarasını verir. #include <stdlib.h> pid_t getppid(void); getpid ve getppid çağrılarının kullanıldığı basit bir örnek Kod-3.3’de verilmiştir. 3. getpgrp() Prosesin yer aldığı grubun grup numarasını verir. #include <sys/types.h> #include <unistd.h> pid_t getpgrp(void); 4. getpgid() Proses grubunun kimlik numarasını verir. #include <stdlib.h> pid_t getpgid(pid_t pid); int setpgid(pid_t pid, pid_t pgid); Komut satırında ls | sort | uniq komutu çalıştırılırsa, 3 proses yaratılır ve bu prosesler aralarında boru (=pipe) olarak adlandırılan özel bir veri yapısı üzerinden haberleşirler: [student@server1 ~]$ ls | sort | uniq Desktop Documents Downloads . . . Bu üç proses ls, sort ve uniq komutlarına karşılık olarak yaratılır. Bu üç proses bir grup oluşturur (Şekil-3.2). Bu grubun kimliğine ulaşmak için getpgid çağrısını kullanıyoruz. Örneğe baktığımızda ls prosesinin kimliği 108, sort prosesinin kimliği 549 ve uniq prosesinin kimliği 3615’dir. Proses grubunun kimliği ise 240’tır. ls| sort| uniq Proses grup 240 PID 108 549 3615 PGID 240 240 240 108 549 Grup lideri 3615 Şekil-3.2 Grup prosesler ve kimlik numaraları Bölüm 3 Prosesler 11 | S a y f a İşletim Sistemleri Kod 3.3: #include <unistd.h> #include <stdio.h> int main() { printf("My pid is: %d\n", getpid()); printf("My parent pid is: %d\n", getppid()); } 3.7 Bir Prosesin Sonlanmasını Beklemek Ebeveyn prosesin sonlanmadan önce gerçekleştirmesi gereken bir sorumluluğu bulunmaktadır. Yarattığı çocuk proseslerin sonlanmasını beklemelidir. İşletim sistemi çekirdeği yaratılan her bir proses ile ilgili bir kayıt tutmaktadır. Normalde bir proses sonlandığında bu kayıt otomatik olarak silinir. Ancak çocuk prosesin sonlanma durumlarını izlenebilmesi için çocuk proses sonlandığında proses tablosundan kaydı silinmez. Bu sorumluluk ebeveyn prosese aittir. Ebeveyn proses bu sorumluluğunu wait ve waitpid çağrıları ile yerine getirir. Eğer ebeveyn proses bu sorumluluğunu yerine getirmez ve çocuk prosesin sonlanmasından önce sona ererse, çocuk prosesle ilgli bilgiler proses tablosundan silinemez. Unix işletim sisteminde, bu durumdaki prosesler, zombi proses olarak adlandırılır. 1. wait() Herhangi bir çocuk proses sonlandığında, sonlanan çocuk prosesin kimlik numarası ile dönülür. #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc); 2. waitpid() waitpid çağrısında wait çağrısından farklı olarak belirli bir prosesin sonlanması beklenebilir. #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid,int *stat_loc,int options); Birinci parametrenin değerine farklı tercihler verilebilir: i. pid = -1 Herhangi bir çocuk proses sonlandığında waitpid çağrısından sonlanan çocuk prosesin kimlik numarası ile dönülür. ii. pid >0 Birinci parametrenin bu değeri sonlanması beklenen prosesin kimliğini tanımlar. iii. pid = 0 Bölüm 3 Prosesler 12 | S a y f a İşletim Sistemleri Çağıran prosesin bulunduğu proses grubundaki herhangi bir proses sonlandığında döner. iv. pid < 0 Birinci parametre olarak negatif bir sayı verildiğinde, bu sayının mutlak değerinin tanımladığı proses grubundan, herhangi bir proses sonlandığında, sonlanan prosesin kimlik numarası ile döner. waitpid çağrısı bloke çalışır. İstenilen proses sonlanana kadar çağrıyı yapan proses askıda kalır. Bu bazen istenmeyen durumdur. Üçüncü parametre olarak WNOHANG değeri verilirse, çağrının yapıldığı anda sonlanan herhangi bir proses yoksa, çağrıyı yapan proses çalışmaya devam eder, askıya alınmaz. Bu şekilde yoklamalı çalışılabilir. waitpid çağrısının kullanıldığı bir örnek Kod-3.4’de verilmiştir. Örnek uygulamada önce bir çocuk proses oluşturmakta ve ardından sonlanmasını beklemektedir. Kod 3.4: #include #include #include #include #include <sys/types.h> <sys/wait.h> <stdio.h> <unistd.h> <stdlib.h> int main() { pid_t pid; int status; /* fork() a child */ switch(pid = fork()) { case -1: perror("fork"); exit(1); /* in child */ case 0: execlp("ls", "ls", "-F", (char *)NULL); perror("execlp"); exit(1); /* parent */ default: break; } if (waitpid(pid, &status, 0) == -1) { perror("waitpid"); exit(1); Bölüm 3 Prosesler 13 | S a y f a İşletim Sistemleri } if (WIFSIGNALED(status)) { printf("ls terminated by signal %d.\n", WTERMSIG(status)); } else if (WIFEXITED(status)) { printf("ls exited with status %d.\n", WEXITSTATUS(status)); } else if (WIFSTOPPED(status)) { printf("ls stopped by signal %d.\n", WSTOPSIG(status)); } return 0; } Bölüm 3 Prosesler 14 | S a y f a
© Copyright 2024 Paperzz