---
title: 'Süreç Yönetimi'
source: 'https://academia.sh/tr/kurslar/c-sistem-programlama/surec-yonetimi'
course: 'C ile Sistem Programlama'
language: tr
updated: '2026-09-26T03:02:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'fork sonrası iki sürecin aynı değişkeni bağımsız kopyalar olarak tuttuğunu, iki yönü de ölçerek gösterir'
    - "fflush(stdout) çağrısının fork'tan önce zorunlu olduğu kuralı, tamponlanmış bir yapının kopyalanma riskini ölçerek açıklar"
    - 'exec ailesinin süreç görüntüsünü neden değiştirdiğini, kodu koşturmadan düzyazıyla anlatır'
---

# Süreç Yönetimi

fork bir süreci ikiye ayırır ve o anki belleğin bir kopyasını çocuğa verir; kopya kurulduktan sonra iki taraf birbirinden bağımsızdır, hiçbir değişiklik öteki tarafa hiçbir yönde ulaşmaz.

Önceki ders `dup` ile türetilen bir tanıtıcının aynı açık dosya kaydını paylaştığını
gösterdi: biri konumu değiştirdiğinde öteki de gördü. `fork` bunun tam tersini yapar.
Çağrıldığı anda süreç belleğinin bir kopyasını çocuğa verir, ve o kopya kurulduktan
sonra iki taraf birbirinden tamamen bağımsızdır. Kurstaki dört sınıftan biri olan
**kopya**, tam olarak bunu tanımlıyor: değişiklik yalnız onu yapan tarafta kalır,
öteki taraf hiçbir zaman görmez. Bu ders bu bağımsızlığı iki yönde, iki farklı
`fork` çağrısında ve iki farklı veri türünde ölçüyor: önce iki düz tamsayı, sonra
çıkış durumunu okuyan makrolar üzerinden, en sonunda da standart çıktının
tamponunu taklit eden küçük bir yapı üzerinden.

## İki Yönlü Kopya Ölçümü

Ölçüm birimleri değişmiyor: `gozlem.h` ve `gozlem.c`, önceki dersteki içerikleriyle
aynı. Bu dosyadaki yeni parça, iki `fork` çağrısını art arda kullanan
`surec_olcumu.c`: ilki iki düz tamsayıyı iki yönde ölçüyor, ikincisi standart
çıktının tamponunu taklit eden küçük bir yapıyı aynı şekilde ölçüyor.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

```c
// surec_olcumu.c — iki ayri fork, iki ayri veri turu, ayni kopyalanma kurali
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "gozlem.h"

/* Model: gercek stdio tamponu degil, ayni kopyalanma kuralini izleyen kucuk bir yapi */
typedef struct {
    char veri[32];
    int uzunluk;
} ModelTampon;

static void model_yaz(ModelTampon *t, const char *s) {
    int n = 0;
    while (s[n] != '\0' && t->uzunluk < 31) {
        t->veri[t->uzunluk] = s[n];
        t->uzunluk++;
        n++;
    }
}

int main(void) {
    int deger_a = 0;
    int deger_b = 0;

    fflush(stdout);
    pid_t p = fork();
    if (p == 0) {
        deger_a = 1;      /* cocugun kendi kopyasinda degisir */
        _exit(deger_b);   /* ebeveynin sonraki degisikligini gormeden cikar */
    }

    deger_b = 7;           /* fork sonrasi yalniz ebeveynin kopyasinda degisir */
    int durum = 0;
    waitpid(p, &durum, 0);

    gozlem("fork sonrasi cocugun degiskeni", KOPYA, deger_a == 1);
    gozlem("fork sonrasi ebeveynin degiskeni", KOPYA, WEXITSTATUS(durum) == 7);
    printf("cocugun normal yoldan cikisi (WIFEXITED): %s\n", WIFEXITED(durum) ? "evet" : "hayir");

    /* ikinci fork: model tampon da ayni kurala uyuyor mu */
    ModelTampon tampon;
    memset(&tampon, 0, sizeof tampon);
    model_yaz(&tampon, "onceki");   /* fork oncesi birikmis, henuz bosaltilmamis icerik modeli */

    fflush(stdout);
    pid_t p2 = fork();
    if (p2 == 0) {
        model_yaz(&tampon, "cocuk");
        _exit(tampon.uzunluk);
    }
    model_yaz(&tampon, "ebeveyn");
    int durum2 = 0;
    waitpid(p2, &durum2, 0);

    gozlem("cocugun tampona eklemesi ebeveyne ulasti mi", KOPYA,
           WEXITSTATUS(durum2) == tampon.uzunluk);

    gozlem_ozeti();
    return 0;
}
```

