Bölüm 3: Süreçler

İşletim Sistemleri
Süreç kavramıBellek yerleşimiOluşturma: fork / exec / wait Sonlandırma, zombi, yetimSüreç durumlarıPCB Çizelgeleme kuyruklarıBağlam anahtarlamaPaylaşımlı bellek Mesaj geçirmeBorular ve soketlerYarış durumu ve klasik problemler
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Süreç modeli

Program ile süreç arasındaki farkı ve bir sürecin bellek yerleşimini açıklamak.

Yaşam döngüsü

Süreç durumlarını, geçişleri ve PCB'nin içeriğini tanımlamak.

UNIX API

fork(), exec(), wait(), exit() ve pipe() kullanan küçük programları izlemek.

Çizelgeleme altyapısı

Hazır/bekleme kuyruklarını ve bağlam anahtarlamanın maliyetini açıklamak.

IPC

Paylaşımlı bellek, mesaj geçirme, boru ve soketleri karşılaştırmak.

Eşzamanlılık sorunları

Yarış durumu, kritik bölge ve klasik problemleri tanımak (ayrıntısı Bölüm 6'da).

Süreç Kavramı

Süreç Nedir?

  • Süreç (process): yürütülmekte olan bir program örneği.
  • Program pasiftir (diskte duran yürütülebilir dosya); süreç aktiftir (program sayacı, yazmaçlar, bellek, açık dosyalar). Program belleğe yüklenince süreç olur.
  • Aynı programdan birden çok süreç oluşabilir (iki terminalde vim); her biri ayrı adres uzayı ve kaynaklara sahiptir, metin bölümü paylaşılabilir.
  • Tek iş parçacıklı bir süreç komutlarını sıralı yürütür; çok iş parçacıklı bir süreç çok çekirdekte paralel ilerleyebilir (Bölüm 4).
a.outprogram (diskte)pasif · dosyaçalıştırçalıştırsüreç · PID 4021metinveriyığınyığıtaktif · bellekte · kendi PCB'sisüreç · PID 4022metinveriyığınyığıtaktif · bellekte · kendi PCB'siaynı programdaniki ayrı süreç:ayrı adres uzayı,ayrı durum ve kaynaklar

Süreç Kavramı

Bellekte Süreç Yerleşimi

çekirdek alanıher sürece eşlenir; kullanıcı modunda erişilemezyığıt (stack)yerel değişkenler, parametreler, dönüş adresleriboş alanyığın (heap)malloc() / new ile çalışma anında ayrılan bellekBSSilk değer verilmemiş global / static (sıfırlanır)veri (data)ilk değer verilmiş global / static değişkenlermetin (text)programın makine kodu (salt okunur, paylaşılabilir)büyürbüyüryüksek adresadres 0'a yakın
Yığıt (stack) yüksek adreslerden aşağı, yığın (heap) yukarı doğru büyür. Program sayacı bellekte değil, işlemcide bir yazmaçtır; sürecin “şu anki konumu”nu gösterir. Gerçek düzende ASLR adresleri her çalıştırmada rastgele kaydırır.

Süreç Kavramı

Değişkenler Nereye Yerleşir?

#include <stdlib.h>
int sayac = 5;    /* data */
int tablo[1000];  /* bss */

int main(int argc, char *argv[]) {
    int yerel = 3;
    static int adet;
    int *p = malloc(4 * sizeof *p);
    p[0] = yerel + sayac;
    free(p);
    return 0;
}

Linux'ta size a.out metin/veri/BSS boyutlarını, cat /proc/PID/maps çalışan sürecin bölgelerini gösterir.

çekirdek alanıyığıt (stack)↓ boş ↑yığın (heap)BSSveri (data)metin (text)main()malloc(), free()(makine kodu)

Metin: main fonksiyonunun derlenmiş komutları; salt okunur ve aynı programın süreçleri arasında paylaşılabilir.

çekirdek alanıyığıt (stack)↓ boş ↑yığın (heap)BSSveri (data)metin (text)sayac = 5

Veri: ilk değeri verilmiş global sayac; değeri yürütülebilir dosyada saklanır.

çekirdek alanıyığıt (stack)↓ boş ↑yığın (heap)BSSveri (data)metin (text)tablo[1000]adet (static)

BSS: ilk değeri verilmemiş global ve static değişkenler; yükleyici sıfırla doldurur, dosyada yer kaplamaz.

çekirdek alanıyığıt (stack)↓ boş ↑yığın (heap)BSSveri (data)metin (text)argc, argvyerel = 3p (işaretçinin kendisi)dönüş adresi

Yığıt: main çağrı çerçevesi: parametreler, yerel değişkenler ve işaretçi p'nin kendisi.

çekirdek alanıyığıt (stack)↓ boş ↑yığın (heap)BSSveri (data)metin (text)*p → 16 bayt(malloc ile)

Yığın: malloc ile ayrılan 16 bayt; p yığıtta durur ama gösterdiği alan yığındadır. free ile geri verilir.

Süreç Kavramı

Sözde Paralellik ve Çoklu Programlama

(a) tek program sayacıABCDsüreçdeğişimi(b) dört bağımsız süreçABCDher birinin mantıksal PC'si var(c) bir anda yalnız biri çalışırzamanDCBA
  • Tek çekirdekte herhangi bir anda yalnızca bir süreç yürütülür; çizelgeleyici süreçler arasında hızla geçiş yaparak sözde paralellik yaratır.
  • Geçişler tipik olarak birkaç milisaniyelik zaman dilimleriyle olur (Linux CFS'de hedef gecikme birkaç ms – onlarca ms); kullanıcıya tüm süreçler aynı anda çalışıyor gibi görünür.
  • Kullanıcı için faydalıdır; ancak yönetimi zordur: süreçlerin ne zaman kesileceği öngörülemez, zaman varsayımı yapılmamalıdır.

Süreç Kavramı

Eşzamanlılık ve Paralellik

Eşzamanlılık(1 çekirdek)ABCABCABParalellik(2 çekirdek)AAABBBAACCCCAACCçekirdek 0çekirdek 1zaman
Eşzamanlılık (concurrency)

Birden çok iş aynı zaman aralığında ilerler; tek çekirdekte iç içe geçerek (interleaving).

Paralellik (parallelism)

Birden çok iş aynı anda yürütülür; birden çok çekirdek ya da işlemci gerekir.

2026 itibarıyla telefonlar bile 6–10 çekirdeklidir; yine de çalışan süreç sayısı (yüzlerce) çekirdek sayısından çok fazladır, bu yüzden çoklu programlama ve geçişler her sistemde vardır.

Süreç Kavramı

Örnek: Chrome'un Çok Süreçli Mimarisi

Tarayıcı süreciarayüz, sekmeler, izinler, diskOluşturucusite AOluşturucusite BGPU süreciçizimAğ hizmetiHTTP, önbellekEklenti / yardımcıuzantı, ses...IPC (Mojo)oluşturucular kum havuzunda (sandbox) çalışır;bir sekme çökse de tarayıcı ve diğer sekmeler yaşar
TarayıcıArayüzü, sekmeleri, disk ve izinleri yönetir; ayrıcalıklı tek süreç.
Oluşturucu (renderer)HTML, CSS, JavaScript işler; site yalıtımı ile site başına ayrı süreç.
GPU / ağ / yardımcıÇizim, ağ, uzantılar ayrı süreçlerde. Eski NPAPI eklentileri (Flash) 2020'de kaldırıldı.

Süreç Oluşturma

Süreç Oluşturma

Süreç oluşturan olaylar

  1. Sistem açılışı (init/systemd, arka plan hizmetleri: daemon).
  2. Çalışan bir sürecin süreç oluşturan sistem çağrısı yapması.
  3. Kullanıcının bir komut ya da uygulama başlatması.
  4. Toplu (batch) bir işin başlatılması.

Her durumda teknik olarak aynı şey olur: var olan bir süreç, yeni süreç oluşturan bir sistem çağrısı yürütür.

Sistem çağrıları

SistemÇağrı
UNIX / Linuxfork() + exec(); ayrıca posix_spawn(), Linux'ta clone()
WindowsCreateProcess(): oluşturma ve program yüklemeyi tek çağrıda yapar
Ata (parent) süreç çocuk (child) süreç oluşturur; çocuklar da kendi çocuklarını oluşturarak bir süreç ağacı oluşur. Her süreç tekil bir PID taşır.

Süreç Oluşturma

Süreç Ağacı

systemdPID 1kthreaddPID 2journaldPID 312sshdPID 890gdmPID 1003kworker/0:1PID 57sshd (oturum)PID 2101gnome-shellPID 1520bashPID 2105firefoxPID 2310vimPID 2230psPID 2241çekirdek işparçacıkları$ pstree -p$ ps -ef --forest

Linux açılışta PID 1 olarak systemd (ya da başka bir init) çalıştırır; PID 2 kthreadd çekirdek iş parçacıklarının atasıdır. PID değerleri örnektir.

Süreç Oluşturma

Ata ve Çocuk İlişkisi

Kaynak paylaşımı

Çocuk ata kaynaklarının tümünü, bir kısmını ya da hiçbirini paylaşmayabilir. UNIX'te çocuk açık dosya tanımlayıcılarını miras alır.

Yürütme

Ata ve çocuk eşzamanlı çalışabilir ya da ata wait() ile çocuğun bitmesini bekleyebilir.

Adres uzayı

UNIX fork(): çocuk atanın adres uzayının kopyasını alır (paylaşmaz). Ardından exec() ile yeni bir program yükleyebilir.

Kimlikler

getpid() kendi PID'ini, getppid() atanın PID'ini döndürür.

Düzeltme: eski slaytta “çocuk, ebeveynin adres uzayını kullanır” deniyordu. fork() sonrası adres uzayları ayrıdır; verimlilik için yalnızca fiziksel sayfalar yazılana kadar ortak tutulur (copy-on-write).

Süreç Oluşturma

fork(): Bir Çağrı, İki Dönüş

pid_t pid = fork();
if (pid < 0)        { perror("fork"); exit(1); }
else if (pid == 0)  { /* çocuk: fork 0 döndürdü */ }
else                { /* ata: pid = çocuğun PID'i */ }
ata (PID 1234)sayfa tablosufiziksel bellek (çerçeveler)sayfa Asayfa Bsayfa C

Başlangıç: ata sürecin sayfa tablosu fiziksel A, B, C sayfalarını gösteriyor.

ata (PID 1234)sayfa tablosuçocuk (PID 1235)sayfa tablosufiziksel bellek (çerçeveler)sayfa Asayfa Bsayfa Cyazmaya karşıkorumalı paylaşım(salt okunur)

fork(): çekirdek PCB'yi ve sayfa tablosunu kopyalar; sayfalar kopyalanmaz, ikisine de salt okunur eşlenir. Ata çocuğun PID'ini, çocuk 0 alır.

ata (PID 1234)sayfa tablosuçocuk (PID 1235)sayfa tablosufiziksel bellek (çerçeveler)sayfa Asayfa Bsayfa Csayfa C′kopyalayazmaya karşıkorumalı paylaşım(salt okunur)çocuk C'ye yazdı →sayfa hatası →çekirdek kopyaladı

Çocuk C sayfasına yazınca sayfa hatası oluşur; çekirdek yalnızca o sayfayı kopyalar (C′). Bu yazınca kopyala (COW) tekniğidir.

Süreç Oluşturma · Kodu adım adım çalıştır

Kod İzleme: fork() + wait()

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    int x = 10;
    pid_t pid = fork();
    if (pid == 0) {
        x = x + 5;
        printf("çocuk: x=%d\n", x);
        return 0;
    }
    wait(NULL);
    printf("ata: x=%d\n", x);
    return 0;
}
ata · 1234çalışıyor

Tek süreç var: ata (PID 1234), x = 10.

ata · 1234fork() → 1235çocuk · 1235fork() → 0

fork() çocuk 1235'i oluşturur. Atada pid = 1235, çocukta pid = 0; iki ayrı x var.

ata · 1234wait() → bekliyorçocuk · 1235hazır

Atada pid ≠ 0: if atlanır, wait(NULL) çocuk bitene kadar ata'yı bekliyor durumuna sokar.

ata · 1234bekliyorçocuk · 1235çalışıyor

Çocukta pid == 0 doğru: çocuk dalına girer. (Çocuk önce çalışmış olsaydı da sonuç aynı olurdu.)

ata · 1234bekliyorçocuk · 1235çalışıyor

Çocuk kendi kopyasını değiştirir: x = 15. Atanın x'i 10 kalır.

ata · 1234bekliyorçocuk · 1235çalışıyor

Çocuk yazdırır.

ata · 1234uyandı (SIGCHLD)çocuk · 1235zombi

Çocuk return 0 ile çıkar (exit(0)); kısa süre zombi olur, çekirdek atayı uyandırır.

ata · 1234çalışıyorçocuk · 1235biçildi

wait 1235 döndürür; çocuğun girdisi süreç tablosundan silinir.

ata · 1234çalışıyorçocuk · 1235biçildi

Ata yazdırır: kendi x'i hâlâ 10.

ata · 1234sonlandıçocuk · 1235biçildi

Ata da sonlanır. wait() sayesinde çıktı sırası her çalıştırmada aynıdır.

Süreç Oluşturma

wait() Olmadan Çıktı Sırası Belirsizdir

pid_t pid = fork();
if (pid == 0) printf("çocuk\n");
else          printf("ata\n");
Çizelge 1fork()ata: printfçocuk: printfataçocukÇizelge 2fork()çocuk: printfata: printfçocukatazaman (tek çekirdek)
fork sonrası hangi sürecin önce çalışacağını çizelgeleyici belirler; çok çekirdekte ikisi aynı anda bile çalışabilir. Programın doğruluğu bu sıraya bağlıysa wait() (ya da başka bir eşzamanlama) kullanılmalıdır.

Süreç Oluşturma

exec() Ailesi

exec öncesiexeclp("ls", ...) sonrasıPID 1235 · PPID 1234metin: a.out koduveri / BSSyığınyığıtaçık dosyalar 0,1,2cwd, kimliklerPID 1235 · PPID 1234metin: ls koduyeni veri / BSSyeni (boş) yığınyeni yığıt: argvaçık dosyalar 0,1,2cwd, kimlikleradres uzayı tümüyleyeniden yüklenirkorunan: PID, PPID,açık dosya tanımlayıcıları
ÇağrıArgümanlarPATH aramasıOrtam
execl / execvliste / dizihayırdevralınır
execlp / execvpliste / dizievetdevralınır
execle / execveliste / dizihayıraçıkça verilir

Hepsi sonunda execve() sistem çağrısını kullanır. Başarılı exec geri dönmez; dönüyorsa hata olmuştur.

Süreç Oluşturma · Kodu adım adım çalıştır

Kod İzleme: fork + exec + waitpid

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
  pid_t pid = fork();
  if (pid == 0) {
    execlp("ls", "ls", "-l", NULL);
    perror("execlp");  /* hata */
    exit(1);
  }
  int st;
  waitpid(pid, &st, 0);
  printf("bitti: %d\n", WEXITSTATUS(st));
  return 0;
}
ata · 1234fork() → 1235çocuk · 1235fork() → 0

