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);
}

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

Ali Rıza SARAL(1)


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


Bir Süreci Bekleyiş
çatal (fork) ile bir çocuk süreç başlattığımızda, çocuk hayatını kendi ellerine alır ve bağımsız olarak koşar. Bazan, bir çocuk sürecin ne zaman bittiğini bulmak isteriz. Örn., bundan önceki programda ebeveyn çocuktan önce biter ve çocuk devam ederken kirli bir çıktı alırız. Çocuk bitinceye kadar ebeveyn sürecini bekleyişini bekle (wait) ‘yi çağırarak düzenleyebiliriz.

#include
#include
pid_t wait(int *stat_loc);

bekle sistem çağrısı bir ebeveyn sürecin, çocuk süreci duruncaya kadar duraklayışına neden olur. Çağrı çocuk sürecin SüreçKimlikNo’sunu (PID) geri döndürür. Bu normalde sona ermiş olan bir çocuk süreçtir. Durum bilgisi (status information) ebeveyn sürece çocuk sürecin çıkış durumunu belirleyiş imkanı sağlar, yani, anagövde (main) tarafından döndürülen değer veya çıkışa (exit) iletilen değer.

Eğer stat_loc bir boş işaretedici (null pointer) değilse, durum bilgisi işaret ettiği yere yazılacaktır. Durum bilgisini sys/wait.h ‘da tanımlanan makroları kullanarak tercüme edebiliriz. Bunlar

Macro Definition
WIFEXITED(stat_val) Çocuk normal bittiyse sıfır değil.
WEXITSTATUS(stat_val) Eğer WIFEXITED sıfır değilse, bu çocuk çıkış kodunu döndürür. WIFSIGNALED(stat_val) Eğer çocuk bir yakalanmadı (uncaught) işareti ile sona erdiyse sıfır değil.
WTERMSIG(stat_val) Eğer WIFSIGNALED sıfır değilse, bu bir işaret sayısı döndürür.
WIFSTOPPED(stat_val) Sıfır değilse çocuk durdu.
WSTOPSIG(stat_val) Eğer WIFSTOPPED sıfır değilse, bu bir işaret sayısı döndürür.
Örnek:
Programı çocuk süreci bekleyip onun bitiş durumunu yoklayabilecek şekilde değiştirelim. Wait.c

#include
#include
#include
#include
int main()
{
pid_t pid;
char *message;
int n;
int exit_code;
printf(“fork program starting\n”);
pid = fork();
switch(pid)
{
case -1:
perror(“fork failed”);
exit(1);
case 0:
message = “This is the child”;
n = 5;
exit_code = 37;
break;
default:
message = “This is the parent”;
n = 3;
exit_code = 0;
break;
}
for(; n > 0; n--) {
puts(message);
sleep(1);
}
Programın bu kısmı çocuk sürecin bitişini bekler.
if (pid != 0) {
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf(“Child has finished: PID = %d\n”, child_pid);
if(WIFEXITED(stat_val))
printf(“Child exited with code %d\n”, WEXITSTATUS(stat_val));
else
printf(“Child terminated abnormally\n”);
}
exit(exit_code);
}
Bu programı çalıştrınca, ebeveynin çocuğu beklediğini görürüz. When we run this program, we see the

$ ./wait
fork program starting
This is the child
This is the parent
This is the parent
This is the child
This is the parent
This is the child
This is the child
This is the child
Child has finished: PID = 1582
Child exited with code 37
$

Thursday 27 December 2007

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

Ali Rıza SARAL(1)


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

Yeni Süreçler Başlatış
Bir program içinden başka bir programın çalışışına neden olabiliriz ve böylece sistem kütüphanesi fonksiyonunu kullanarak yeni bir süreç yaratabiliriz.

#include
int system (const char *string);

Sistem fonksiyonu kendisine bir karakter dizisi olarak geçirilen komutu koşturur ve bitişi için bekler. Komut aşağıdaki şekilde

$ sh -c string

bir kabuğa verilmiş gibi çalıştırılır. Sistem eğer komutu koşturmak için bir kabuk açılamazsa 127 ve eğer başka bir hata oluşursa -1 döndürür. Başka durumlarda, sistem komutun geri dönüş kodunu döndürür.

Örnek:
#include
#include
int main()
{
printf(“Running ps with system\n”);
system(“ps -ax”);
printf(“Done.\n”);
exit(0);
}

Çıktı:
$ ./system1
Running ps with system
PID TTY STAT TIME COMMAND
1 ? S 0:05 init
2 ? SW 0:00 [keventd]
...
1262 pts/1 S 0:00 /bin/bash
1273 pts/2 S 0:00 su -
1274 pts/2 S 0:00 -bash
1463 pts/1 S 0:00 oclock -transparent -geometry 135x135-10+40
1465 pts/1 S 0:01 emacs Makefile
1480 pts/1 S 0:00 ./system1
1481 pts/1 R 0:00 ps -ax
Done.

Alternatif:
System1.c içindeki fonksiyon çağrısını şununla değiştiriniz:

system(“ps -ax &”);

Çıktı:
$ ./system2
Running ps with system
PID TTY STAT TIME COMMAND
1 ? S 0:05 init
2 ? SW 0:00 [keventd]
...
Done.
$ 1246 ? S 0:00 kdeinit: klipper -icon klipper -miniicon klipper
1274 pts/2 S 0:00 -bash
1463 pts/1 S 0:00 oclock -transparent -geometry 135x135-10+40
1465 pts/1 S 0:01 emacs Makefile
1484 pts/1 R 0:00 ps -ax
.
Bir Süreç Görüntüsünün Yerine Geçiş
Exec başlığı altında büyük bir ilişkili fonksiyonlar ailesi var. Süreçleri başlatış ve program başlatış değişkenlerini Sunuş açısından farklılaşırlar. Bir exec fonksiyonu şu andaki sürecin yerine yeni bir süreç koyar, bir patika veya dosya başlangıç değişkeni belirleyerek .

Genelde, sistem komutunu kullanmak süreçleri başlatmak için idealden çok uzak bir yaklaşımdır.

Bu fonksiyonlar iki gruba aittir. execl, execlp, ve execle sonu boş işaret edici ile biten değişik sayıda başlangıç değişkeni alırlar. Eğer exec fonksiyonunu ps programını başlatmak için kullanmak isterseniz, aşağıda gösterilen altı exec ailesinin birinden seçebilirsiniz.

#include
/* Example of an argument list */
/* Note that we need a program name for argv[0] */
char *const ps_argv[] =
{“ps”, “-ax”, 0};
/* Example environment, not terribly useful */
char *const ps_envp[] =
{“PATH=/bin:/usr/bin”, “TERM=console”, 0};
/* Possible calls to exec functions */
execl(“/bin/ps”, “ps”, “-ax”, 0); /* assumes ps is in /bin */
execlp(“ps”, “ps”, “-ax”, 0); /* assumes /bin is in PATH */
execle(“/bin/ps”, “ps”, “-ax”, 0, ps_envp); /* passes own environment */
execv(“/bin/ps”, ps_argv);
execvp(“ps”, ps_argv);
execve(“/bin/ps”, ps_argv, ps_envp);

Alternatif:
#include
#include
int main()
{
printf(“Running ps with execlp\n”);
execlp(“ps”, “ps”, “-ax”, 0);
printf(“Done.\n”);
exit(0);
}

Pexec.c’yi çalıştırınca, alışılagelmiş ps çıktısını alırız fakat done çıkmaz. Ayrıca çıkışta pexec’ten bahsedilmez.

$ ./pexec
Running ps with execlp
PID TTY STAT TIME COMMAND
1 ? S 0:05 init
2 ? SW 0:00 [keventd]
...
1262 pts/1 S 0:00 /bin/bash
1273 pts/2 S 0:00 su -
1274 pts/2 S 0:00 -bash
1463 pts/1 S 0:00 oclock -transparent -geometry 135x135-10+40
1465 pts/1 S 0:01 emacs Makefile
1514 pts/1 R 0:00 ps –ax

