Prosesler

İş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