fork(): iki süreç aynı a.out görüntüsünü çalıştırıyor.

ata · 1234waitpid → bekliyorçocuk · 1235hazır

Ata çocuk dalını atlar ve waitpid(1235, ...) ile bloke olur.

ata · 1234bekliyorçocuk · 1235exec: ls

Çocuk execlp çağırır: PATH içinde ls bulunur, adres uzayı /usr/bin/ls ile değiştirilir. PID hâlâ 1235.

ata · 1234bekliyorçocuk · 1235ls çalışıyor

Artık çocuk ls programıdır; 10–11. satırlar hiç çalışmaz (exec başarılı). Miras alınan stdout'a listeyi yazar.

ata · 1234uyandıçocuk · 1235çıktı: 0

ls exit(0) ile biter; waitpid döner ve st çıkış durumunu taşır.

ata · 1234çalışıyorçocuk · 1235biçildi

WEXITSTATUS(st) = 0 yazdırılır.

Süreç Oluşturma

Kabuk Döngüsü: fork → exec → wait

bashPID 2105$ ls → komutu okur

Kabuk istemi gösterir ve ls komutunu okur.

bashPID 2105bash (kopya)PID 2300fork()waitpid(2300)→ bekliyor

fork(): kabuğun bir kopyası (PID 2300) oluşur; ata waitpid ile beklemeye geçer.

bashPID 2105lsPID 2300fork()execvp("ls")görüntü ls ile değişirwaitpid(2300)→ bekliyor

Çocuk execvp("ls", argv) çağırır; görüntüsü ls ile değişir.