```text
fork sonrasi cocugun degiskeni     gormedi  kopya
fork sonrasi ebeveynin degiskeni   gormedi  kopya
cocugun normal yoldan cikisi (WIFEXITED): evet
cocugun tampona eklemesi ebeveyne ulasti mi gormedi  kopya
siniflar: kopya 3, ortak 0, aktarim 0, gozlenmeyen 0
ulasan 0, ulasmayan 3
```

**SA3.** İlk iki satır da `gormedi` diyor. Çocuk `deger_a`'yı kendi kopyasında `1`
yapıyor; ebeveyn `waitpid`'den sonra kendi `deger_a`'sına bakıyor ve hâlâ `0`
buluyor — çocuğun yazması hiçbir zaman ebeveynin kopyasına ulaşmadı. Simetrik
yönde, ebeveyn `fork` döndükten sonra kendi `deger_b`'sini `7` yapıyor; çocuk bu
satırı hiç görmeden `_exit(deger_b)` ile çıkıyor, ve kendi kopyasındaki `deger_b`
hâlâ `0` olduğu için çıkış kodu da `0`. Ebeveyn bunu `WEXITSTATUS` ile okuyup `7`
değil `0` bulduğunda, kendi değişikliğinin çocuğa hiç ulaşmadığını doğruluyor.
İkisi de aynı sınıfa düşüyor: **kopya**. `fork` bir kere kopyalar, sonra iki taraf
birbirinden habersizdir — kim önce, kim sonra yazdığının burada hiçbir önemi yok,
çünkü paylaşılan bir kayıt yok, iki bağımsız kopya var.

## fflush Kuralının Ölçümü

**SA4.** Üçüncü satır bu bağımsızlığı bir tamsayıdan çıkarıp çok satırlı bir
tampona taşıyor. `ModelTampon`, gerçek `stdio` tamponunun yerini tutan bir model:
`fork`tan önce `"onceki"` metnini biriktiriyor, tıpkı boşaltılmamış bir çıktı
satırının terminale yazılmayı beklemesi gibi. `fork` çağrıldığında bu yapı da
belleğin geri kalanıyla birlikte kopyalanıyor — çocuk kendi kopyasına `"cocuk"`
ekliyor ve uzunluğunu çıkış koduyla bildiriyor, ebeveyn kendi kopyasına `"ebeveyn"`
ekliyor. `"cocuk"` beş, `"ebeveyn"` yedi karakter olduğu için iki tarafın son
uzunluğu hiçbir zaman eşit çıkmıyor, ve ölçüm bunu `gormedi` olarak okuyor: çocuğun
kendi tamponuna yaptığı ekleme ebeveynin tamponuna hiç ulaşmadı. Kurstaki her
derste geçerli olan kural tam olarak bu ölçümün gösterdiği şey: `fork`'tan önceki
`fflush(stdout)` çağrısı, gerçek `stdio` tamponunun içindeki boşaltılmamış içeriği
sıfırlar, böylece kopyalanacak bir şey kalmaz. Bu satır atlanırsa, `"onceki"` gibi
henüz yazılmamış bir içerik hem ebeveynin hem çocuğun kopyasında birlikte durur;
ikisi de kendi kapanışında kendi tamponunu boşaltırsa aynı satır iki kez
görünür — bir kez ebeveynden, bir kez çocuktan, çünkü artık ikisi de aynı sınıfa,
yani **kopya**'ya ait ayrı birer nüsha taşıyor. `surec_olcumu.c` bu riski iki kez
kapatıyor: her iki `fork`tan hemen önce `fflush(stdout)` çağrılıyor, ve `gozlem`
çağrılarının kendisi yalnız `waitpid` sonrası ebeveyn tarafında koşuyor; çocuk
hiçbir zaman yazdırmıyor, `_exit` ile sessizce çıkıyor.