Süreç Görüntüsünü Katlayış
Aynı anda birden çok fonksiyon çalıştıran süreçler kullanmak için, bağlar(threads) kullanabiliriz veya init’in yaptığı gibi program içinde tamamen ayrı bir süreç yaratırız, exec durumunda olduğu gibi, şu anda çalışan bağın yerine geçirmek yerine.

Fork’u çağırarak yeni bir süreç yaratabiliriz. Bu sistem çağrısı şu andaki sürecin çok sayıda özellikleri ile süreç tablosunda yeni bir hane açarak şu andaki süreci tekrar yaratır.
Yeni süreç hemen hemen aslı ile aynıdır, aynı kodu çalıştırır fakat kendi adres alanı, ortamı, ve dosya tanımlayıcıları ile. Exec fonksiyonları ile birlikte fork yeni süreçler yaratmak için ihtiyacımız olan yegane şeydir.

#include
#include
pid_t fork(void);

Şekil 11-2’de gördüğünüz gibi, ebeveyn içinde fork’a yapolan çağrı yeni çocuk sürecin SüreçKimlikNo’sunu (PID) döndürür. Yeni süreç aslının aynı şekilde çalışmağa devam eder, çocuk sürecin fork’a çağrısı 0 döndürmek istisnası ile. Bu hem ebeveynin hem de çocuğun kimin kim olduğunu belirleyişini mümkün kılar.
Şekil 11-2

Eğer fork başaramazsa -1 döndürür. Bu bir ebeveynin sahip olabileceği çocukların üst sınırı (COCUK_MAX (CHILD_MAX)) nedeni ile bu durum yaygındır, bu durumda errno EAGAIN değerine eşitlenir. Eğer süreç tablosunda bir hane için yeterli yer yoksa, veya yeterli sanal bellek yoksa, errno değişkeni ENOMEM ile eşitlenir.

Tipik bir fork kod parçası:

pid_t new_pid;
new_pid = fork();
switch(new_pid) {
case -1 : /* Error */
break;
case 0 : /* We are child */
break;
default : /* We are parent */
break;
}

Örnek: fork1.c
#include
#include
#include
int main()
{
pid_t pid;
char *message;
int n;
printf(“fork program starting\n”);
pid = fork();
switch(pid)
{
case -1:
perror(“fork failed”);
exit(1);
case 0:
message = “This is the child”;
n = 5;
break;
default:
message = “This is the parent”;
n = 3;
break;
}
for(; n > 0; n--) {
puts(message);
sleep(1);
}
exit(0);
}
Bu program iki süreç olarak çalışır. Bir çocuk doğar ve bir mesajı 5 defa basar. Asıl süreç(ebeveyn) mesajı yalnız üç defa basar. Ebeveyn süreç çocuk mesajlarının hepsini basmadan önce biter, dolayısıyla bir sonraki kabuk sorgucusu(prompt) çıkış ile karışarak belirir.

$ ./fork1
fork program starting
This is the parent
This is the child
This is the parent
This is the child
This is the parent
This is the child
$ This is the child
This is the child

Monday 24 December 2007

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

Ali Rıza SARAL(1)


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

Süreç Planlayış
Daha ileri bir ps çıktısı ps komutunun kendisi için olan hanedir.

1357 pts/2 R 0:00 ps –ax

Bu, süreç 1357’nin koşuş halinde (run state (R)) olduğunu ve ps –ax komutunu çalıştırmakta olduğunu belirtir. Böylece bu süreç kendi çıktısında tarif edilmektedir! Durum (status) belirteci yalnızca programın koşmağa hazır olduğunu belirtir, gerçekten o anda koştuğunu değil.