bashPID 2105lsPID 2300fork()execvp("ls")görüntü ls ile değişirwaitpid(2300)→ bekliyorlsçalışırstdout'a yazar

ls çalışır, miras aldığı terminal stdout'una yazar.

bashPID 2105lsPID 2300fork()execvp("ls")görüntü ls ile değişirwaitpid(2300)→ bekliyorlsçalışırstdout'a yazarexit(0) → SIGCHLDçocuk biçilir (reap)

ls çıkar; çekirdek atayı SIGCHLD ile bilgilendirir, waitpid çocuğu biçer.

bashPID 2105lsPID 2300fork()execvp("ls")görüntü ls ile değişirwaitpid(2300)→ bekliyorlsçalışırstdout'a yazarexit(0) → SIGCHLDçocuk biçilir (reap)$ yeni istem (prompt)

Kabuk yeni istemi gösterir. Komut sonuna & eklenirse kabuk beklemeden devam eder (arka plan işi).

Süreç Sonlandırma

Süreç Sonlandırma

NedenTürÖrnek
Normal çıkışgönüllüreturn 0, exit(0); derleyici işi bitti
Hata sonrası çıkışgönüllüdosya bulunamadı → exit(1)
Ölümcül hataistemsizgeçersiz bellek erişimi (SIGSEGV), sıfıra bölme (SIGFPE)
Başka süreç tarafındanistemsizkill(pid, SIGTERM), Windows'ta TerminateProcess()
  • exit(durum): C kütüphanesi tamponları boşaltır, atexit işleyicilerini çalıştırır, sonra _exit sistem çağrısı.
  • abort(): çağıran süreç kendine SIGABRT gönderir, anormal biter (çekirdek dökümü oluşabilir). Başka süreci sonlandırmak için kill() kullanılır.
  • Çekirdek sürecin belleğini, açık dosyalarını ve diğer kaynaklarını geri alır; çıkış durumu ata wait() çağırana kadar saklanır.
  • Bazı sistemlerde ata sonlanınca çocuklar da sonlandırılır (basamaklı sonlandırma); UNIX'te ise çocuklar yaşamaya devam eder.

Süreç Sonlandırma

Ölümcül Hatalar

HataSinyal
Geçersiz/izinsiz bellek erişimiSIGSEGV
Tamsayı sıfıra bölmeSIGFPE
Geçersiz komutSIGILL
Hizalama / veri yolu hatasıSIGBUS
Kapalı boruya yazmaSIGPIPE
  • İşletim sistemi donanım istisnasını yakalar, sürece ilgili sinyali gönderir; işlenmezse süreç sonlanır.
  • Süreç bellekten temizlenir, kaynakları serbest bırakılır.
  • Çekirdek dökümü (core dump) alınabilir: coredumpctl, gdb a.out core ile hatanın nedeni incelenir (crash dump analizi).
Düzeltme: bellek sızıntısı ve kaynakları serbest bırakmamak kendi başına ölümcül hata değildir; süreç çalışmayı sürdürür. Bellek tükenirse Linux OOM killer süreci SIGKILL ile sonlandırabilir.

Süreç Sonlandırma

Sinyallerle Sonlandırma: kill, pkill, killall