## Çıkış Durumu Makroları

**SA5.** `waitpid`'in doldurduğu `durum` tek bir tamsayı ama içinde iki ayrı soru
kodlanmış: süreç nasıl bitti, ve hangi değerle. `WEXITSTATUS(durum)` yalnız ikinci
soruyu yanıtlıyor — çocuğun `_exit`'e ya da `return`'e verdiği değeri döndürüyor —
ama bu makro yalnız birinci soru olumluysa, yani çocuk gerçekten normal yoldan
çıktıysa anlamlı. `WIFEXITED(durum)` birinci soruyu yanıtlıyor: çocuğun bir
sinyalle değil, kendi isteğiyle sonlandığını doğruluyor. Bu ders `WIFEXITED`'i
her iki `fork`tan sonra da doğru buluyor, çünkü iki çocuk da `_exit` ile çıkıyor;
gelecek dersin sinyal konusu bu makronun karşıtını, yani bir sinyalle sonlanan
bir süreç görene kadar bu ayrım soyut kalır. Buradaki nokta şu: `WEXITSTATUS`'a
`WIFEXITED` kontrolü olmadan güvenmek yanıltıcı olabilir, çünkü sinyalle
sonlanan bir sürecin `durum` alanı aynı bitleri başka bir amaçla taşır — bu ders
bunu koşturmuyor, ama makronun neden yalnız başına yeterli olmadığını burada
kaydediyor.

`WEXITSTATUS`'un döndürdüğü değer sekiz bitle sınırlıdır, çünkü POSIX çıkış
kodunu bu genişlikte taşımayı garanti eder; `surec_olcumu.c` içindeki iki ölçüm
de bu sınırın altında kalacak şekilde seçildi — `deger_b` yedi, model tamponun
uzunluğu otuz birin altında. Bir programın `_exit`'e üç haneli ya da negatif bir
değer verip beklentisiyle çıkış kodunu okuması, bu sekiz bitlik kesilme yüzünden
yanıltıcı sonuç üretebilir; bu ders o durumu ayrıca ölçmüyor, ama `gozlem`
çağrılarının küçük sabit değerler seçmesinin nedeni tam olarak bu sınırdan
kaçınmak.

`WIFEXITED`'in karşıtı `WIFSIGNALED`'dir: çocuk bir sinyal yüzünden sonlandıysa
bu makro doğru döner, ve o durumda `WTERMSIG(durum)` hangi sinyalin sonlandırdığını
okur. Bu ders sinyal numarası ya da adı yazdırmayı yasaklayan kural gereği bu
makroyu koşturmuyor — ne yazdırabileceği bir değer üretir ne de bu dersin ölçüm
eksenine bir şey katar, çünkü sinyalle sonlanan bir sürecin çıkış kodu diye bir
şey yoktur, yalnız sonlandıran sinyal vardır. Bir programın `waitpid` sonrası
hem `WIFEXITED` hem `WIFSIGNALED`'i kontrol etmeden `WEXITSTATUS`'a güvenmesi,
sinyalle sonlanan bir çocukta tanımsız bir değeri çıkış koduymuş gibi okumasına
yol açar; bu yüzden ikisi arasındaki ayrım yalnız bir stil tercihi değil, doğru
okumanın önkoşuludur.

## exec Ailesi

