Friday 28 December 2007

LINUX Programlamağa Giriş – Süreçler 6

Ali Rıza SARAL(1)


(1) Neil Matthew ve Richard Stones, Beginning Linux Programming ‘den faydalanarak derlenmiştir.


Hortlak Süreçler (Zombies)
Süreçler yaratmak için çatal (fork) ‘ı kullanmak çok kullanışlı olabilir, fakat çocuk süreçlerin izini takip etmek zorundasınız. Bir çocuk süreç sona erdiğinde, ebeveyni ile ilişkisi ebeveyn kendi sırası ile sona erince veya bekle (wait) ‘yi çağırıncaya kadar canlılığını sürdürür.

Süreç tablosundaki çocuk süreç hanesi bu yüzden hemen serbest bırakılmaz. Artık canlı olmasa da, çocuk süreç hala sistem içindedir çünkü ebeveyn sonradan bekle (wait) ‘yi çağırırsa diye, çıkış kodunu saklamak gerekir. O işe yaramayan, veya hortlak süreç (zombie) olarak bilinir.

Fork örnek programında mesajların sayısını değiştirirsek hortlak süreçlerin yaratıldığını görebiliriz. Eğer çocuk ebeveyninden daha az sayıda mesaj basarsa, önce biter ve ebeveyni bitinceye kadar bir hortlak olarak var olur.

Örnek: Hortlaklar
Fork2.c fork1.c ile çocuk ve ebeveyn süreçlerin bastığı mesaj sayılarının değiş tokuş edilişi dışında aynıdır. İlgili kod satırları aşağıdadır:

switch(pid)
{
case -1:
perror(“fork failed”);
exit(1);
case 0:
message = “This is the child”;
n = 3;
break;
default:
message = “This is the parent”;
n = 5;
break;
}

Eğer bundan önceki programı ./fork2 & ile çalıştırır ve sonra ps programını çocuk bittikten sonra fakat ebevyn bitmeden çalıştırırsak aşağıdaki çıktıyı görürüz (Bazı sistemler işlevsiz (defunct) yerine hortlak (zombie) diyebilir.)

$ ps –al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
004 S 0 1273 1259 0 75 0 - 589 wait4 pts/2 00:00:00 su
000 S 0 1274 1273 0 75 0 - 731 schedu pts/2 00:00:00 bash
000 S 500 1463 1262 0 75 0 - 788 schedu pts/1 00:00:00 oclock
000 S 500 1465 1262 0 75 0 - 2569 schedu pts/1 00:00:01 emacs
000 S 500 1603 1262 0 75 0 - 313 schedu pts/1 00:00:00 fork2
003 Z 500 1604 1603 0 75 0 - 0 do_exi pts/1 00:00:00 fork2
000 R 500 1605 1262 0 81 0 - 781 - pts/1 00:00:00 ps

Eğer ebeveyn o zaman anormal şekilde sona ererse, çocuk süreç SüreçKimlikNo’su 1 olan süreci sıfırdan-başla (init) ebeveyn olarak alır. Çocuk süreç şimdi artık çalışmayan bir hayalettir ama ebeveyninin anormal sona erişi yüzünden sıfırdan-başla (init) tarafından miras alınmıştır.

Hortlak sıfırdan-basla tarafından toplanıncaya kadar süreç tablosunda kalacaktır. Tablo ne kadar büyük olursa bu süreç o kadar yavaş olur. Sıfırdan-başla onları temizleyinceye kadar sistem kaynaklarını kullanan hortlak süreçlerden kaçınmanız gerekir.

Çocuk süreçleri çağırmak için kullanabileceğiniz bir başka sistem çağrısı var. Buna SüreçKimlikNosunuBekle (waitpid) adı verilir, ve belirli bir sürecin sona erişini beklemek için bunu kullanabilirsiniz.

#include
#include
pid_t waitpid(pid_t pid, int *stat_loc, int options);

pid başlangıç değişkeni beklenecek belirli bir çocuk sürecin Süreç Kimlik No’sunu belirler. Eğer -1 ise, herhangi bir çocuk süreç için geçerli bilgileri getirir. Bekle (wait) gibi, eğer stat_loc boş işaretedici değil ise, onun belirttiği konuma durum bilgisini yazar.

Options başlangıç değişkeni waitpid’nin davranışını değiştirişimize izin verir. En kullanışlı seçenek, SüreçKimlikNosunuBekle (waitpid) tarafından, çağıranın çalışışının askıya alınışını engelleyen WNOHANG ‘dır. Bunu herhangi bir çocuk sürecin sona erip ermediğini bulmak, ve eğer değilse devam etmek için kullanabilirsiniz. Diğer seçenekler bekle (wait) ile aynıdır.

Dolayısıyla, eğer bir ebeveyn süreç tarafından düzenli olarak belirli bir çocuk sürecin sona erip ermediğini sınamasını isterseniz aşağıdaki çağrıyı kullanabilirsiniz:

waitpid(child_pid, (int *) 0, WNOHANG);

Eğer çocuk sona ermiş ya da durmuş ise bu komut 0 döndürür, veya eğer var ise child_pid. SüreçKimlikNosunuBekle (waitpid) hata durumunda -1 döndürür ve hatanosu (errno) ’ya uygun değer verir. Bu eğer hiçbir çocuk süreç yok ise (hatanosu (errno) ECHILD değerli) olur, eğer çağrı bir işaret (signal(EINTR)) ile kesilirse veya seçenek giriş değişkeni geçersizse (EINVAL) ).

Giriş ve Çıkış Yeniden Yönlendiriş (Input Output Redirection)
Süreçler hakkındaki bilgimizi faydalanarak açık dosya tanımlayıcıların çatal (fork) ve çalıştır (exec) komutları içinden geçerek korunduğu gerçeğini kullanacağız.

Örnek: Redirection
Standart girişini okur ve kullanışlı bir dönüşüm uygulayıp standart çıkışına yazar.
upper.c, girişi okuyup büyük harfe çevirir.

#include
#include
int main()
{
int ch;
while((ch = getchar()) != EOF) {
putchar(toupper(ch));
}
exit(0);
}

Programı çalıştırınca, beklediğimizi yapar:

$ ./upper
hello THERE
HELLO THERE
^D
$

Tabii ki, onu, kabuk yeniden yönlendirişini kullanarak, bir dosyayı büyük harfe çevirmek için kullanabiliriz.

$ cat file.txt
this is the file, file.txt, it is all lower case.
$ ./upper < file.txt
THIS IS THE FILE, FILE.TXT, IT IS ALL LOWER CASE.

Eğer bu filtreyi bir program içinden kullanmak istersek ne olur? Bu program, upper.c ‘yi kullanır, bir dosya ismini başlatış değişkeni olarak alır ve eğer yanlış çağırılırsa bir hata ile yanıt verir.

#include
#include
int main(int argc, char *argv[])
{
char *filename;
if (argc != 2) {
fprintf(stderr, “usage: useupper file\n”);
exit(1);
}
filename = argv[1];

Standart tekrar açarız, bunu yaparken tekrar herhangi bir hata olup olmadığını kontrol ederiz, ve upper ‘ı çağırırken execl ‘i kullanırız.

if(!freopen(filename, “r”, stdin)) {
fprintf(stderr, “could not redirect stdin from file %s\n”, filename);
exit(2);
}
execl(“./upper”, “upper”, 0);

execl ‘in şu andaki sürecin yerine geçtiğini unutmayınız; eğer bir hata var ise, kalan satırlar çalıştırılmaz.

perror(“could not exec ./upper”);
exit(3);
}