SinyalNoAnlamı
SIGTERM15kibarca bitir; süreç yakalayıp temizlik yapabilir (kill'in varsayılanı)
SIGKILL9zorla bitir; yakalanamaz, yok sayılamaz, temizlik yapılamaz
SIGINT2terminalde Ctrl+C
SIGTSTP20terminalde Ctrl+Z (duraklat); fg/bg sürdürür
SIGSTOP / SIGCONT19 / 18durdur (yakalanamaz) / sürdür

Numaralar x86/ARM Linux içindir; taşınabilir kodda adları kullanın.

$ ps -e | grep yaz
 1234 ?  00:00:03 yaz
$ kill 1234       # SIGTERM
$ kill -9 1234    # SIGKILL
$ pkill yaz       # ada göre
$ killall yaz
  • Önce SIGTERM, yanıt yoksa SIGKILL.
  • Sinyal göndermek için aynı kullanıcı ya da yetkili (root) olmak gerekir.

Süreç Sonlandırma

Zombi ve Yetim Süreçler

Senaryo 1: çocuk önce biterata · PID 100çalışıyorçocuk · PID 101çalışıyor

Ata (100) ve çocuk (101) çalışıyor.

Senaryo 1: çocuk önce biterata · PID 100çalışıyorçocuk · PID 101ZOMBİ (Z) · çıkış kodu saklı

Çocuk exit() ile bitti ama ata henüz wait() çağırmadı: çocuk zombidir. Belleği serbest; süreç tablosunda PID ve çıkış durumu bekler (ps'te Z / <defunct>).

Senaryo 1: çocuk önce biterata · PID 100wait() çağırırPID 101 süreç tablosundan silindidurum=0 alındı

Ata wait() çağırır, çıkış durumunu alır; çekirdek girdiyi siler (biçme, reaping). Zombi kalmaz.

Senaryo 2: ata önce bitersystemd · PID 1init / subreaperata (100) sonlandıçocuk · PID 101YETİM · PPID artık yok

Senaryo 2: ata (100) çocuktan önce sonlandı. Çocuk (101) hâlâ çalışıyor: yetim süreç.

Senaryo 2: ata önce bitersystemd · PID 1init / subreaperçocuk · PID 101evlat edinildi · PPID = 1

Çekirdek yetimi init/systemd'ye (PID 1) ya da tanımlı bir alt-biçici (subreaper) sürece bağlar; getppid() artık 1 döndürür.

Senaryo 2: ata önce bitersystemd · PID 1init / subreaperçocuk · PID 101bitti → systemd wait() ile biçer

Yetim bitince yeni atası onu wait() ile biçer; böylece zombi birikmez.

Zombi kaynak harcamaz ama PID tüketir; wait çağırmayan uzun ömürlü bir sunucu binlerce zombi biriktirip PID’leri tüketebilir. Çözüm: wait/waitpid ya da SIGCHLD işleyicisi.

Durumlar ve PCB

Süreç Durumları (Beş Durum Modeli)

Yeninew: oluşturuluyor
Hazırready: CPU'yu bekliyor
Çalışıyorrunning: komutlar yürütülüyor
Bekliyorwaiting/blocked: bir olayı bekliyor
Sonlandıterminated: yürütme bitti
kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Bir çekirdekte aynı anda yalnız bir süreç çalışıyor olabilir; pek çoğu hazır ya da bekliyor durumundadır.

Durumlar ve PCB

Durum Geçişleri Adım Adım

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Kullanıcı programı başlattı: PCB oluşturuluyor, bellek ayrılıyor → Yeni.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Çekirdek süreci kabul etti ve hazır kuyruğuna koydu → Hazır.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Çizelgeleyici bu süreci seçti, bağlamı yüklendi → Çalışıyor.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Süreç diskten read() istedi; veri gelene kadar CPU'yu bırakır → Bekliyor.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Disk kesmesi: veri hazır. Süreç doğrudan çalışmaz, hazır kuyruğuna döner → Hazır.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Yeniden seçildi → Çalışıyor.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Zamanlayıcı kesmesi: zaman dilimi doldu (önleyici çizelgeleme) → Hazır.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

Tekrar seçildi → Çalışıyor.

kabul (admitted)çizelgeleyici seçti (dispatch)kesme / zaman dilimi dolduG/Ç ya da olay bekleG/Ç ya da olay tamamlandıçıkış (exit)YeninewHazırreadyÇalışıyorrunningBekliyorwaitingSonlandıterminated

exit() çağırdı → Sonlandı; ata wait() edene kadar zombi.

Bekliyor → Çalışıyor geçişi yoktur: olay tamamlanan süreç önce hazır kuyruğuna girer.

Durumlar ve PCB

Linux'ta Süreç Durumları

ps STATAnlamModeldeki karşılığı
Rçalışıyor ya da çalışmaya hazırÇalışıyor + Hazır
Skesilebilir uyku (olay bekliyor)Bekliyor
Dkesilemez uyku (genelde disk G/Ç)Bekliyor
Tdurdurulmuş (SIGSTOP, hata ayıklayıcı)askıya alınmış
ZzombiSonlandı (biçilmedi)
Iboşta çekirdek iş parçacığı—
$ ps -eo pid,stat,comm
  PID STAT COMMAND
    1 Ss   systemd
  890 Ss   sshd
 2105 Ss   bash
 2241 R+   ps
 2307 Z    yaz <defunct>

Ek harfler: s oturum lideri, + ön plan grubu. Linux hazır ile çalışıyoru ayırmaz; ikisi de R.

Durumlar ve PCB

Süreç Kontrol Bloğu (PCB)

süreç durumusüreç kimliği (PID)program sayacıyazmaçlarçizelgeleme bilgisibellek yönetim bilgisiaçık dosyalar listesimuhasebe bilgisi (CPU süresi)• • •bağlam(context)
  • Çekirdek her süreç için bir PCB (process control block) tutar; sürece dair bilinmesi gereken her şey buradadır.
  • Tüm PCB'lerin oluşturduğu yapıya süreç tablosu denir (Tanenbaum'un tablosu bu girdilerin alanlarını listeler).
  • Süreç CPU'dan ayrıldığında program sayacı ve yazmaçlar PCB'ye kaydedilir; geri döndüğünde buradan yüklenir. Bu bilgiye bağlam denir.
  • PCB'ler çekirdek belleğinde durur; kullanıcı süreçleri doğrudan erişemez.

Durumlar ve PCB

PCB Alanları (Tanenbaum'a göre)

Süreç yönetimiBellek yönetimiDosya yönetimi
yazmaçlar · program sayacı · program durum sözcüğü (PSW) · yığıt işaretçisi · süreç durumu · öncelik · çizelgeleme parametreleri · PID · ata süreç · süreç grubu · sinyaller · başlama zamanı · kullanılan CPU zamanı · çocukların CPU zamanı · sonraki alarm zamanımetin, veri ve yığıt bölümlerinin (segment) işaretçileri; günümüzde sayfa tablosu işaretçisi ve bellek bölgeleri listesikök dizin · çalışma dizini · dosya tanımlayıcıları · kullanıcı kimliği (UID) · grup kimliği (GID)
PSW

Durum bayrakları (sıfır, taşma...), çalışma kipi (kullanıcı/çekirdek), kesme izinleri.

Süreç grubu

Birlikte yönetilen süreçler: kabuktaki bir boru hattı tek grup olur, Ctrl+C hepsine gider.

Sonraki alarm

alarm() ile istenen SIGALRM'nin teslim zamanı.

Durumlar ve PCB

Linux'ta PCB: task_struct

struct task_struct {          /* <linux/sched.h> */
    pid_t pid;                     /* süreç kimliği */
    unsigned int __state;          /* süreç durumu */
    int prio;                      /* öncelik */
    struct sched_entity se;        /* CFS/EEVDF bilgisi */
    struct task_struct *parent;    /* ata süreç */
    struct list_head children;     /* çocuklar */
    struct files_struct *files;    /* açık dosyalar */
    struct mm_struct *mm;          /* adres uzayı */
    /* ... yüzlerce alan daha ... */
};
task_structPID 1task_structPID 2105task_structPID 2241currenttasks: çift yönlü bağlı liste
  • Tüm görevler çift yönlü bağlı listededir; current o çekirdekte çalışan görevi gösterir.
  • Eski slayttaki long state / time_slice alanları güncel çekirdeklerde __state ve sched_entity içine taşınmıştır.

Çizelgeleme

Hazır ve Bekleme Kuyrukları

hazır kuyruğubaş · sonPCB 7PCB 2PCB 9disk G/Ç beklemebaş · sonPCB 3PCB 14ağ / klavye beklemebaş · sonPCB 6CPUPCB 5 çalışıyorkuyruklar PCB'lere bağlı listeler olarak tutulur
  • Çizelgeleyici (scheduler) hazır süreçler arasından CPU'ya geçecek olanı seçer; amaç CPU'yu meşgul tutmak ve kullanıcıya hızlı yanıt vermektir.
  • Hazır kuyruğu: bellekte olup yürütülmeyi bekleyen süreçler. Bekleme kuyrukları: belirli bir olayı (G/Ç bitişi, çocuğun sonlanması...) bekleyenler; her olay türü için ayrı kuyruk vardır.
  • Linux her çekirdeğe ayrı bir çalışma kuyruğu (runqueue) atar; CFS/EEVDF bunu kırmızı-siyah ağaçla tutar. Algoritmalar Bölüm 5'te.

Çizelgeleme

Kuyruk Diyagramı: Bir Sürecin Yolculuğu

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

Yeni süreç hazır kuyruğunun sonuna eklenir ve sırasını bekler.

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

Çizelgeleyici onu seçer (dispatch): süreç CPU'da çalışır.

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

G/Ç isteği yaptı: G/Ç bekleme kuyruğuna geçer; G/Ç bitince hazır kuyruğuna döner.

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

Zaman dilimi doldu: zamanlayıcı kesmesiyle CPU'dan alınır, doğrudan hazır kuyruğuna döner.

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

Çocuk oluşturup wait() çağırdı: çocuk bitene kadar bekler.

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

Bir kesmeyi/olayı bekliyor (örn. sleep(), ağ paketi).

hazır kuyruğuCPUsonlan (exit)G/Ç bekleme kuyruğuG/Ç isteğizaman dilimi dolduçocuğun bitmesini bekleçocuk oluştur (fork)kesme bekleme kuyruğukesme bekle

İşi bitti: exit() ile sistemden çıkar; PCB'si ata wait edince silinir.

Çizelgeleme

CPU'ya Bağlı ve G/Ç'ye Bağlı Süreçler

(a) CPU'ya bağlı(b) G/Ç'ye bağlıCPU patlaması (burst)G/Ç beklemezaman
CPU'ya bağlı (CPU-bound)

Uzun hesaplama patlamaları, seyrek G/Ç: derleme, video kodlama, bilimsel hesap.

G/Ç'ye bağlı (I/O-bound)

Kısa CPU patlamaları, sık bekleme: editör, web sunucusu, veritabanı istemcisi.

  • CPU'lar disklerden çok daha hızlı geliştiği için süreçler giderek G/Ç'ye bağlı hâle gelir.
  • G/Ç'ye bağlı süreç hazır olunca hemen CPU almalı (hızlı bağlam anahtarlama); iki tür dengeli karıştırılmalıdır.

Çizelgeleme

Çoklu Programlamanın Modellenmesi

204060801000246810bellekteki süreç sayısı (çoklu programlama derecesi) nCPU %%20 G/Ç bekleme%50 G/Ç bekleme%80 G/Ç bekleme

Bir süreç zamanının p kesrini G/Ç bekleyerek geçirsin. Bellekte n bağımsız süreç varsa hepsinin aynı anda beklemesi olasılığı pⁿ'dir:

CPU kullanımı = 1 − pⁿ

Örn. p = 0,8: n = 1 → %20, n = 4 → %59, n = 10 → %89.

Kaba bir model: süreçlerin bağımsız olduğu varsayılır.

Çizelgeleme

Bağlam Anahtarlama (Context Switch)

süreç P₀işletim sistemisüreç P₁zaman

P₀ çalışıyor (kalın çizgi); P₁ hazır kuyruğunda bekliyor.

süreç P₀işletim sistemisüreç P₁kesme ya da sistem çağrısıdurumu PCB₀'a kaydetzaman

Kesme ya da sistem çağrısı: CPU çekirdek kipine geçer, P₀'ın program sayacı ve yazmaçları PCB₀'a kaydedilir.

süreç P₀işletim sistemisüreç P₁kesme ya da sistem çağrısıdurumu PCB₀'a kaydetdurumu PCB₁'den yüklezaman

Çizelgeleyici P₁'i seçer; P₁'in bağlamı PCB₁'den yüklenir (adres uzayı da değişir).

süreç P₀işletim sistemisüreç P₁kesme ya da sistem çağrısıdurumu PCB₀'a kaydetdurumu PCB₁'den yükleboşta (idle)zaman

P₁ çalışıyor; P₀ boşta (hazır ya da bekliyor).

süreç P₀işletim sistemisüreç P₁kesme ya da sistem çağrısıdurumu PCB₀'a kaydetdurumu PCB₁'den yükledurumu PCB₁'e kaydetzaman

Yeni kesme: P₁'in durumu PCB₁'e kaydedilir.

süreç P₀işletim sistemisüreç P₁kesme ya da sistem çağrısıdurumu PCB₀'a kaydetdurumu PCB₁'den yükledurumu PCB₁'e kaydetdurumu PCB₀'dan yüklezaman

PCB₀'dan geri yüklenir; P₀ kaldığı yerden devam eder. Aradaki gri bölümlerde hiçbir kullanıcı işi yapılmaz: ek yük.

Çizelgeleme

Bağlam Anahtarlamanın Maliyeti

  • CPU başka sürece geçerken eski sürecin durumunu kaydetmeli, yenisininkini yüklemelidir.
  • Bu süre saf ek yüktür (overhead): bu sırada sistem yararlı iş yapmaz.
  • PCB ve işletim sistemi ne kadar karmaşıksa anahtarlama o kadar uzun sürer.
Doğrudan maliyet

Yazmaçların kaydı/yüklenmesi, çekirdek kodu: güncel Linux'ta tipik olarak birkaç mikrosaniye.

Dolaylı maliyet

Önbellek ve TLB'nin “soğuması”: yeni süreç ilk anlarda daha çok önbellek ıskası yaşar; çoğu zaman doğrudan maliyetten büyüktür.

Donanım desteği

Birden çok yazmaç kümesi, TLB'de adres uzayı etiketi (ASID / PCID) ile boşaltmadan kaçınma.

Çizelgeleme

Kesmenin Ele Alınması

Bir G/Ç kesmesi geldiğinde işletim sisteminin en alt katmanında olanlar (Tanenbaum):

1. Donanım program sayacı, PSW vb. değerleri yığıta kaydeder.

Kesme isteği komut sınırında kabul edilir; CPU çekirdek kipine geçer.

2. Donanım kesme vektöründen yeni program sayacını yükler.

Vektör, her kesme türü için işleyicinin adresini tutan tablodur.

3. Assembly yordamı yazmaçları kaydeder.

Yazmaçlar o anki sürecin PCB'sine (ya da çekirdek yığıtına) yazılır.

4. Assembly yordamı yeni bir yığıt hazırlar.

Kesme işleyicisi çekirdek yığıtında çalışır.

5. C kesme hizmet yordamı çalışır.

Genelde girdiyi okur/tamponlar; bekleyen süreci hazır yapar.

6. Çizelgeleyici sıradaki süreci seçer.

Uyanan süreç daha öncelikliyse kesilen süreç yerine o seçilebilir.

7. C yordamı assembly koduna döner.

Kalan iş, seçilen sürecin bağlamının yüklenmesidir.

8. Assembly yordamı yeni süreci başlatır.

Yazmaçlar ve bellek eşlemesi yüklenir, kullanıcı kipine dönülür.

Süreçler Arası İletişim

Süreçler Arası İletişim

  • Süreçler bağımsız (başkasını etkilemez) ya da işbirlikçi (veri paylaşır) olabilir.
  • İşbirliği nedenleri: bilgi paylaşımı, hızlanma (işi parçalamak), modülerlik, güvenlik ve yalıtım (Chrome).
  • İşbirlikçi süreçler bir IPC (interprocess communication) mekanizmasına ihtiyaç duyar.

Üç temel soru

  1. Bir süreç diğerine bilgiyi nasıl iletir?
  2. Süreçler birbirinin yoluna çıkmasın: aynı koltuğa iki rezervasyon nasıl önlenir?
  3. Bağımlılıklarda doğru sıra nasıl sağlanır? (Nişan almadan ateş edilmemeli; üretilmeden tüketilmemeli.)
İki temel model: paylaşımlı bellek ve mesaj geçirme. Son iki soru iş parçacıkları için de geçerlidir (Bölüm 6).

Süreçler Arası İletişim

Paylaşımlı Bellek ve Mesaj Geçirme

(a) paylaşımlı bellek(b) mesaj geçirmesüreç Apaylaşımlı bölgeiki adres uzayına da eşlenirsüreç Bçekirdekshm_open + mmapsüreç Asüreç Bçekirdek: mesaj kuyruğum₀m₁m₂

Paylaşımlı bellek: çekirdek bir bölge oluşturur ve iki sürecin adres uzayına eşler (yalnızca kurulumda sistem çağrısı).

(a) paylaşımlı bellek(b) mesaj geçirmesüreç Apaylaşımlı bölgeiki adres uzayına da eşlenirsüreç Bçekirdekyazsüreç Asüreç Bçekirdek: mesaj kuyruğum₀m₁m₂

A bölgeye doğrudan yazar: çekirdek devrede değildir, bu yüzden çok hızlıdır.

(a) paylaşımlı bellek(b) mesaj geçirmesüreç Apaylaşımlı bölgeiki adres uzayına da eşlenirsüreç Bçekirdekokusüreç Asüreç Bçekirdek: mesaj kuyruğum₀m₁m₂

B aynı veriyi okur. Ancak eşzamanlamayı (kim ne zaman yazar?) süreçler kendisi sağlamalıdır.

(a) paylaşımlı bellek(b) mesaj geçirmesüreç Apaylaşımlı bölgeiki adres uzayına da eşlenirsüreç Bçekirdeksüreç Asüreç Bçekirdek: mesaj kuyruğum₀m₁m₂m₃send(m₃)

Mesaj geçirme: A send(m₃) der; çekirdek mesajı kendi kuyruğuna kopyalar.

(a) paylaşımlı bellek(b) mesaj geçirmesüreç Apaylaşımlı bölgeiki adres uzayına da eşlenirsüreç Bçekirdeksüreç Asüreç Bçekirdek: mesaj kuyruğum₁m₂m₃receive() → m₀

B receive() ile sıradaki mesajı alır. Her mesajda sistem çağrısı ve kopyalama vardır; ama eşzamanlama çekirdekçe sağlanır, ağ üzerinden de çalışır.

Süreçler Arası İletişim

POSIX Paylaşımlı Bellek

/* üretici */
int fd = shm_open("/os03", O_CREAT|O_RDWR, 0600);
ftruncate(fd, 4096);
char *p = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
               MAP_SHARED, fd, 0);
strcpy(p, "merhaba");   /* doğrudan yaz */

/* tüketici (başka süreç) */
int fd = shm_open("/os03", O_RDONLY, 0);
char *p = mmap(NULL, 4096, PROT_READ,
               MAP_SHARED, fd, 0);
printf("%s\n", p);
shm_unlink("/os03");    /* sil */
  • shm_open adlı bir nesne oluşturur (Linux'ta /dev/shm altında).
  • ftruncate boyutu belirler, mmap adres uzayına eşler.
  • Sonrası sıradan bellek erişimidir.
  • Tüketici üreticiden önce okursa boş görür: gerçek kodda semafor gibi bir eşzamanlama gerekir.
  • Eski System V API: shmget/shmat.

Süreçler Arası İletişim

Mesaj Geçirme Tasarımı

İki temel işlem: send(mesaj) ve receive(mesaj). Önce bir iletişim bağı kurulmalıdır.

BoyutSeçenekler
Fiziksel bağpaylaşımlı bellek, donanım veri yolu, ağ
Adlandırmadoğrudan (sürecin adı) · dolaylı (posta kutusu / port)
Eşzamanlılıkbloklayan (senkron): gönderen alınana kadar, alıcı mesaj gelene kadar bekler · bloklamayan (asenkron): hemen döner
Tamponlamasıfır kapasite (gönderen bekler: buluşma/rendezvous) · sınırlı (kuyruk doluysa bekler) · sınırsız (hiç beklemez)
Bloklayan send + bloklayan receive = buluşma (rendezvous): üretici-tüketici problemi kendiliğinden çözülür. Örnekler: POSIX mq_send/mq_receive, Mach portları, Windows ALPC.

Süreçler Arası İletişim

Doğrudan ve Dolaylı İletişim

doğrudan: send(Q, m)PQtek bağlantı · bir çift süreçdolaylı: send(A, m)posta kutusu Aport / kuyrukPQRçok süreç · çok bağlantı
Doğrudan
  • send(P, m): P'ye gönder; receive(Q, m): Q'dan al.
  • Bağlantı otomatik kurulur, tam olarak bir çift sürece aittir.
  • Her çift arasında tek bağlantı; genelde çift yönlü.
Dolaylı (posta kutusu)
  • send(A, m) / receive(A, m): A kutusu üzerinden.
  • Her posta kutusunun tekil kimliği vardır; aynı kutuyu paylaşanlar haberleşir.
  • Bir bağlantı çok sürece, bir çift çok bağlantıya sahip olabilir; tek ya da çift yönlü.

Düzeltme: eski sunumda ikinci slayt da “Doğrudan İletişim” başlığını taşıyordu; posta kutusu modeli dolaylı iletişimdir.

Süreçler Arası İletişim

Borular (Pipes)

atafd[0] kapalı · fd[1] yazarçocukfd[0] okur · fd[1] kapalıboru: çekirdekte FIFO tamponwrite(fd[1])read(fd[0])yazma ucu → okuma ucu (tek yönlü)

Tasarım soruları

  • Tek yönlü mü (simplex), çift yönlü mü?
  • Çift yönlüyse yarı mı (half), tam mı (full duplex)?
  • Süreçler arasında ata-çocuk ilişkisi gerekli mi?
  • Ağ üzerinden kullanılabilir mi?
Sıradan (adsız)Adlandırılmış (FIFO)
Erişimyalnız oluşturan ve çocuklarıdosya adı bilen her süreç
Yöntek yönlüUNIX: yarı çift yönlü; Windows: tam çift yönlü
Ömürsüreçler kapatınca bitermkfifo; dosya sisteminde kalır
AğhayırUNIX hayır; Windows evet

Süreçler Arası İletişim · Kodu adım adım çalıştır

Kod İzleme: pipe() + fork()

int main(void) {
  int fd[2];
  char buf[32];
  pipe(fd);
  pid_t pid = fork();
  if (pid == 0) {   /* çocuk: okur */
    close(fd[1]);
    ssize_t n = read(fd[0], buf, sizeof buf - 1);
    buf[n] = '\0';
    printf("çocuk aldı: %s\n", buf);
    close(fd[0]);
    return 0;
  }
  close(fd[0]);     /* ata: yazar */
  write(fd[1], "merhaba", 7);
  close(fd[1]);
  wait(NULL);
  return 0;
}
ata 1234çalışıyor(boş)4 (w)3 (r)

pipe(fd): çekirdek boş bir tampon açar; okuma ucu fd[0]=3, yazma ucu fd[1]=4 (0–2 stdin/stdout/stderr).

ata 1234çalışıyorçocuk 1235çalışıyor(boş)4 (w)3 (r)4 (w)3 (r)

fork(): çocuk tanımlayıcıları miras alır; şimdi her iki uç da iki süreçte açık.

ata 1234çalışıyorçocuk 1235çalışıyor(boş)4 (w)3 (r)3 (r)

Çocuk kullanmayacağı yazma ucunu kapatır.

ata 1234çalışıyorçocuk 1235read → bekliyor(boş)4 (w)3 (r)3 (r)

Boru boş: read çocuğu bloke eder.

ata 1234çalışıyorçocuk 1235bekliyor(boş)4 (w)3 (r)

Ata okuma ucunu kapatır.

ata 1234çalışıyorçocuk 1235uyandımerhaba4 (w)3 (r)

Ata 7 bayt yazar; ≤ PIPE_BUF yazımlar bölünmez (atomik). Bekleyen okuyucu uyanır.

ata 1234wait → bekliyorçocuk 1235hazırmerhaba3 (r)

Ata yazma ucunu kapatır (okuyucu ileride EOF görebilsin) ve wait ile bekler.

ata 1234bekliyorçocuk 1235çalışıyor(boş)3 (r)

read 7 döndürür; veri tampondan çocuğun buf dizisine kopyalanır.

ata 1234bekliyorçocuk 1235çalışıyor(boş)3 (r)

Sonlandırıcı '\0' eklenir ve yazdırılır.

ata 1234çalışıyorçocuk 1235bitti(boş)

Çocuk okuma ucunu kapatıp çıkar; ata wait'ten döner ve biter. Yalnız çocuk yazdığı için çıktı belirlenimlidir.

Süreçler Arası İletişim

Kabukta Boru Hattı: ls | wc -l

bashpipe(), 2 × fork(), waitlsdup2(fd[1], 1) → stdoutwc -ldup2(fd[0], 0) → stdinboru (64 KiB tampon)her iki süreç de kullanmadığı uçları kapatmalı; aksi hâlde wc EOF göremez
int fd[2]; pipe(fd);
if (fork() == 0) { dup2(fd[1], 1); close(fd[0]); close(fd[1]);
                   execlp("ls", "ls", NULL); }
if (fork() == 0) { dup2(fd[0], 0); close(fd[0]); close(fd[1]);
                   execlp("wc", "wc", "-l", NULL); }
close(fd[0]); close(fd[1]); wait(NULL); wait(NULL);

Hata denetimleri kısalık için çıkarıldı. dup2(eski, yeni) yeni tanımlayıcıyı eskinin kopyası yapar; programlar stdin/stdout ile çalıştığı için boruyu fark etmez.

Süreçler Arası İletişim

Soketler

istemci 146.86.5.20soket146.86.5.20:1625web sunucusu 161.25.19.8soket161.25.19.8:80TCP bağlantısıgeçici port ↔ iyi bilinen port
  • Soket: iletişimin uç noktası; IP adresi + port ile tanımlanır (161.25.19.8:80).
  • İletişim bir çift soket arasında olur; port, makinedeki hizmeti ayırt eder.
  • 1024'ün altındaki portlar iyi bilinen hizmetlere ayrılmıştır (SSH 22, HTTP 80, HTTPS 443); bağlanmak için yetki gerekir.
  • 127.0.0.1 (IPv6: ::1) geri döngü adresidir: aynı makinedeki süreçler.
  • TCP bağlantılı, sıralı, güvenilir bayt akışı; UDP bağlantısız datagram.
  • Aynı makinede UNIX alan soketleri (AF_UNIX) daha hızlıdır; Docker, systemd, X/Wayland bunları kullanır.

Eşzamanlılık Sorunları

Yarış Durumu: Yazıcı Biriktirme Dizini

biriktirme dizini (spooler directory)4abc5prog.c6prog.n7—out = 4in = 7süreç Anext_free = ?süreç Bnext_free = ?

Yazdırılacak dosyalar sırayla yuvalara yazılır. out sıradaki yazdırılacak, in ilk boş yuva.

biriktirme dizini (spooler directory)4abc5prog.c6prog.n7—out = 4in = 7süreç Anext_free = 7süreç Bnext_free = ?

A in'i okur (7) ve yerel next_free'ye koyar; tam o anda zaman dilimi biter.

biriktirme dizini (spooler directory)4abc5prog.c6prog.n7—out = 4in = 7süreç Anext_free = 7süreç Bnext_free = 7

B de in'i okur: yine 7!

biriktirme dizini (spooler directory)4abc5prog.c6prog.n7b.txtout = 4in = 8süreç Anext_free = 7süreç Bnext_free = 7

B dosya adını 7. yuvaya yazar, in = 8 yapar.

biriktirme dizini (spooler directory)4abc5prog.c6prog.n7a.txtout = 4in = 8süreç Anext_free = 7süreç Bnext_free = 7

A kaldığı yerden devam eder: next_free = 7 olduğundan 7. yuvaya yazar, B'nin girdisini ezer; in = 8.

biriktirme dizini (spooler directory)4abc5prog.c6prog.n7a.txtout = 4in = 8süreç Anext_free = 7süreç Bnext_free = 7B'nin dosyası hiç yazdırılmaz!

Dizin tutarlı görünür ama B'nin çıktısı kaybolmuştur. Sonuç kimin ne zaman çalıştığına bağlıdır: yarış durumu.

Yarış durumu (race condition): iki ya da daha çok süreç paylaşılan veriye eriştiğinde nihai sonucun yürütme sırasına bağlı olması.

Eşzamanlılık Sorunları

Tekrarlanamaz Yürütme (Non-reproducible)

/* Süreç 1 */     /* Süreç 2 */
repeat           repeat
  n = n + 1;       print(n);
                   n = 0;

n = 5 iken olası sıralar:

  • n=n+1; print; n=0 → 6 yazar
  • print; n=0; n=n+1 → 5 yazar, n=1
  • print; n=n+1; n=0 → 5 yazar, artış kaybolur

Tek bir C satırı bile (count++) makinede üç komuttur:

T₀üreticireg₁ = countreg₁ = 5

Başta count = 5. Üretici count++ için önce değeri yazmaca yükler.

T₀üreticireg₁ = countreg₁ = 5T₁üreticireg₁ = reg₁ + 1reg₁ = 6

Üretici yazmacı artırır: 6. Henüz belleğe yazmadı ve kesildi.

T₀üreticireg₁ = countreg₁ = 5T₁üreticireg₁ = reg₁ + 1reg₁ = 6T₂tüketicireg₂ = countreg₂ = 5

Tüketici count--: bellekteki değer hâlâ 5.

T₀üreticireg₁ = countreg₁ = 5T₁üreticireg₁ = reg₁ + 1reg₁ = 6T₂tüketicireg₂ = countreg₂ = 5T₃tüketicireg₂ = reg₂ − 1reg₂ = 4

Tüketici yazmacı azaltır: 4.

T₀üreticireg₁ = countreg₁ = 5T₁üreticireg₁ = reg₁ + 1reg₁ = 6T₂tüketicireg₂ = countreg₂ = 5T₃tüketicireg₂ = reg₂ − 1reg₂ = 4T₄üreticicount = reg₁count = 6

Üretici belleğe yazar: count = 6.

T₀üreticireg₁ = countreg₁ = 5T₁üreticireg₁ = reg₁ + 1reg₁ = 6T₂tüketicireg₂ = countreg₂ = 5T₃tüketicireg₂ = reg₂ − 1reg₂ = 4T₄üreticicount = reg₁count = 6T₅tüketicicount = reg₂count = 4

Tüketici üzerine yazar: count = 4. Doğrusu 5'ti! Sıra değişseydi 6 çıkabilirdi.

Eşzamanlılık Sorunları

Kritik Bölge ve Karşılıklı Dışlama

  • Kritik bölge: programın paylaşılan veriye eriştiği kod parçası.
  • Karşılıklı dışlama: bir süreç kritik bölgedeyken diğerlerinin girmesini engellemek.

İyi bir çözümün dört koşulu (Tanenbaum):

  1. İki süreç aynı anda kritik bölgede olamaz.
  2. CPU hızı ya da sayısı hakkında varsayım yapılmaz.
  3. Kritik bölge dışındaki süreç başkasını engelleyemez.
  4. Hiçbir süreç girmek için sonsuza dek beklemez.
Süreç ASüreç BA kritik bölgedeB blokeB kritik bölgedeT1A girerT2B girmeye çalışırT3A çıkar, B girerT4B çıkar

Eşzamanlılık Sorunları · Kodu adım adım çalıştır

Peterson Çözümü

bool flag[2] = { false, false };
int turn;

void enter(int i) {
    int j = 1 - i;
    flag[i] = true;   /* girmek istiyorum */
    turn = j;         /* ama önce sen */
    while (flag[j] && turn == j)
        ;             /* meşgul bekle */
}
void leave(int i) {
    flag[i] = false;
}
P0dışarıdaP1dışarıdakritik bölge: boş

Başlangıç: kimse girmek istemiyor.

P0flag[0]=trueP1dışarıdakritik bölge: boş

P0 girmek istediğini bildirir.

P0kesildiP1flag[1]=truekritik bölge: boş

Kesme! P1 de hemen ardından girmek istediğini bildirir.

P0turn=1P1kesildikritik bölge: boş

P0 sırayı kibarca P1'e verir.

P0kesildiP1turn=0kritik bölge: boş

P1 de sırayı P0'a verir. turn'e en son yazan bekleyecek.

P0kritik bölgedeP1hazırkritik bölge: P0

P0: flag[1] && turn == 1? turn = 0 → yanlış: P0 girer.

P0kritik bölgedeP1dönüyorkritik bölge: P0

P1: flag[0] && turn == 0 doğru → döngüde döner (CPU harcar).

P0çıktıP1dönüyorkritik bölge: boş

P0 çıkarken flag[0] = false yapar.

P0dışarıdaP1kritik bölgedekritik bölge: P1

P1'in koşulu yanlış olur: P1 girer. Karşılıklı dışlama ve ilerleme sağlandı.

Uyarı: modern CPU ve derleyiciler yükleme/saklama sırasını değiştirebilir; Peterson ancak _Atomic değişkenler (sıralı tutarlılık) ya da bellek bariyerleriyle doğru çalışır. Pratikte donanım atomik komutları ve kilitler kullanılır.

Eşzamanlılık Sorunları

Meşgul Bekleme ve Uyu/Uyandır

Meşgul beklemenin sorunları

  • Kilit beklerken CPU boşa döner.
  • Öncelik tersinmesi (priority inversion): düşük öncelikli L kritik bölgedeyken yüksek öncelikli H gelip dönmeye başlarsa, H hep seçildiği için L hiç çalışıp çıkamaz; H sonsuza dek döner.

Çözüm: bloke et

  • sleep(): çağıranı bekliyor durumuna geçirir, CPU'yu bırakır.
  • wakeup(p): uyuyan p'yi hazır yapar.
  • Ama yanlış kullanılırsa uyandırma sinyali kaybolabilir (sonraki slaytlar).
Düzeltme: eski slaytta bu durum “ölümcül kilitlenme (deadlock)” olarak geçiyordu; literatürdeki adı öncelik tersinmesidir. 1997 Mars Pathfinder sıfırlanmaları bunun ünlü örneğidir; çözüm öncelik kalıtımı (priority inheritance).

Eşzamanlılık Sorunları

Üretici-Tüketici (Sınırlı Tampon) Problemi

  • İki süreç sabit boyutlu (N) bir tamponu paylaşır: üretici yazar, tüketici okur.
  • Sınırlı tampon: doluysa üretici, boşsa tüketici bekler. Sınırsız tampon: yalnız tüketici bekler.
01234567out (tüketici)in (üretici)dairesel: (i + 1) % Ncount = 0boş: tüketici beklemeli

Tampon boş (count = 0): tüketici okuyamaz, beklemelidir.

A0B1C234567out (tüketici)in (üretici)dairesel: (i + 1) % Ncount = 3

Üretici A, B, C ekler: her eklemede in = (in + 1) % N.

0B1C234567out (tüketici)in (üretici)dairesel: (i + 1) % Ncount = 2

Tüketici A'yı alır: out = 1, count = 2.

I0B1C2D3E4F5G6H7out (tüketici)in (üretici)dairesel: (i + 1) % Ncount = 8dolu: üretici beklemeli

Üretici D…I ekler; in başa sarar (1). count = N = 8: tampon dolu, üretici beklemelidir.

I012D3E4F5G6H7out (tüketici)in (üretici)dairesel: (i + 1) % Ncount = 6

Tüketici B ve C'yi alır: out = 3, count = 6; üretici yeniden yazabilir.

Eşzamanlılık Sorunları · Kodu adım adım çalıştır

Ölümcül Yarış: Kayıp Uyandırma

#define N 100
int count = 0;
void producer(void) {
    while (TRUE) {
        item = produce_item();
        if (count == N) sleep();
        insert_item(item);
        count = count + 1;
        if (count == 1) wakeup(consumer);
    }
}
void consumer(void) {
    while (TRUE) {
        if (count == 0) sleep();
        item = remove_item();
        count = count - 1;
        if (count == N - 1) wakeup(producer);
        consume_item(item);
    }
}
üreticihazırtüketiciuyumak üzerecount = 0

Tampon boş. Tüketici count'u okur: 0. Tam sleep() çağıracakken kesilir.

üreticiçalışıyortüketicikesildicount = 0

Üretici bir öğe üretir ve tampona koyar.

üreticiçalışıyortüketicikesildicount = 1

count 1 olur.

üreticiwakeup → kayıptüketicikesildicount = 1

count == 1 → tüketici uyuyor sanılır, wakeup(consumer) gönderilir. Ama tüketici henüz uyumadı: sinyal kaybolur.

üreticihazırtüketiciuyuyorcount = 1

Tüketici kaldığı yerden devam eder: eski kararına göre sleep() çağırır ve uyur.

üreticiçalışıyortüketiciuyuyorcount = 100

Üretici tamponu doldurur; tüketici uyuduğu için kimse okumuyor.

üreticiuyuyortüketiciuyuyorcount = 100

count == N → üretici de uyur. İkisi de sonsuza dek uyur. Sorun: count testi ile sleep bölünmez (atomik) değil.

Eşzamanlılık Sorunları

Semaforlarla Üretici-Tüketici

semaphore mutex = 1, empty = N, full = 0;
void producer(void) {               void consumer(void) {
  while (TRUE) {                      while (TRUE) {
    item = produce_item();              down(&full);
    down(&empty);                       down(&mutex);
    down(&mutex);                       item = remove_item();
    insert_item(item);                  up(&mutex);
    up(&mutex);                         up(&empty);
    up(&full);                          consume_item(item);
  }                                   }
}                                   }
empty / full

Boş ve dolu yuvaları sayar: sıralamayı (kim bekler) sağlar. Sayaç ile bekleme atomik olduğu için uyandırma kaybolmaz.

mutex

Tampona aynı anda tek erişim: karşılıklı dışlama.

Dikkat

Üreticide down(&mutex) ile down(&empty) yer değiştirirse tampon doluyken kilitlenme olur.

Semafor, mutex ve monitörler Bölüm 6'da ayrıntılı işlenecektir.

Eşzamanlılık Sorunları

Yemek Yiyen Filozoflar (Dining Philosophers)

F0F1F2F3F45 filozof, 5 çatal.Yemek için iki çatal gerekir:sol ve sağ.

Her filozof düşünür ya da yer; yemek için solundaki ve sağındaki iki çatalı da alması gerekir.

F0F1F2F3F4Hepsi aynı anda acıkır vesol çatalını alır.Masada çatal kalmaz.

Saf çözüm: pick_up_left_fork(); pick_up_right_fork();. Hepsi aynı anda acıkıp solu alırsa...

F0F1F2F3F4Herkes sağındakini bekler:döngüsel bekleme →ölümcül kilitlenme (deadlock).

...her biri sağındakini bekler; kimse bırakmaz. Döngüsel bekleme: ölümcül kilitlenme. Rastgele bekleyip tekrar denemek ise açlığa (starvation) yol açabilir.

Dijkstra (1965): sınırlı kaynakların (G/Ç aygıtları gibi) birden çok süreç arasında kilitlenmeden paylaşılmasını modeller.

Eşzamanlılık Sorunları

Filozoflar: Durum Dizisi ile Çözüm

#define N 5
#define LEFT  (i + N - 1) % N
#define RIGHT (i + 1) % N
int state[N];          /* THINKING, HUNGRY, EATING */
semaphore mutex = 1;   /* state[] için */
semaphore s[N];        /* filozof başına, başta 0 */

void take_forks(int i) {           void test(int i) {
    down(&mutex);                      if (state[i] == HUNGRY &&
    state[i] = HUNGRY;                     state[LEFT] != EATING &&
    test(i);    /* 2 çatal al */           state[RIGHT] != EATING) {
    up(&mutex);                            state[i] = EATING;
    down(&s[i]); /* alamadıysa */          up(&s[i]);
}                                      }
void put_forks(int i) {            }
    down(&mutex);
    state[i] = THINKING;           void philosopher(int i) {
    test(LEFT);  /* komşular */        while (TRUE) {
    test(RIGHT); /* yiyebilir mi? */       think(); take_forks(i);
    up(&mutex);                            eat();   put_forks(i);
}                                      }
                                   }

Filozof ancak iki komşusu da yemiyorsa yer; çatallar tek tek değil, durum dizisi üzerinden birlikte alınır. Kilitlenme olmaz ve en fazla ⌊N/2⌋ = 2 filozof aynı anda yer.

Eşzamanlılık Sorunları

Okur-Yazar Problemi

semaphore mutex = 1;   /* rc için */          void reader(void) {
semaphore db = 1;      /* veritabanı için */    while (TRUE) {
int rc = 0;            /* okuyan sayısı */          down(&mutex);
                                                  rc = rc + 1;
void writer(void) {                               if (rc == 1) down(&db);
  while (TRUE) {                                  up(&mutex);
    think_up_data();                              read_data_base();
    down(&db);                                    down(&mutex);
    write_data_base();                            rc = rc - 1;
    up(&db);                                      if (rc == 0) up(&db);
  }                                               up(&mutex);
}                                                 use_data_read();
                                                }
                                              }
  • Veritabanı erişimini modeller: çok okur aynı anda okuyabilir; yazar ise tek başına çalışmalıdır.
  • İlk okur db'yi kilitler, son okur bırakır.
Dezavantaj: okurlar sürekli gelirse rc hiç 0 olmaz, yazar açlık çeker. Çözüm: yazar öncelikli sürüm ya da pthread_rwlock.

Mobil Sistemler

Mobil Sistemlerde Çoklu Görev

iOS / iPadOS
  • İlk sürümler (2007) yalnız tek kullanıcı uygulamasına izin veriyordu; iOS 4 (2010) sınırlı çoklu görev getirdi.
  • Arka planda yalnız izin verilen işler: ses çalma, konum, VoIP, BGTaskScheduler görevleri; diğerleri askıya alınır.
Android
  • Arka plan süreçlerine izin verir; uzun işler hizmet (service) bileşeniyle, arayüz askıdayken bile sürer.
  • Android 8'den beri arka plan kısıtları: uzun iş için bildirimli ön plan hizmeti ya da WorkManager.
ön plan (foreground)kullanıcının etkileşimde olduğu etkinlikgörünür (visible)ekranda ama odakta değilhizmet (service)örn. arka planda senkronizasyonönbellekteki (cached)arka plan + boş süreçlerbellek azalınca önce alttakiler sonlandırılır (lmkd)

Kısıtların nedeni sınırlı bellek ve pil. Eski belgelerdeki “arka plan” ve “boş” süreçler bugün önbellekteki (cached) grupta toplanır; bellek azalınca lmkd alttan başlayarak sonlandırır.

Özet

Özet

Süreç

Yürütülen program: metin, veri, yığın, yığıt + CPU bağlamı + kaynaklar. Çekirdek onu PCB ile izler.

Yaşam döngüsü

Yeni → Hazır ⇄ Çalışıyor → Bekliyor → Hazır … → Sonlandı. Çıkış durumu wait ile alınır.

UNIX API

fork kopyalar (COW), exec görüntüyü değiştirir, wait bekler ve biçer, exit bitirir.

Çizelgeleme altyapısı

Hazır/bekleme kuyrukları; bağlam anahtarlama saf ek yüktür.

IPC

Paylaşımlı bellek hızlı ama eşzamanlama ister; mesaj geçirme, boru ve soketler çekirdek aracılığıyla çalışır.

Eşzamanlılık

Paylaşılan veride yarış durumu; kritik bölgeler karşılıklı dışlamayla korunmalı.

Özet

Kontrol Soruları

  1. fork(); fork(); çalıştıran program toplam kaç süreçle biter? Ya for (i = 0; i < 3; i++) fork();?
  2. printf("A"); fork(); printf("B\n"); stdout bir dosyaya yönlendirildiğinde çıktı nedir? Neden?
  3. Bir süreç Bekliyor durumundan doğrudan Çalışıyor durumuna geçebilir mi?
  4. Zombi ile yetim süreç arasındaki fark nedir? Hangisi sistem için daha çok sorun çıkarır?
  5. exec başarılı olursa ardından gelen satır ne zaman çalışır?
  6. p = 0,5 ve n = 3 için CPU kullanımı nedir?
  7. Boru örneğinde ata close(fd[1]) yapmasaydı ve çocuk EOF'a kadar okusaydı ne olurdu?
Cevapları göster
  1. 4 süreç (1 → 2 → 4). Döngüde 2³ = 8 süreç (7 yeni).
  2. AB iki kez: dosyaya yazarken stdout tam tamponludur; "A" fork anında tamponda olduğu için çocuğa da kopyalanır.
  3. Hayır; önce Hazır'a geçer, sonra çizelgeleyici seçer.
  4. Zombi bitmiş ama biçilmemiş; yetim çalışıyor ama atası yok (init evlat edinir). Biriken zombiler PID tüketir.
  5. Hiçbir zaman; satır yalnız exec başarısız olursa çalışır.
  6. 1 − 0,5³ = 0,875 → %87,5.
  7. Yazma ucu atada açık kaldığı için read asla 0 döndürmez; çocuk sonsuza dek bekler, ata da wait'te bekler.

Özet

Kaynaklar

  • A. Silberschatz, P. B. Galvin, G. Gagne, Operating System Concepts, 10. baskı, Wiley, 2018 — Bölüm 3: Processes.
  • A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4. baskı (5. baskı 2023), Pearson — Bölüm 2: Processes and Threads.
  • R. H. Arpaci-Dusseau, A. C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces (OSTEP), pages.cs.wisc.edu/~remzi/OSTEP — Processes, Process API.
  • Linux kılavuz sayfaları: fork(2), execve(2), wait(2), pipe(7), signal(7), shm_overview(7).
  • Chromium Projects, “Multi-process Architecture” ve “Site Isolation” belgeleri; Android Developers, “Processes and app lifecycle”.

Son

Bölüm 3: Süreçler
Sonraki bölüm: İş Parçacıkları
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.