**SA6.** `fork`tan sonra çocuk sürecinde çoğunlukla ikinci bir adım gelir: `exec`
ailesinden bir çağrı. `fork` belleği kopyalarken program kodunu değiştirmez —
çocuk hâlâ aynı programın bir kopyasıdır. `exec` ise süreç kimliğini korur ama
süreç **görüntüsünü** baştan kurar: kod, veri ve yığıt yeni bir programınkiyle
değiştirilir, ve çağrı başarılı olduğunda geri dönmez. Bu ders `exec`'i
koşturmuyor, çünkü ölçtüğü soru — kim neyi görüyor — `exec` sonrasında anlamını
yitirir: eski sürecin değişkenleri, `deger_a`'sı, `deger_b`'si, model tamponu
artık yok, yerlerine yenisi geldi. `fork` ve `exec`'in birlikte kullanımı, bir
kabuğun yeni bir program başlatmasının temelidir: kabuk önce `fork` ile kendi
kopyasını üretir, çocuk süreç ortam değişkenlerini ve açık tanımlayıcıları kalıtım
yoluyla devralır, sonra `exec` ile o kopyanın üzerine istenen programı yükler.
Ebeveyn kabuk hiçbir zaman değişmez; yalnız çocuk `exec` çağırır, ve başarılı
olduğunda artık kabuk değil, çağrılan programdır.

`exec` ailesinin üyeleri birbirinden üç eksende ayrılır, hepsi düzyazıyla:
argümanları nasıl aldıkları (`execl` gibi değişken sayıda argümanla çağrılanlar,
`execv` gibi bir dizi alanlar), programı nasıl buldukları (`execve` yalnız tam
yolu kabul eder, `execvp` `PATH` ortam değişkeninde arama yapar) ve ortamı nasıl
devraldıkları (`execve` ve `execvpe` yeni bir ortam dizisi kabul eder, diğerleri
çağıran sürecin ortamını olduğu gibi kullanır). Hepsinin ortak noktası dönüş
değeri yoktur — çağrı başarılıysa geri dönmez, başarısızsa `-1` döner ve çağıran
süreç kendi değişkenleriyle devam eder. `fork` olmadan çağrılan bir `exec`,
çağıran süreci geri dönülmez biçimde başka bir programa çevirir; bu yüzden bir
kabuğun kendi sürecini kaybetmeden yeni bir program başlatması, `fork` ile önce
bir kopya üretip `exec`i o kopyada çağırmasına bağlıdır. Açık dosya tanımlayıcıları
varsayılan olarak `exec` sonrasında da açık kalır — `FD_CLOEXEC` bayrağı
işaretlenmediği sürece çocuğun `fork`tan miras aldığı tanımlayıcılar yeni
programda da geçerlidir; bir kabuğun yönlendirme (`>`, `<`) sözdizimini
gerçeklemesi tam olarak buna dayanır: kabuk önce tanımlayıcıyı `dup2` ile istenen
konuma taşır, sonra `exec` çağırır, ve yeni program o tanımlayıcının nereden
geldiğini hiç bilmeden kullanır.

`fork`un bellek kopyasını nasıl ürettiği bu dersin konusu değil; işletim sistemleri
kursunun bellek yönetimi bölümü bu soruyu zaten yanıtladı — çekirdek çoğu zaman
sayfaları gerçekten çoğaltmaz, yazma-üzerine-kopyala düzeniyle işaretler ve gerçek
kopya yalnız bir taraf yazdığında oluşur. Bu ders o iç işleyişe dokunmuyor; ölçtüğü
tek şey, kopyalama hangi düzenekle yapılırsa yapılsın iki tarafın sonuçta bağımsız
görünmesi — ister bir tamsayı, ister çok satırlı bir tampon olsun.

`ModelTampon` ölçümü gerçek bir stdio tamponunun dahili yapısını taklit
etmiyor; yalnız aynı kopyalanma kuralına uyan, kendi tanımladığımız küçük bir
yapı. Buradaki genellik önemli: gerçek `FILE*` tamponu içinde tutulan bayt
dizisi de, bu dersin başka yerinde ölçtüğü bir tamsayı değişkeni de, `fork`
anında aynı kopyalama kuralına tabidir — çocuk ebeveynin o anki durumunu alır,
sonrasında ikisi bağımsız değişir. `ModelTampon` bu genelliği somutlaştırıyor:
üç bayt uzunluğundaki bir sayaç ile otuz iki baytlık bir dizi, `fork` açısından
eşit muamele görür. Gerçek stdio'da fark, tamponun ne zaman boşaldığını
programcının değil kütüphanenin kararının belirlemesi; satır tamponlu bir
terminalde her satır sonunda, tamamen tamponlu bir dosyada yalnız tampon
dolduğunda ya da program kapanırken boşalır. `fork` bu boşalma zamanlamasını
değiştirmez, yalnız o ana kadar birikmiş içeriği iki bağımsız kopyaya böler;
kuralı ihlal eden `fflush(stdout)` çağrısının eksikliği değil, birikmiş
içeriğin iki kez boşalacağı beklentisidir.