Tek işlemcili bir bilgisayar üzerinde, bir anda tek bir süreç çalışabilir, diğerleri sıralarını beklerken. Zaman dilimleri (time slices) olarak bilinen bu sıralar, çok kısadır ve programlar aynı zamanda çalışıyor izlenimini verirler. R yalnızca programın başka süreçlerin bitmesini beklemediğini ya da giriş çıkışın tamamlanışını beklediğini belirtir. ps çıktısında bunun gibi iki süreçgörmenizin nedeni budur. (Bir başka genellikle koşuyor şeklinde işaretlenmiş süreç X görüntü sunucusudur (X display server).

Linux çekirdeği hangi sürecin bir sonraki zaman dilimini alacağına karar vermek için bir süreç planlayıcısı (process scheduler) kullanır. Bunu süreç önceliğine (process priority) göre yapar. Yüksek öncelikli süreçler daha sık koşmak durumuna ulaşırken, düşük öncelikli arkaplan görevleri gibi diğerleri daha seyrek çalışır.

Linux ile, süreçler kendilerine ayrılan zaman dilimini aşamazlar. Şüf’alı çok görevli (pre-emptively multitasked) (*) oldukları için işbirlikleri gerekmeden askıya alınırlar ve kaldıkları yerden devam ederler. Eski sistemler, başka süreçlerin kaldıkları yerden devam edebilişleri için genellikle süreçlerin doğrudan kontrolü bırakışını gerektirir.

(Şüf’a = pre-emptive kelimesi Hukuk’ta geçer, Sn. Nadir YÜCEL’den alınmıştır.)

Linux gibi çok sayıda programın büyük olasılıkla aynı imkanlar için yarıştığı çok görevli bir sistemde, kısa iş yapış patlayışları ve giriş için ara verişler yapan programlar daha iyi davranışlı olarak değerlendirililer, aralıksızca bir değer hesaplayarak veya sistemi yeni girişi var mı diye sürekli sorgulayarak işlemciyi esir alanlara göre… İyi davranışlı programlar hoş (nice) programlar olarak adlandırılırlar, ve bu “hoşluk” bir bakıma ölçülebilir.

İşletim sistemi bir sürecin önceliğini bir “hoş” (nice) değerine dayanarak belirler, kendiliğinden değeri 0’dır, ve programın davranışına göre değişir. Hiç durmadan, uzun süreler için çalışan programlar düşük öncelikler alırlar. Girdi beklerken vb. duraklayan programlar ödüllendirilirler.

Bu, kullanıcı ile etkileşen bir programı canlı tutmağa yardım eder; öte yandan kullanıcıdan bir girdi beklerken sistem önceliğini arttırır, öyle ki kaldığı yerden devam etmeğe hazır olduğunda yüksek öncelik sahibi olur. Süreç hoş değerini hoş (nice) ile belirleyebiliriz ve yeniden-hoş (renice) ile ayarlayabiliriz. Hoş (nice) komutu hoş değerini 10 arttırır, ona düşük öncelik vererek. Faal süreçlerin hoş değerlerine ps komutunun –l veya –f (uzun çıktı için) seçenekleri ile bakabiliriz. İlgilendiğimiz değer NI(nice) sütununda gösterilir.
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
000 S 500 1259 1254 0 75 0 - 710 wait4 pts/2 00:00:00 bash
000 S 500 1262 1251 0 75 0 - 714 wait4 pts/1 00:00:00 bash
000 S 500 1313 1262 0 75 0 - 2762schedu pts/1 00:00:00 emacs
000 S 500 1362 1262 2 80 0 - 789 schedu pts/1 00:00:00 oclock
000 R 500 1363 1262 0 81 0 - 782 - pts/1 00:00:00 ps

Burada oclock programının(süreç 1362) kendiliğinden gelen hoş değeri ile çalıştığını görebiliriz. Eğer şu komut ile başlatılmış olsaydı

$ nice oclock &
+10 hoş değeri ayrılmış olacaktı ona. Eğer şu komut ile bu değeri ayarlarsak

$ renice 10 1362
1362: old priority 0, new priority 10

Saat (clock) programı daha seyrek koşacak. Değiştirilmiş hoş değerini yine ps ile görebiliriz:

F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
000 S 500 1259 1254 0 75 0 - 710 wait4 pts/2 00:00:00 bash
000 S 500 1262 1251 0 75 0 - 714 wait4 pts/1 00:00:00 bash
000 S 500 1313 1262 0 75 0 - 2762 schedu pts/1 00:00:00 emacs
000 S 500 1362 1262 0 90 10 - 789 schedu pts/1 00:00:00 oclock
000 R 500 1365 1262 0 81 0 - 782 - pts/1 00:00:00 ps

Durum sütunu hoş değerinin kendiliğinden gelenden daha farklı olduğunu belirtmek için şimdi N değerini belirtir. ps çıktısının PPID alanı ebeveyn süreç KİMLİKNO (ID)’sunu belirtir, yani ya bu sürecin başlayışına neden olan sürecin SüreçKimlikNo’su (PID), ya da eğer bu süreç artık koşmuyorsa, sıfırdan-başla(init) (SüreçKimlikNo 1). Linux planlayıcısı hangi sürecin çalışışına izin vereceğine öncelik temelinde karar verir. Tam gerçekleyişler değişebilirler, tabii, fakat yüksek öncelikli süreçler daha sık koşar. Bazı durumlarda, eğer yüksek öncelikli süreçler koşmağa hazır iseler, düşük öncelikli süreçler hiçbir zaman çalışmayabilir.

Sunday 23 December 2007

LİNUX Programlamağa Giriş - Süreçler 2

Ali Rıza SARAL(1)

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

Süreçleri İzleyiş
ps komutu çalıştırmakta olduğumuz süreçleri, bir başka kullanıcının çalıştırdığı süreci ya da sistem üzerindeki bütün süreçleri gösterir. İşte örnek bir çıktı:

$ ps -af
UID PID PPID C STIME TTY TIME CMD
root 433 425 0 18:12 tty1 00:00:00 [bash]
rick 445 426 0 18:12 tty2 00:00:00 -bash
rick 456 427 0 18:12 tty3 00:00:00 [bash]
root 467 433 0 18:12 tty1 00:00:00 sh /usr/X11R6/bin/startx
root 474 467 0 18:12 tty1 00:00:00 xinit /etc/X11/xinit/xinitrc --
root 478 474 0 18:12 tty1 00:00:00 /usr/bin/gnome-session
root 487 1 0 18:12 tty1 00:00:00 gnome-smproxy --sm-client-id def
root 493 1 0 18:12 tty1 00:00:01 [enlightenment]
root 506 1 0 18:12 tty1 00:00:03 panel --sm-client-id default8
root 508 1 0 18:12 tty1 00:00:00 xscreensaver -no-splash -timeout
root 510 1 0 18:12 tty1 00:00:01 gmc --sm-client-id default10
root 512 1 0 18:12 tty1 00:00:01 gnome-help-browser --sm-client-i
root 649 445 0 18:24 tty2 00:00:00 su
root 653 649 0 18:24 tty2 00:00:00 bash
neil 655 428 0 18:24 tty4 00:00:00 -bash
root 713 1 2 18:27 tty1 00:00:00 gnome-terminal
root 715 713 0 18:28 tty1 00:00:00 gnome-pty-helper
root 717 716 13 18:28 pts/0 00:00:01 emacs
root 718 653 0 18:28 tty2 00:00:00 ps –af

Bu çıktı, çok sayıda süreç hakkında bilgi göstermektedir, bir Linux sisteminde X altında çalışan bir Emacs editörü de dahil olmak üzere. Örneğin, TTY sütunu sürecin hangi terminalde başlatıldığını, TIME ise o ana kadar kullanılan MİB (CPU) zamanını, ve CMD sütunu süreci başlatmak için kullanılan komutu gösterir. Bunlardan bazılarına daha yakından bakalım:

neil 655 428 0 18:24 tty4 00:00:00 –bash
Başlangıçtaki login 4 nolu sanal konsolda icra edilmiştir. Bu makinadaki konsoldur bu. Çalışan kabuk (shell) programı Linux’un default’u, bash’tir.

root 467 433 0 18:12 tty1 00:00:00 sh /usr/X11R6/bin/startx
Xwindow sistemi xibaşlat (startx) komutu ile başlatılmıştır. Bu, X sunucusunu (server) başlatır ve bazı başlangıç X programlarını koşturur.

root 717 716 13 18:28 pts/0 00:00:01 emacs
Bu süreç X içinde Emacs’i çalıştıran bir pencereyi temsil eder. Yeni bir pencereden gelen bir istek doğrultusunda pencere yöneticisi tarafından başlatılmıştır. Ona yazmak ve ondan okumak için kabuğa yeni bir sözde terminal pts/0 atanmıştır.

root 512 1 0 18:12 tty1 00:00:01 gnome-help-browser --sm-client-i
Bu pencere yöneticisi tarafından başlatılmış GNOME yardım gözden-geçiricisidir(browser).

Yalnızca(by default), ps programı yalnız bir terminal, bir konsol, bir seri hat, veya sözde (pseudo) terminal ile bir bağlantıyı sürdüren süreçleri gösterir. Diğer süreçler bir kullanıcı ya da terminal ile bağlantıya ihtiyaç duymadan çalışabilirler. Bunlar ortak kullanılan kaynakları yönetmek için Linux’un kullandığı tipik süreçlerdir. Bütün bu tür süreçleri ps’in –a seçeneği ile ve “tam” (“full”) bilgi elde etmek için –f seçeneği ile görebiliriz.

Sistem Süreçleri
İşte Linux sisteminde çalışan başka bazı süreçler. Çıktı netlik açısından kısaltılmıştır.
Here are some of the other processes running on this Linux system. The output has been abbreviated for clarity.
$ ps -ax
PID TTY STAT TIME COMMAND
1 ? S 0:05 init
2 ? SW 0:00 [keventd]
3 ? SW 0:00 [kapmd]
4 ? SWN 0:00 [ksoftirqd_CPU0]
5 ? SW 0:00 [kswapd]
6 ? SW 0:00 [bdflush]
7 ? SW 0:00 [kupdated]
8 ? SW 0:00 [kinoded]
10 ? SW 0:00 [mdrecoveryd]
75 ? SW<>

Saturday 22 December 2007

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

Ali Rıza SARAL(1)

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

Süreçler
Bir süreç “içinde bir veya daha çok bağın çalıştığı ve o bağlar için gerekli sistem kaynaklarının yer aldığı bir adres alanıdır.” Şimdilik, bir süreci çalışmakta olan bir program olarak değerlendireceğiz.

Linux gibi çok-görevli (multi-tasking) bir işletim sistemi çok sayıda programın aynı anda çalışışına izin verir. Çalışmakta olan bir programın her bir gerçekleşişi(instance) ayrı bir süreç ortaya koyar.

Çok-kullanıcılı bir sistem olarak, Linux çok sayıda kullanıcının sisteme aynı anda erişişine izin verir. Her kullanıcı çok sayıda programları, hatta aynı programın çok sayıda gerçekleşişini aynı anda çalıştırabilir. Sistem kaynaklarını yönetmek ve kullanıcı erişimini kontrol etmek için sistemin kendisi başka programlar çalıştırır aynı zamanda.

Çalışmakta olan bir program—veya süreç—program kodu, veri, değişkenler (sistem belleğini kaplayan), açık dosyalar(dosya tanımlayıcıları), ve ortam(environment) ‘dan oluşur. Genellikle, Linux sistemi kod ve sistem kütüphanelerini süreçler arasında ortak kullandırarak bir anda bellekte kodun yalnız bir kopyasının bulunmasını sağlar.

Süreç Yapısı Şekil 1-1
Eğer ps komutunu aşağıda belirtildiği gibi koşturursak, çıkış şunun gibi görünecektir:

$ ps -af
UID PID PPID C STIME TTY TIME CMD
rick 101 96 0 18:24 tty2 00:00:00 grep pid_t /usr/include/sys/*.h
neil 102 92 0 18:24 tty4 00:00:00 grep XOPEN /usr/include/features.h

Her sürece süreç tanımlayıcıya SüreçKimlikNosu - process identifier PID adı verilen eşsiz bir sayı ayrılır. 1 sayısı diğer süreçleri yöneten özel bir süreç olan sıfırdan-başlat (init) ‘a ayrılır.

Normalde, bir Linux süreci program kodunu tutan bellek alanına yazamaz, dolayısıyla kod belleğe yalnız-oku (read-only) olarak yüklenir. Bu alana yazılamasa bile, bu alan emin bir şekilde başkaları ile paylaşılabilir.

Sistem kütüphaneleri paylaşılabilir. Böylece, çok sayıda program çağırsa bile, bellekte, örneğin printf ‘in tek bir kopyası oluşu yeterlidir. Bu Pencereler (Windows)‘in devingen ilişki kütüphaneleri (dynamic link libraries (DLLs))’nin çalışış şekline benzer fakat daha işlenmiş bir modeldir.

Standart kütüphanelerin ortak alt-programlarını hariç tutmak bütün işletim sistemi üzerinde büyük bir bellek alanı kurtarılışını sağlar. Fakat bir programın çalışışı için ihtiyacı olan herşey, örneğin kullandığı değişkenler, dosyalar her süreç için ayrıdır. Ek olarak, fonksiyonlar içindeki yerel değişkenler (local variables) ve fonksiyonları kontrol ve onlardan geri döndürülen değerler için kullanılan, bir sürecin kendine ait yığın(stack) alanı vardır. Aynı zamanda, yalnız bu sürecin kullanması için kurulmuş, ortam değişkenlerini içeren kendi ortam alanı (environment space) vardır.

Bir süreç, aynı zamanda kendisi ile ilgili çalışan bağ içinde, çalışırken nereye geldiğinin kaydını yani program sayacını tutmak zorundadır.

Bir çok LİNUX sisteminde ve bazı UNİX sistemlerinde /proc adı verilen özel bir dosya kümesi vardır. Bunlar gerçek dosyalar olmaktan çok, süreçler çalışırken sanki kütüphanelerdeki dosyalar gibi içlerine bakmanıza izin verdikleri için özeldirler.

Son olarak, LİNUX, UNİX gibi kod ve veriyi sabit diskin bir alanına sayfalayan sanal bir bellek sistemine sahip olduğu için fiziksel belleğe sığabilecek olandan çok dafa fazla sayıda süreç yönetilebilir.

Süreç Tablosu
Linux süreç tablosu (process table) şu anda yüklü olan bütün süreçlerin, örneğin PID, durum(status), ve komut dizisi türünden ps çıktısında görebileceğimiz türden bilgilerini tanımlar.

İşletim sistemi süreçleri SüreçKimlikNo’ları (PID) ‘nı kullanarak yönetir, ve onlar süreç tablosuna index olarak kullanılırlar. Bu tablo sınırlı büyüklüktedir, bu yüzden bir sistemin destekleyebileceği süreç sayısı sınırlıdır.

Monday 17 December 2007

LİNUX Çekirdeğini Anlamak - Süreçler 6


3.4 Süreçleri Yok etmek (Destroying Processes)
Süreçlerin çoğu koşturmak zorunda oldukları kodun çalışışını bitirmek anlamında “ölürler.”
Bu olduğunda, çekirdek, bu sürecin sahibi olduğu kaynakları serbest bırakmak için haberdar edilmelidir; bu bellek, açık dosyalar, ve semaphor gibi başka ıvır zıvırı içerir. Bir sürecin alışılagelmiş bitiş şekli çık() (exit()) sistem çağrısını harekete geçirmektir. Bu sistem çağrısı programcı tarafından doğrudan konabilir. Ek olarak, çık() (exit()) sistem çağrısı kontrol akışı ana prosedürün son komut satırına geldiği her kez çalıştırılır (C programlarında ana() (main()) fonksiyonu). Ya da, çekirdek bir süreci ölmeye zorlayabilir.

Bu tipik olarak, süreç, muamele edemeyeceği veya yok sayamayacağı bir işaret (signal) aldığında veya çekirdek bir süreç yararına çalışırken, Çekirdek Çalışma Türünde kurtarılamayacak bir MİB istisnası (exception) ilan edilirse ortaya çıkar.

3.4.1 Süreç Sonu (Process Termination)
Bütün süreç sonları çekirdek veri alanlarının sona eren sürece değindiği noktaları(references) kaldıran çıkış_yap() (do_exit( )) fonksiyonu tarafından ele alınır. çıkış_yap() (do_exit( )) fonksiyonu aşağıdaki eylemleri yapar:

1. Süreç tanımlayıcının PF_ÇIKIYOR (PF_EXITING) bayrak alanını işaretler.
2. Eğer gerekiyorsa süreç tanımlayıcıyı bir IPC semaphore kuyruğundan sem_çık() (sem_exit( )) fonksiyon ya da devingen zamanlayıcı kuyruğundan yoket_zamanlayıcı (del_timer( )) fonksiyonu ile çıkar.

3. __exit_mm( ), __exit_files( ), __exit_fs( ), and _ _exit_sighand( ) fonksiyonları ile sayfalayış, dosyasistemi, ve işaret muamelesi ile ilgili süreç veri yapılarını sınar. Bu fonksiyonlar aynı zamanda eğer başka hiçbir süreç paylaşmıyorsa bu veri yapılarının herhangi birini yok ederler.

4. Süreç tanımlayıcının hal (state) alanını GÖREV_ZOMBİE (TASK_ZOMBIE) olarak işaretler.

5. Süreç tanımlayıcının çıkış_kodu (exit_code) alanını süreç sona eriş koduna eşitler. Bu değer ya çıkış() (exit()) sistem çağrısı parametresi(normal sonlanış), veya çekirdek tarafından sağlanan hata kodudur(anormal sonlanış).

6. Hem ebeveyn sürecinin hem de çocuk süreçlerin ebeveyn ilişkilerini güncelleştirmek için çıkış_bildirim() (exit_notify()) fonksiyonunu harekete geçirir. Sona eren süreç tarafından yaratılmış bütün çocuk süreçleri sıfırdan_başla (init) sürecinin çocukları haline gelirler.

7. Yeni bir süreci koşturmağa seçmek için zaman_ planla() (schedule()) fonksiyonunu harekete geçirir. GÖREV_ZOMBİE (TASK_ZOMBIE) halindeki bir süreç zaman_planlayıcı tarafından yok sayıldığı için, süreç zaman_planla() (schedule( )) içindeki ona_geç() (switch_to) makrosu harekete geçtikten hemen sonra çalışmağı durduracaktır.

3.4.2 Süreç Kaldırılışı (Process Removal)
Unix işletim sistemi bir sürecin ebeveyninin SüreçKimlikNosu (PID)’nu temin etmek için ya da çocuklarından herhangi birinin çalışım halini temin etmek için o sürecin çekirdeği sorgulayışına izin verir.

Bir süreç, örneğin belirli bir görev için bir çocuk süreç yaratır ve sonra bekle() (wait())- benzeri bir sistem çağrısı icra ederek çocuğun bitip bitmediğini kontrol eder. Eğer çocuk bitmiş ise, bitiş kodu ebeveyn sürece görevin başarı ile yapılıp yapılmadığını anlatır. Bu tasarım seçeneklerine uymak için, Unix çekirdeklerinin bir süreç bittikten hemen sonra süreç tanımlayıcı alanı içine dahil edilen verileri çöpe atmalarına izin verilmez. Böyle yapmalarına yalnız ebeveyn süreç bir bekle() (wait()) benzeri sistem çağrısını yaptıktan sonra izin verilir, sona erdirilmiş sürece ilişkin. GÖREV_ZOMBİE (TASK_ZOMBIE) hali işte bu yüzden kullanılmıştır: süreç teknik açıdan ölü olsa bile, tanımlayıcısı, ebeveyn süreci haberdar edilinceye kadar saklanmalıdır.

Eğer ebeveyn süreç çocuklarından önce sona ererse ne olur? Böyle bir durumda, sistem zombie süreçler ile dolup taşabilir, var olan görev hanelerini kullanıp tüketebilen. Daha önce bahsedildiği gibi, bu problem bütün öksüz süreçlerin zorla sıfırdan_başla (init) sürecinin çocukları haline getirilişi ile çözülür.

Bu şekilde, sıfırdan_başla süreci bir bekle()-benzeri sistem çağrısı ile kendi meşru çocuklarından birini kontrol ederken zombie’leri yok eder.

Saturday 15 December 2007

LINUX Çekirdeğini Anlamak - Süreçler 5

3.3 Süreçleri yaratış (Creating Processes)
Unix işletim sistemi kullanıcılarını memnun etmek için yüklü bir şekilde süreç yaratmağa başvurur. Örnek olarak, kabuk(shell) süreci kullanıcı ne zaman bir komut verirse kabuğun bir başka kopyasını çalıştıran yeni bir süreç yaratır. Geleneksel Unix sistemleri bütün süreçlere aynı şekilde muamele eder: ebeveyn süreç tarafından sahip olunan kaynaklar katlanır, ve bir kopyası çocuk sürece verilir.

Bu yaklaşım, süreç yaratışı çok yavaş ve verimsiz kılar, çünkü ebeveyn sürecinin bütün adres alanını kopyalamağı gerektirir. Çocuk süreç ebeveynine ait kaynakların tümünü okumak ya da değiştirmek ihtiyacını nadiren duyar; bir çok durumda, hemen bir çalıştırve() (execve( )) komutu verir ve o kadar dikkatli olarak yaratılmış adres alanını siliverir. Çağdaş Unix çekirdekleri bu sorunu üç ayrı mekanizma ile çözer:

•Yazarken Kopyala (Copy On Write) tekniği ebeveyn ve çocuğun aynı fiziki sayfaları okuyuşlarına olanak verir. Ne zaman biri fiziki sayfaya yazmağa kalkışırsa, çekirdek onun içeriğini yazan sürece atanmış olan yeni bir fiziksel sayfaya kopyalar.

• Hafifsıklet süreçler hem ebeveynin hem de çocuğun süreç başına yaratılan bir çok çekirdek veri yapısını paylaşmalarına olanak sağlar, sayfalayış (paging) tabloları (ve dolayısıyla bütün Kullanıcı Çalışış Türü adres alanı (User Mode address space) ) ve açık dosya tabloları gibi.

• vdallan() (vfork( )) sistem çağrısı ebeveyninin bellek adres alanını paylaşan bir süreç yaratır.
Çocuğun ihtiyaç duyduğu verinin ebeveyn tarafından üstüne yazılmaması için, ebeveynin çalıştırılışı çocuk bitinceye ya da çocuk yeni bir program çalıştırıncaya kadar engellenir.

3.3.1 Kopyala(), dallan(), ve vdallan() Sistem Çağrıları (The clone( ), fork( ), and vfork( ) System Calls)
Hafifsıklet süreçler Linux tarafından __kopyala() (__clone( )) adlı bir fonksiyon kullanarak yaratılırlar, dört tane parametre kullanan:

fn Yeni süreç tarafından çalıştırılan bir fonksiyon belirler; fonksiyondan geri dönüldüğünde, çocuk sona erer. Fonksiyon bir tam sayı (integer) döndürür, çocuk sürecinin bitiş kodunu temsil eden.

Arg fn( ) fonksiyonuna geçirilen veriye işaret eden bir işaret edici.

Bayraklar (Flags) Çeşitli bilgiler. Alçak byte çocuk bittiğinde ebeveyn sürece gönderilecek işaret (signal) sayısını belirler; İŞAÇOCUK (SIGCHLD) işareti genellikle seçilir. Kalan 3 byte bir grup kopyalayış bayrağını (clone flags) şifreler (encode), çocuk ile ebeveyn arasında paylaşılan kaynakları belirleyen. Bayraklar, kuruluklarında aşağıdaki anlamlara sahiptir:

KOPYA_SB (CLONE_VM) Sanal bellek tanımlayıcı ve bütün sayfa tabloları.
KOPYA_DS (CLONE_FS) :Kök kütüphaneyi ve şu anda çalışılan kütüphaneyi belirleyen tablo.
KOPYA_DOSYALAR (CLONE_FILES) : Açık dosyaları belirleyen tablo.
KOPYA_İŞAMUA (CLONE_SIGHAND) : İşaret muamele edicileri belirleyen tablo. KOPYA_SURECKIMLIKNO (CLONE_PID) : Süreç kimlik nosu.
KOPYA EİZLE (CLONE_PTRACE) : Eğer bir eizle() (ptrace()) sistem çağrısı ebeveyn sürecin izlenmesine(traced) neden oluyorsa, çocuğu da izlenecektir.
KOPYA_VDALLAN (CLONE_VFORK) : vdallan() (vfork( )) sistem çağrısı için kullanılır.
Çocuk_yığını (child_stack) çocuk sürecin esp kayıtına (esp register) atanacak olan Kullanıcı Çalışış Türü yığın işaret edicisini belirler.
3.3.2 Çekirdek Bağları (Kernel Threads)
Geleneksel Unix sistemleri bazı kritik görevleri arada çalışan süreçlere atar, disk tamponlarını yazmak, kullanılmayan sayfa çerçevelerini değiştirmek (swapping out unused page frames), ağ bağlantılarına hizmet vermek ve diğerleri gibi. Gerçekten, bu görevleri katı doğrusal üslupta yerine getirmek etkin olmaz; hem işlevleri hem de eğer arkaplanda çalışırlarsa son kullanıcı süreçleri daha iyi yanıt alır.

Bazı sistem süreçleri yalnız Çekirdek Çalışım Türünde koştukları için, çağdaş işletim sistemleri işlevlerini çekirdek bağlarına (kernel threads) atarlar, gereksiz Kullanıcı Çalışım Türü ile ağırlaşmayan. Linux’ta çekirdek bağları alışımış süreçlerden şu açılarda farklıdır:

• Her çekirdek bağı tek bir özel çekirdek fonksiyonunu çaıştırır, alışılmış süreçler ise çekirdek fonksiyonlarını yalnız sistem çağrıları içinden çalıştırır.

• Çekirdek bağları yalnız Kernel Çelrşrm Türünde koşarlar, alışılmış süreçler ise bazan Çekirdek bazan Kullanıcı Çalışım Türünde koşarlar.

• Çekirdek bağları yalnız Çekirdek Çalışım Türünde çalıştıkları için, yalnız SAYFA_ÖTELEYİŞİ (PAGE_OFFSET) ’ten büyük doğrusal adresleri kullanırlar. Alışılmış süreçler, öte yandan, yalnız 4 gigbyte doğrusal adres kullanırlar, Kullanıcı ay da Çekirdek Çalışım Türlerinden her birinde.

3.3.2.1 Bir çekirdek bağı yaratmak (Creating a kernel thread)
çekirdek_bağı() (kernel_thread()) fonksiyonu yeni bir çekirdek bağı yaratır ve yalnız bir başka çekirdek bağı tarafından çalıştırılabilir. …
3.3.2.2 Süreç 0 (Process 0)
Bütün süreçlerin atası veya tarihsel nedenlerle değiştirici süreç (swapper process) adı verilen
Süreç0 (process0), Linux’un başlatılış aşamasında çekirdeği_başlat() (start_kernel()) fonksiyonunca sıfırdan yaratılan bir çekirdek bağıdır.

3.3.2.3 Süreç 1 (Process 1)
Süreç tarafından yaratılan çekirdek bağı sıfırdan-başlat() (init()) fonksiyonunu çalıştırır, o da çekirdek_bağı() (kernel_thread( )) fonksiyonunu dört defa çalıştırarak sıradan çekirdek görevleri için gerekli dört çekirdek bağını başlatır.

kernel_thread(bdflush, NULL, CLONE_FS CLONE_FILES CLONE_SIGHAND);
kernel_thread(kupdate, NULL, CLONE_FS CLONE_FILES CLONE_SIGHAND);
kernel_thread(kpiod, NULL, CLONE_FS CLONE_FILES CLONE_SIGHAND);
kernel_thread(kswapd, NULL, CLONE_FS CLONE_FILES CLONE_SIGHAND);














Thursday 13 December 2007

Linux Çekirdeğini Anlamak - Süreçler 4

3.2 Süreç Açıp Kapayış (Process Switching)
Süreçlerin çalıştırılışını kontrol etmek için, çekirdek MerkeziİşlemBirimi üzerinde çalışmakta olan sürecin çalıştırılışını askıya alabilmek ve daha önce askıya alınmış başka bir sürecin çalıştırılışını kaldığı yerden devam ettirebilmek zorundadır. Bu faaliyet süreç açıp-kapayış ( process switching) , görev açıp-kapayış (task switching), ya da bağlam açıp-kapayış (context switching) olarak adlandırılır.

Aşağıdaki kısımlar Linux’ta süreç açıp-kapayış’ın unsurlarını tarif etmektedir:
• Donanım bağlamı Hardware context
• Donanım desteği Hardware support
• Linux kodu Linux code
• Yüzen nokta kayıtlarının saklanışı Saving the floating point registers

3.2.1 Donanım Bağlamı (Hardware Context)
Her süreç kendine ait bir adress alanına sahip olabildiği gibi, bütün süreçler MerkeziİşlemBirimi kayıtlarını (register) paylaşmak zorundadırlar. Dolayısıyla bir sürecin çalıştırılışına kaldığı yerden devam etmeden önce, çekirdek bunun gibi her kayıtın(register), süreç askıya alınmadan önce sahip olduğu değerle yeniden yüklendiğini
Garantiye almalıdır.

Süreç MerİşBrmi üzerinde çalışışına kaldığı yerden devam etmeden önce kayıtlara(registers) yüklenmek zorunda olan veri grubuna donanım bağlamı (hardware context) adı verilir. Donanım bağlamı süreç çalıştırılışı için ihtiyaç duyulan bütün bilgileri içeren, süreç çalıştırış bağlamının bir altkümesidir. Linux’ta, bir sürecin donanım bağlamının bir kısmı TSS bölmesine(segment), kalan kısmı da Çekirdek Çalışma Türü Yığınına (Kernel Mode stack) saklanır. TSS bölmesi süreç tanımlayıcının tss alanına den düşer.


önceki (prev) yerel değişkeninin (local variable) kapatılan sürecin süreç tarif edicisine ve sonraki (next)’in açılmakta olup onun yerine geçeni belirttiğini kabul edeceğiz. Böylece süreç açıp-kapayışı öncekinin (prev) donanım bağlamını saklamak ve onun yerine sonrakinin (next) donanım bağlamını yerleştirmek faaliyeti olarak tanımlayabiliriz. Süreç açıp kapayışları sık sık olduğundan, donanım bağlamlarını saklamak ve yeniden yüklemek için harcanan zamanı en aza indirmek önemlidir.

3.2.2 Görev Hali Bölmesi (Task State Segment)
Intel 80x86 mimarisi, donanım bağlamlarını saklamak için, Görev Hali Bölmesi (Task State Segment (TSS)) adı verilen özel bir bölme türü içerir. Her süreç en az 104 byte uzunluğunda kendi TSS bölmesini içerir. Donanım tarafından otomatik olarak kaydedilmemiş kayıtları (registers) ve G/Ç İzin haritasını (I/O Permission bitmap) depolamak için işletim sistemi ek byte’lara ihtiyaç duyar. Bu harita ioperm( ) ve iopl( ) sistem çağrıları Kullanıcı Çalışma Türündeki (User Mode) bir sürece belirli G/Ç kapılarına doğrudan erişim izni verebildiği için gereklidir.

3.2.3 Değiştir Makrosu (The switch_to Macro)
değiştir() (switch_to) makrosu bir süreç açış işlemini icra eder. Önceki (prev) ve sonraki (next) ile belirtilen iki parametre kullanır: ilki askıya alınacak sürecin süreç tanımlayıcı işaretedicisi, ikincisi MİB üzerinde çalıştırılacak sürecin süreç tanımlayıcısı işaret edicisidir. Bu makro planla() (schedule( )) fonksiyonunca MİB üzerinde yeni bir süreç çalıştırmak için plan yapılmak amacı ile harekete geçirilir. değiştir (switch_to) makrosu çekirdeğin en çok donanım-bağımlı alt-programlarından biridir.

3.2.4 Kayan Nokta Kayıtlarının Saklanışı (Saving the Floating Point Registers)
80486 ile başlayarak, AKU Aritmetik Kayan Nokta Birimi (arithmetic floating point unit (FPU)) MİB içine birleşik inşa edildi. Matematik ortakişlemcisi (mathematical coprocessor) kayan nokta hesaplarının pahalı özel amaçlı yongalarla uygulandığı günlerin anısı için hala kullanılmaktadır. Eski modellerle uyumluluğu sürdürmek için, yine de, kayan nokta aritmetiği fonksiyonları KAÇ (ESCAPE) komutlarını kullanmaya başvurarak icra edilmektedir. KAÇ komutları öncül (prefix) byteları 0xd8 ve 0xdf arasında değişen komutlardır. Bu komutlar MİB’ne dahil edilen bir küme kayan nokta kayıtı üzerinde etkili olurlar. Açıkçası, eğer bir süreç KAÇ (ESCAPE) komutları kullanıyorsa, kayan nokta kayıtlarının içerikleri onun donanım bağlamına aittir. Intel 80x86 mikroişlemcileri kayan nokta kayıtlarını TSS içine otomatik olarak saklamazlar. Yine de, çekirdeklerin bu kayıtları yalnız gerekli olduğunda saklamalarına olanak sağlayan bazı donanım destekleyicilerini içerirler.

Wednesday 12 December 2007

LINUX Çekirdeğini Anlamak - Süreçler 3

3.1.3 Süreçler Arasında Ebeveynlik İlişkileri (Parenthood Relationships Among Processes)
Bir program tarafından yaratılan süreçler ebeveyn/çocuk ilişkisine sahiptirler.
Bir süreç birden çok sayıda çocuk yaratabileceklerinden, bunlar kardeşlik ilişkisine sahip olurlar. Bu ilişkileri temsil etmek için bir süreç tanmlayıcıda çok sayıda alan kullanılması gerekir. Süreç 0 ve 1 çekirdek tarafından yaratılır; süreç 1 (sıfırla (init)) diğer bütün süreçlerin atasıdır.

Bir P sürecinin tanımlayıcısı aşağıdaki alanlara içerir:
p_opptr (doğal ebeveyn) (original parent)
p_pptr (ebeveyn) (parent)
p_cptr (çocuk) (child)
p_ysptr (daha genç kardeş) (younger sibling)
p_osptr (daha yaşlı kardeş) (older sibling)


Şekil 3-6. Beş süreç arasında ebeveynlik ilişkileri
Parenthood relationships among five processes

3.1.4 Bekleyiş Kuyrukları (Wait Queues)
Çalıştırışkuyruğu (runqueue) listesi bir GÖREV_ÇALIŞIYOR (TASK_RUNNING) halindeki bütün süreçleri biraraya getirir. Başka hallerdeki süreçleri gruplayışa gelince, çeşitli haller farklı tür ele alışı gerektirir, Linux’un aşağıdaki seçeneklerden birini tercih edişine yol açarak:

• GÖREV-DURDU (TASK_STOPPED) veya GÖREV_ZOMBİ (TASK_ZOMBIE) halindeki süreçler belirli listelere bağlanmaz. Bunları gruplamağa gerek yoktur, çünkü çocuk sürece erişmek için ebeveyn süreç tarafından ya süreç SüreçKimlikNo’su (PID) ya da süreç ebeveynlik ilişkileri kullanılabilir.

• GÖREV_KESİLEBİLİR (TASK_INTERRUPTIBLE) veya GÖREV_KESİLEMEZ (TASK_UNINTERRUPTIBLE) halindeki süreçler her biri belirli bir olaya (event) denk düşen çok sayıda alt sınıfa ayrılabilir. Bu durumda, süreç hali sürece çabukça erişmek (retrieve) için yeterli bilgi sağlamaz, bu yüzden ek süreç listeleri kullanmak gerekir. Bu ek listelere bekleyiş listeleri (wait queues) adı verilir.

Bekleyiş listeleri çekirdek içinde bir çok kullanımlara sahiptir, özellikle kesiş muamelesi (interrupt handling), süreç eşzamanlayışı (process synchronization), ve zamanlayış (timing) için. Bir süreç genellikle bir olayın gerçekleşişini beklemek zorundadır; bir disk işleminin bitişi, bir sistem kaynağının serbest bırakılışı, veya sabit aralık zamanın doluşu gibi… Bekleyiş kuyrukları olaylara bağlı koşullu bekleyişleri gerçekleştirir: belirli bir olayı beklemek isteyen bir süreç kendini bir bekleyiş kuyruğuna yerleştirir ve kontrolü serbest bırakır. Bunun yüzünden, bir bekleyiş kuyruğu bir koşul sağlandığında çekirdek tarafından uyandırılan bir grup uyuyan süreci temsil eder.

Bekleyiş kuyrukları elemanları süreç tanımlayıcılara işaret ediciler içeren çembersel listeler olarak gerçekleştirilir. Bir bekleyiş kuyruğunun her elemanı bekleyiş_kuyruğu (wait_queue) türündendir.

yapı görev_kuyruğu { struct wait_queue {
yapı görev_yapısı * görev; struct task_struct * task;
yapı bekleyiş_kuyruğu * sonraki; struct wait_queue * next;
}; };

Her bekleyiş kuyruğu bir bekleyiş kuyruğu işaret edicisi(wait queue pointer) tarafından belirlenir, ilk elemanın adresini taşıyan ya da eğer liste boş ise, boş işaret ediciyi taşıyan…
bekleyiş_kuyruğu (wait_queue) veri yapısı listedeki sonra gelen ilk elemana işaret eder, sonraki (next) alanı anlamsız (dummy) bir liste elemanına işaret eden son eleman hariç.
Anlamsız(dummy)’ın sonraki (next) alanı bekleyiş kuyruğundan bir işaret edici genişliği azını işaret eden bir değişken ya da alanın adresini taşır (İntel platformlarında bir işaret edici 4 byte uzunluğundadır). Böylece, bekleyiş kuyruğu listesi çekirdek fonksiyonları tarafından gerçek bir çembersel liste şeklinde değerlendirilebilir, son elemanın sonraki(next) alanı, bekleyiş kuyruğu işaretedicisi ile çakışan anlamsız(dummy) bekleyiş kuyruğu yapısına işaret ettiği için… (Şekil 3-7 ’ye bakınız).


Şekil 3-7. Bekleyiş kuyruğu veri yapısı
The wait queue data structure

bekleyişkuyruğunu_sıfırla (init_waitqueue()) fonksiyonu boş bir bekleyiş kuyruğunu sıfırlar; bekleyişkuyruğuna_ekle(kyrk, hane) (add_wait_queue(q,entry)) fonksiyonu hane(entry) adresli yeni haneyi bekleyiş kuyruğu işaretedicisi kyrk (q) ile belirlenen bekleyiş kuyruğuna ekler.


Bekleyiş kuyrukları önemli çekirdek fonksiyonları kadar kesiş muamele edicileri (interrupt handlers) tarafından da değiştirildikleri için, bu fonksiyon aşağıdaki işlemleri engellenmiş kesişlerle(disabled interrupts) çalıştırır:

3.1.5 Süreç Kullanım Sınırları (Process Usage Limits)
Süreçler kullanım sınırları (usage limits ) grupları ile ilişkilendirilirler, kullandıkları sistem kaynakları miktarını belirleyen.

Özellikle, Linux aşağıdaki sınırları tanır:
RLIMIT_CPU Süreç için Merkezi işlem Birimi zaman üst sınırı
RLIMIT_FSIZE İzin verilen dosya uzunluğu üst sınırı
RLIMIT_DATA Düz bellek (heap) büyüklüğü üst sınırı
RLIMIT_STACK Yığın(stack) büyüklüğü üst sınırı
RLIMIT_CORE Nüve dökümü (core dump) dosya büyüklüğü üst sınırı
RLIMIT_RSS Süreç tarafından sahip olunan sayfa çerçeveleri sayısı üst sınırı
RLIMIT_NPROC Bir kullanıcının sahip olabileceği en çok süreç sayısı
RLIMIT_NOFILE Açık dosya sayısı üst sınırı
RLIMIT_MEMLOCK Maximum size of nonswappable memory
RLIMIT_AS Süreç adres alanı büyüklüğü üst sınır





Saturday 8 December 2007

LINUX Çekirdeğini Anlamak – Süreçler 2

Ali Rıza SARAL(1)

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

3.1.2.4 Süreç Listesi (The process list)
Verilmiş bir çeşit süreçlerin arasında etkili bir arayışa imkan sağlamak için (örn., çalışabilir halde olan bütün süreçler) çekirdek, bu süreçlerin bir çok listesini yaratır. Her liste süreç tanımlayıcılara işaretedicilerden oluşur.

Bir liste işaretedicisi(yani, herbir sürecin bir sonraki sürece işaret etmek için kullandığı alan) doğrudan süreç tanımlayıcı veri yapısı içine gömülüdür. görev_yapısı (task_struct)’nın C-dili tanımlanışına baktığınızda, tanımlayıcılar kendi üstlerine karışık bir tekrarçağıran (recursive) tavırla döner gibi gözükürler.

Yine de, bu kavram kendisinin bir başka gerçeklenişine bir işaret edici taşıyan bir veri yapısı olan bir listeden daha karışık değildir. Çembersel çift bağlı liste (bkz. Şekil 3-3) var olan bütün süreç tanımlayıcıları birbirlerine bağlar; buna süreç listesi adını vereceğiz.

Listeyi gerçekleştirmek için her süreç tanımlayıcının önceki_görev ve sonraki_görev (prev_task and next_task) alanları kullanılır. görev dizisinin ilk elemanı tarafından belirtilen(referenced) görevi-sıfırla (init_task) tanımlayıcısı listenin başıdır: bütün süreçlerin atasıdır(anchestor), ve one süreç0(process 0) ya da swapper adı verilir. görev_sıfırla(init_task )’nın önceki_görev (prev_task) alanı listeye eklenen en son süreç tanımlayıcıya işaret eder.


Şekil 3-3. Süreç listesi

BAĞLARI_KUR (SET_LINKS) ve BAĞLARI_KALDIR (REMOVE_LINKS) makroları süreç listesine bir süreç eklemek veya kaldırmak için kullanılır. Bu makrolar aynı zmanda sürecin ebevyn ilişkisininde çaresine bakar. her_görev_için (for_each_task) adı verilen bir başka kullanışlı makro bütün süreç listesini tarar.

Bu makro çekirdek programcısı çevrimi sağladıktan sonra gelen çevrim kontrol ibaresi (statement). görev_sıfırla (init_task) süreç tanımlayıcısının basitçe liste başı rolünü nasıl oynadığına dikkat ediniz. Makro görev_sıfırla (init_task)’dan sonraki göreve geçerek harekete başlar ve tekrar görev_sıfırla ’ya ulaşıncaya kadar devam eder.

3.1.2.5 GÖREV_ÇALIŞIYOR süreçlerinin listesi (The list of TASK_RUNNING processes)
MerkeziİşlemBirimi (CPU) üzerinde çalıştırmak için yeni bir süreç ararken, çekirdeğin yalnız çalıştırılabilir (runnable) süreçleri dikkate almaklığı gerekir(yani GÖREV_ÇALIŞIYOR (TASK_RUNNING) durumundaki süreçler) . Bütün süreç listesi taramak çok etkisiz olacağından, çalıştırış kuyruğu (runqueue) adı verilen bir çift bağlı çembersel liste geliştirilmiştir. Süreç tanımlayıcılar çalıştırış kuyruğu (runqueue) ’yu gerçekleştirmek için sonraki_çalışan ve önceki_çalışan (next_run and prev_run) alanlarını içerir.

Bir önceki durumda olduğu gibi, görev_sıfırla (init_task) süreç tanımlayıcısı liste başı rolünü oynar. sy_çalıyor (nr_running) değişkeni çalıştırılabilir süreçlerin toplam sayısını saklar. çalıştırış_kuyruğuna_ekle (add_to_runqueue( )) fonksiyonu bir sürec tanımlayıcıyı listenin başlangıcına ekler, çalıştırış_kuyruğundan_sil (del_from_runqueue( )) ise bir süreç tanımlayıcıyı listeden çıkartır.

Çalıştırım planlamak amacı ile, süreç tanımlayıcıyı başa ya da sona almak için iki fonksiyon, çalıştırış_kuyruğunda_başa_al ve çalıştırış_kuyruğunda_sona_al (move_first_runqueue( ) ve move_last_runqueue( )) sağlanmıştır.

Son olarak, süreci_uyandır() (wake_up_process( )) fonksiyonu bir süreci çalştırılabilir yapmak için kullanılır. Süreç halini GÖREV_ÇALIŞIYOR (TASK_RUNNING)’a ayarlar, süreci çalıştırış kuyruğuna sokmak için çalıştırışkuyruğuna_ekle() ( add_to_runqueue( ))’yi harekete geçirir ve sy_çalıyor (nr_running) ’i arttırır.

Süreç gerçek-zaman (real-time) olduğu veya şu anki süreçten çok daha büyük bir devinimsel öncelik(dynamic priority) sahibi olduğunda çalıştırım planlayıcıyı zorla harekete geçirir.

3.1.2.6 süreçkimliknosueşleştiriş tablosu ve zincirlenmiş listeler (The pidhash table and chained lists)
Bir çok durumda, çekirdek SüreçKimlikNosu (PID)’ndan süreç tanımlayıcı işaretedicisini çıkartabilmek zorundadır. Bu örneğin, öldür() (kill( )) sistem çağrısına hizmet ederken olur: P1 süreci bir başka sürece, P2, işaret göndermek istediğinde, P2’nin SüreçKimlikNo’sunu parametre olarak belirleyerek öldür() sistem çağrısını harekete geçirir.

Çekirdek süreç tanımlayıcı işaretedicisini SüreçKimlikNo’sundan çıkarır ve daha sonra P2 süreç tanımlayıcısından gelip beklemekte olan (pending) işaretleri kayıt eden veri yapısına işaretediciyi elde eder. Süreç listesini sıradan taramak ve süreç tanımlayıcıların pid alanlarını kontrol etmek başvurulabilir fakat hiç te etkin olmaz.

Arayışı hızlandırmak için, SÜREÇKİMLİKNOSUEŞLEŞTİRİŞ_SY (PIDHASH_SZ) sayısında elemandan oluşan bir süreçkimliknosueşleştiriş tablosu (pidhash hash table) ’a başvurulmuştur (PIDHASH_SZ genellikle GÖREV_SY (NR_TASKS)/4 ‘tür). Bu tablo haneleri süreç tanımlayıcı işaretedicilerini taşır.

SüreçKimlikNo’su süreçkimliknosu_eşleştirişfonksiyonu (pid_hashfn) makrosunu kullanarak bir tablo indexine dönüştürülür.





Şekil 3-4. Süreçkimliknosueşleştiriş tablosu ve zincirlenmiş listeler Zincirleyerek eşleştiriş
SüreçKimlikNo’larından tablo indexlerine doğrusal bir dönüştürüşe terih edilir, çünkü SüreçKimlikNo’su 0 ile 32767 arasında herhangi bir değeri alabilir. GÖREV_SY(NR_TASKS) , yani süreçlerin alabileceği en büyük sayı değeri genellikle 512 olduğundan, 32768 haneden oluşan bir tablo tanımlamak bir depo ziyanı olurdu.
SüreçKimNo_eşleştir (hash_ pid( ) ve SüreçKimNo_karşılıkBul (unhash_ pid( )) fonksiyonları süreçKimlikNoEşleştiriş tablosu (pidhash table) ’na süreç eklemek veya çıkarmak için kullanılır.
SüreçKimNo_ile_gorev_bul(find_task_by_pid( )) fonksiyonu işleştiriş tablosunu arar ve belirli bir SüreçKimlikNosu ile verilen bir sürecin süreç tanımlayıcı işaretedicisini geri verir (ya da eğer süreci bulmazsa boş bir işaret edici
3.1.2.7 Serbest görev hanelerinin listesi (The list of task free entries)
Her süreç yaratılışında ya da yok edilişnde gorev (task) dizisi güncelleştirilmek zorundadır. Diziye yeni bir hane eklenişi etkin bir şekilde yapılır:diziyi doğrusal olarak arayıp ilk serbest haneyi bulmaktansa, çekirdek ayrı bir çift bağlı, çembersel olmayan bir serbest haneler listesi tutar. görevdizisi_serbestlistesi (tarray_freelist) o listenin ilk elemanını içerir; listedeki her serbest hane bir başka serbest haneye işaret eder, listenin en son elemanı boş bir işaretedici içerir.
Bir süreç yok edildiğinde, gorev (task) dizisi içindeki karşı düşen eleman liste başına eklenir. Şekil 3-5’ te, eğer ilk eleman 0 olarak sayılırsa, görevdizisi_serbestlistesi (tarray_freelist) değişkeni 4. elemana işaret eder çünkü o son serbest bırakılan elemandır. Önceden, 2. ve 1. elemanlara karşı düşen süreçler o sıra ile yok edilmişler. 2. eleman şekilde gösterilmeyen bir başka serbest gorevler (tasks) elemanına işaret eder. Şekil 3-5. Bir serbest haneli görev dizisi örneği

Diziden bir hane yok edilişi de etkin bir şekilde yapılır. Her süreç tanımlayıcı p , p’ye işaret ediciyi taşıyan görev (task) hanesine işaret eden bir görevdizisi_işaretedicisi (tarray_ ptr) alanı içerir. serbest_gorevhanesi_al (get_free_taskslot( ) ve serbest_gorev_hanesi_ekle (add_free_taskslot( )) fonsiyonları serbest bir hane almak ya da serbest bırakmak için kullanılır.