## Sınırlayıcı Ölçüm

Bu ölçümün sınırı zamanlamadan bağımsız olmasıdır, ve bu kasıtlı bir tasarım. Çocuğun
`_exit` çağrısı ile ebeveynin sonraki yazması hangi sırayla çalışırsa çalışsın
sonuç değişmez, çünkü ikisi aynı belleğe yazmıyor — her biri kendi kopyasına
yazıyor. Bu yüzden ölçüm bir `sleep` ya da bekleme gerektirmiyor; `waitpid`
çağrısının kendisi yeterli, çünkü sıralamayı değil kopyaların bağımsızlığını
doğruluyor. Model tampon ölçümü de aynı sınırı taşıyor: `"cocuk"` ile `"ebeveyn"`
metinlerinin farklı uzunlukta seçilmesi rastgele değil — sonucun her koşumda
aynı çıkması için kasıtlı, çünkü bu ders bir yarış koşulunu değil yalnız
kopyalanmanın varlığını ölçüyor. Kurstaki diğer sınıflar — özellikle aktarım —
sıralamaya duyarlı olabilir, ama kopya sınıfı için sıralama hiçbir zaman sonucu
değiştirmez.

Bu ölçümün kapsamadığı bir başka soru, kaç kez `fork` çağrıldığında sonucun nasıl
değiştiğidir. `surec_olcumu.c` iki `fork`u art arda, ilkinin çocuğu tamamen
kapandıktan sonra çağırıyor — ikinci `fork` birinci çocuğun süreç tablosundan
düşmesini beklemiyor, çünkü `waitpid` zaten birincisini topladı. Bir program bunun
yerine iki çocuğu aynı anda, ikincisini birincisinin bitişini beklemeden
başlatsaydı, iki çocuk da aynı ebeveyn belleğinin ayrı birer kopyasını taşırdı;
kopya sınıfının garantisi çocuk sayısından bağımsızdır, çünkü her `fork` çağrısı
kendi başına yeni ve bağımsız bir kopya üretir, önceki çocuklardan habersiz. Bu
ders tek seferde birden çok çocuk üretmiyor, çünkü sonucun gösterdiği şey zaten
tek bir çift üzerinden tam olarak kanıtlanıyor; ekstra çocuklar aynı garantiyi
tekrarlardı, yeni bir sınıf ya da yeni bir sınır getirmezdi.

## Özet

- `fork`, çağrıldığı andaki bellek durumunun bir kopyasını çocuğa verir; kopya
  kurulduktan sonra iki taraf bağımsızdır.
- Bu bağımsızlık iki yönlüdür: çocuğun yazdığı bir değişken ebeveyne ulaşmaz,
  ebeveynin `fork` sonrası yazdığı bir değişken de çocuğa ulaşmaz.
- Aynı bağımsızlık tek bir tamsayıyla sınırlı değil; çok satırlı bir tampon gibi
  daha büyük bir yapı da aynı kurala uyar.
- `fflush(stdout)`, `fork`'tan önce zorunludur; aksi hâlde tamponlanmış içerik
  kopyalanır ve aynı satır iki kez yazılabilir.
- `WIFEXITED` ve `WEXITSTATUS` birlikte okunur: biri sürecin nasıl bittiğini,
  öteki hangi değerle bittiğini söyler; biri olmadan öteki yanıltıcı olabilir.
- `exec` ailesi süreç görüntüsünü baştan kurar ve geri dönmez; bu ders onu
  koşturmuyor, çünkü kopyalanan değişkenlerin sorusu `exec` sonrasında anlamsızlaşıyor.

## Sonraki Adım

Bir sonraki ders `fork`un kopyaladığı değişkenlerden bambaşka bir taşıma biçimine
geçiyor: bir sinyal işleyicisinin kaldırdığı bir bayrak, ana akışa yalnız bir kez
ulaşan bir aktarım.
