Bölüm 3: Süreçler
Giriş
Öğrenme Çıktıları
Program ile süreç arasındaki farkı ve bir sürecin bellek yerleşimini açıklamak.
Süreç durumlarını, geçişleri ve PCB'nin içeriğini tanımlamak.
fork(), exec(), wait(), exit() ve pipe() kullanan küçük programları izlemek.
Hazır/bekleme kuyruklarını ve bağlam anahtarlamanın maliyetini açıklamak.
Paylaşımlı bellek, mesaj geçirme, boru ve soketleri karşılaştırmak.
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).
Süreç Kavramı
Bellekte Süreç Yerleşimi
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.
Metin: main fonksiyonunun derlenmiş komutları; salt okunur ve aynı programın süreçleri arasında paylaşılabilir.
Veri: ilk değeri verilmiş global sayac; değeri yürütülebilir dosyada saklanır.
BSS: ilk değeri verilmemiş global ve static değişkenler; yükleyici sıfırla doldurur, dosyada yer kaplamaz.
Yığıt: main çağrı çerçevesi: parametreler, yerel değişkenler ve işaretçi p'nin kendisi.
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
- 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
Birden çok iş aynı zaman aralığında ilerler; tek çekirdekte iç içe geçerek (interleaving).
Birden çok iş aynı anda yürütülür; birden çok çekirdek ya da işlemci gerekir.
Süreç Kavramı
Örnek: Chrome'un Çok Süreçli Mimarisi
Süreç Oluşturma
Süreç Oluşturma
Süreç oluşturan olaylar
- Sistem açılışı (init/systemd, arka plan hizmetleri: daemon).
- Çalışan bir sürecin süreç oluşturan sistem çağrısı yapması.
- Kullanıcının bir komut ya da uygulama başlatması.
- 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 / Linux | fork() + exec(); ayrıca posix_spawn(), Linux'ta clone() |
| Windows | CreateProcess(): oluşturma ve program yüklemeyi tek çağrıda yapar |
Süreç Oluşturma
Süreç Ağacı
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
Ç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.
Ata ve çocuk eşzamanlı çalışabilir ya da ata wait() ile çocuğun bitmesini bekleyebilir.
UNIX fork(): çocuk atanın adres uzayının kopyasını alır (paylaşmaz). Ardından exec() ile yeni bir program yükleyebilir.
getpid() kendi PID'ini, getppid() atanın PID'ini döndürür.
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 */ }
Başlangıç: ata sürecin sayfa tablosu fiziksel A, B, C sayfalarını gösteriyor.
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.
Ç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; }
Tek süreç var: ata (PID 1234), x = 10.
fork() çocuk 1235'i oluşturur. Atada pid = 1235, çocukta pid = 0; iki ayrı x var.
Atada pid ≠ 0: if atlanır, wait(NULL) çocuk bitene kadar ata'yı bekliyor durumuna sokar.
Çocukta pid == 0 doğru: çocuk dalına girer. (Çocuk önce çalışmış olsaydı da sonuç aynı olurdu.)
Çocuk kendi kopyasını değiştirir: x = 15. Atanın x'i 10 kalır.
Çocuk yazdırır.
Çocuk return 0 ile çıkar (exit(0)); kısa süre zombi olur, çekirdek atayı uyandırır.
wait 1235 döndürür; çocuğun girdisi süreç tablosundan silinir.
Ata yazdırır: kendi x'i hâlâ 10.
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");
wait() (ya da başka bir eşzamanlama) kullanılmalıdır.Süreç Oluşturma
exec() Ailesi
| Çağrı | Argümanlar | PATH araması | Ortam |
|---|---|---|---|
execl / execv | liste / dizi | hayır | devralınır |
execlp / execvp | liste / dizi | evet | devralınır |
execle / execve | liste / dizi | hayır | açı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; }
fork(): iki süreç aynı a.out görüntüsünü çalıştırıyor.
Ata çocuk dalını atlar ve waitpid(1235, ...) ile bloke olur.
Çocuk execlp çağırır: PATH içinde ls bulunur, adres uzayı /usr/bin/ls ile değiştirilir. PID hâlâ 1235.
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.
ls exit(0) ile biter; waitpid döner ve st çıkış durumunu taşır.
WEXITSTATUS(st) = 0 yazdırılır.
Süreç Oluşturma
Kabuk Döngüsü: fork → exec → wait
Kabuk istemi gösterir ve ls komutunu okur.
fork(): kabuğun bir kopyası (PID 2300) oluşur; ata waitpid ile beklemeye geçer.
Çocuk execvp("ls", argv) çağırır; görüntüsü ls ile değişir.
ls çalışır, miras aldığı terminal stdout'una yazar.
ls çıkar; çekirdek atayı SIGCHLD ile bilgilendirir, waitpid çocuğu biçer.
Kabuk yeni istemi gösterir. Komut sonuna & eklenirse kabuk beklemeden devam eder (arka plan işi).
Süreç Sonlandırma
Süreç Sonlandırma
| Neden | Tü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 hata | istemsiz | geçersiz bellek erişimi (SIGSEGV), sıfıra bölme (SIGFPE) |
| Başka süreç tarafından | istemsiz | kill(pid, SIGTERM), Windows'ta TerminateProcess() |
exit(durum): C kütüphanesi tamponları boşaltır,atexitişleyicilerini çalıştırır, sonra_exitsistem ç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çinkill()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
| Hata | Sinyal |
|---|---|
| Geçersiz/izinsiz bellek erişimi | SIGSEGV |
| Tamsayı sıfıra bölme | SIGFPE |
| Geçersiz komut | SIGILL |
| Hizalama / veri yolu hatası | SIGBUS |
| Kapalı boruya yazma | SIGPIPE |
- İş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 coreile hatanın nedeni incelenir (crash dump analizi).
Süreç Sonlandırma
Sinyallerle Sonlandırma: kill, pkill, killall
| Sinyal | No | Anlamı |
|---|---|---|
| SIGTERM | 15 | kibarca bitir; süreç yakalayıp temizlik yapabilir (kill'in varsayılanı) |
| SIGKILL | 9 | zorla bitir; yakalanamaz, yok sayılamaz, temizlik yapılamaz |
| SIGINT | 2 | terminalde Ctrl+C |
| SIGTSTP | 20 | terminalde Ctrl+Z (duraklat); fg/bg sürdürür |
| SIGSTOP / SIGCONT | 19 / 18 | durdur (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
Ata (100) ve çocuk (101) çalışıyor.
Ç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>).
Ata wait() çağırır, çıkış durumunu alır; çekirdek girdiyi siler (biçme, reaping). Zombi kalmaz.
Senaryo 2: ata (100) çocuktan önce sonlandı. Çocuk (101) hâlâ çalışıyor: yetim süreç.
Ç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.
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)
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
Kullanıcı programı başlattı: PCB oluşturuluyor, bellek ayrılıyor → Yeni.
Çekirdek süreci kabul etti ve hazır kuyruğuna koydu → Hazır.
Çizelgeleyici bu süreci seçti, bağlamı yüklendi → Çalışıyor.
Süreç diskten read() istedi; veri gelene kadar CPU'yu bırakır → Bekliyor.
Disk kesmesi: veri hazır. Süreç doğrudan çalışmaz, hazır kuyruğuna döner → Hazır.
Yeniden seçildi → Çalışıyor.
Zamanlayıcı kesmesi: zaman dilimi doldu (önleyici çizelgeleme) → Hazır.
Tekrar seçildi → Çalışıyor.
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 STAT | Anlam | Modeldeki karşılığı |
|---|---|---|
R | çalışıyor ya da çalışmaya hazır | Çalışıyor + Hazır |
S | kesilebilir uyku (olay bekliyor) | Bekliyor |
D | kesilemez uyku (genelde disk G/Ç) | Bekliyor |
T | durdurulmuş (SIGSTOP, hata ayıklayıcı) | askıya alınmış |
Z | zombi | Sonlandı (biçilmedi) |
I | boş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)
- Ç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önetimi | Bellek yönetimi | Dosya 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 listesi | kök dizin · çalışma dizini · dosya tanımlayıcıları · kullanıcı kimliği (UID) · grup kimliği (GID) |
Durum bayrakları (sıfır, taşma...), çalışma kipi (kullanıcı/çekirdek), kesme izinleri.
Birlikte yönetilen süreçler: kabuktaki bir boru hattı tek grup olur, Ctrl+C hepsine gider.
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 ... */ };
- Tüm görevler çift yönlü bağlı listededir;
currento çekirdekte çalışan görevi gösterir. - Eski slayttaki
long state/time_slicealanları güncel çekirdeklerde__statevesched_entityiçine taşınmıştır.
Çizelgeleme
Hazır ve Bekleme Kuyrukları
- Ç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
Yeni süreç hazır kuyruğunun sonuna eklenir ve sırasını bekler.
Çizelgeleyici onu seçer (dispatch): süreç CPU'da çalışır.
G/Ç isteği yaptı: G/Ç bekleme kuyruğuna geçer; G/Ç bitince hazır kuyruğuna döner.
Zaman dilimi doldu: zamanlayıcı kesmesiyle CPU'dan alınır, doğrudan hazır kuyruğuna döner.
Çocuk oluşturup wait() çağırdı: çocuk bitene kadar bekler.
Bir kesmeyi/olayı bekliyor (örn. sleep(), ağ paketi).
İş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
Uzun hesaplama patlamaları, seyrek G/Ç: derleme, video kodlama, bilimsel hesap.
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
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)
P₀ çalışıyor (kalın çizgi); P₁ hazır kuyruğunda bekliyor.
Kesme ya da sistem çağrısı: CPU çekirdek kipine geçer, P₀'ın program sayacı ve yazmaçları PCB₀'a kaydedilir.
Çizelgeleyici P₁'i seçer; P₁'in bağlamı PCB₁'den yüklenir (adres uzayı da değişir).
P₁ çalışıyor; P₀ boşta (hazır ya da bekliyor).
Yeni kesme: P₁'in durumu PCB₁'e kaydedilir.
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.
Yazmaçların kaydı/yüklenmesi, çekirdek kodu: güncel Linux'ta tipik olarak birkaç mikrosaniye.
Ö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.
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):
Kesme isteği komut sınırında kabul edilir; CPU çekirdek kipine geçer.
Vektör, her kesme türü için işleyicinin adresini tutan tablodur.
Yazmaçlar o anki sürecin PCB'sine (ya da çekirdek yığıtına) yazılır.
Kesme işleyicisi çekirdek yığıtında çalışır.
Genelde girdiyi okur/tamponlar; bekleyen süreci hazır yapar.
Uyanan süreç daha öncelikliyse kesilen süreç yerine o seçilebilir.
Kalan iş, seçilen sürecin bağlamının yüklenmesidir.
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
- Bir süreç diğerine bilgiyi nasıl iletir?
- Süreçler birbirinin yoluna çıkmasın: aynı koltuğa iki rezervasyon nasıl önlenir?
- Bağımlılıklarda doğru sıra nasıl sağlanır? (Nişan almadan ateş edilmemeli; üretilmeden tüketilmemeli.)
Süreçler Arası İletişim
Paylaşımlı Bellek ve Mesaj Geçirme
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 bölgeye doğrudan yazar: çekirdek devrede değildir, bu yüzden çok hızlıdır.
B aynı veriyi okur. Ancak eşzamanlamayı (kim ne zaman yazar?) süreçler kendisi sağlamalıdır.
Mesaj geçirme: A send(m₃) der; çekirdek mesajı kendi kuyruğuna kopyalar.
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_openadlı bir nesne oluşturur (Linux'ta/dev/shmaltında).ftruncateboyutu belirler,mmapadres 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.
| Boyut | Seçenekler |
|---|---|
| Fiziksel bağ | paylaşımlı bellek, donanım veri yolu, ağ |
| Adlandırma | doğrudan (sürecin adı) · dolaylı (posta kutusu / port) |
| Eşzamanlılık | bloklayan (senkron): gönderen alınana kadar, alıcı mesaj gelene kadar bekler · bloklamayan (asenkron): hemen döner |
| Tamponlama | sıfır kapasite (gönderen bekler: buluşma/rendezvous) · sınırlı (kuyruk doluysa bekler) · sınırsız (hiç beklemez) |
mq_send/mq_receive, Mach portları, Windows ALPC.Süreçler Arası İletişim
Doğrudan ve Dolaylı İletişim
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ü.
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)
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şim | yalnız oluşturan ve çocukları | dosya adı bilen her süreç |
| Yön | tek yönlü | UNIX: yarı çift yönlü; Windows: tam çift yönlü |
| Ömür | süreçler kapatınca biter | mkfifo; dosya sisteminde kalır |
| Ağ | hayır | UNIX 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; }
pipe(fd): çekirdek boş bir tampon açar; okuma ucu fd[0]=3, yazma ucu fd[1]=4 (0–2 stdin/stdout/stderr).
fork(): çocuk tanımlayıcıları miras alır; şimdi her iki uç da iki süreçte açık.
Çocuk kullanmayacağı yazma ucunu kapatır.
Boru boş: read çocuğu bloke eder.
Ata okuma ucunu kapatır.
Ata 7 bayt yazar; ≤ PIPE_BUF yazımlar bölünmez (atomik). Bekleyen okuyucu uyanır.
Ata yazma ucunu kapatır (okuyucu ileride EOF görebilsin) ve wait ile bekler.
read 7 döndürür; veri tampondan çocuğun buf dizisine kopyalanır.
Sonlandırıcı '\0' eklenir ve yazdırılır.
Ç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
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
- 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
Yazdırılacak dosyalar sırayla yuvalara yazılır. out sıradaki yazdırılacak, in ilk boş yuva.
A in'i okur (7) ve yerel next_free'ye koyar; tam o anda zaman dilimi biter.
B de in'i okur: yine 7!
B dosya adını 7. yuvaya yazar, in = 8 yapar.
A kaldığı yerden devam eder: next_free = 7 olduğundan 7. yuvaya yazar, B'nin girdisini ezer; in = 8.
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 yazarprint; n=0; n=n+1→ 5 yazar, n=1print; n=n+1; n=0→ 5 yazar, artış kaybolur
Tek bir C satırı bile (count++) makinede üç komuttur:
Başta count = 5. Üretici count++ için önce değeri yazmaca yükler.
Üretici yazmacı artırır: 6. Henüz belleğe yazmadı ve kesildi.
Tüketici count--: bellekteki değer hâlâ 5.
Tüketici yazmacı azaltır: 4.
Üretici belleğe yazar: count = 6.
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):
- İki süreç aynı anda kritik bölgede olamaz.
- CPU hızı ya da sayısı hakkında varsayım yapılmaz.
- Kritik bölge dışındaki süreç başkasını engelleyemez.
- Hiçbir süreç girmek için sonsuza dek beklemez.
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; }
Başlangıç: kimse girmek istemiyor.
P0 girmek istediğini bildirir.
Kesme! P1 de hemen ardından girmek istediğini bildirir.
P0 sırayı kibarca P1'e verir.
P1 de sırayı P0'a verir. turn'e en son yazan bekleyecek.
P0: flag[1] && turn == 1? turn = 0 → yanlış: P0 girer.
P1: flag[0] && turn == 0 doğru → döngüde döner (CPU harcar).
P0 çıkarken flag[0] = false yapar.
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): uyuyanp'yi hazır yapar.- Ama yanlış kullanılırsa uyandırma sinyali kaybolabilir (sonraki slaytlar).
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.
Tampon boş (count = 0): tüketici okuyamaz, beklemelidir.
Üretici A, B, C ekler: her eklemede in = (in + 1) % N.
Tüketici A'yı alır: out = 1, count = 2.
Üretici D…I ekler; in başa sarar (1). count = N = 8: tampon dolu, üretici beklemelidir.
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); } }
Tampon boş. Tüketici count'u okur: 0. Tam sleep() çağıracakken kesilir.
Üretici bir öğe üretir ve tampona koyar.
count 1 olur.
count == 1 → tüketici uyuyor sanılır, wakeup(consumer) gönderilir. Ama tüketici henüz uyumadı: sinyal kaybolur.
Tüketici kaldığı yerden devam eder: eski kararına göre sleep() çağırır ve uyur.
Üretici tamponu doldurur; tüketici uyuduğu için kimse okumuyor.
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); } } } }
Boş ve dolu yuvaları sayar: sıralamayı (kim bekler) sağlar. Sayaç ile bekleme atomik olduğu için uyandırma kaybolmaz.
Tampona aynı anda tek erişim: karşılıklı dışlama.
Ü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)
Her filozof düşünür ya da yer; yemek için solundaki ve sağındaki iki çatalı da alması gerekir.
Saf çözüm: pick_up_left_fork(); pick_up_right_fork();. Hepsi aynı anda acıkıp solu alırsa...
...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.
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
- İ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,
BGTaskSchedulergörevleri; diğerleri askıya alınır.
- 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.
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
Yürütülen program: metin, veri, yığın, yığıt + CPU bağlamı + kaynaklar. Çekirdek onu PCB ile izler.
Yeni → Hazır ⇄ Çalışıyor → Bekliyor → Hazır … → Sonlandı. Çıkış durumu wait ile alınır.
fork kopyalar (COW), exec görüntüyü değiştirir, wait bekler ve biçer, exit bitirir.
Hazır/bekleme kuyrukları; bağlam anahtarlama saf ek yüktür.
Paylaşımlı bellek hızlı ama eşzamanlama ister; mesaj geçirme, boru ve soketler çekirdek aracılığıyla çalışır.
Paylaşılan veride yarış durumu; kritik bölgeler karşılıklı dışlamayla korunmalı.
Özet
Kontrol Soruları
fork(); fork();çalıştıran program toplam kaç süreçle biter? Yafor (i = 0; i < 3; i++) fork();?printf("A"); fork(); printf("B\n");stdout bir dosyaya yönlendirildiğinde çıktı nedir? Neden?- Bir süreç Bekliyor durumundan doğrudan Çalışıyor durumuna geçebilir mi?
- Zombi ile yetim süreç arasındaki fark nedir? Hangisi sistem için daha çok sorun çıkarır?
execbaşarılı olursa ardından gelen satır ne zaman çalışır?- p = 0,5 ve n = 3 için CPU kullanımı nedir?
- Boru örneğinde ata
close(fd[1])yapmasaydı ve çocuk EOF'a kadar okusaydı ne olurdu?
Cevapları göster
- 4 süreç (1 → 2 → 4). Döngüde 2³ = 8 süreç (7 yeni).
ABiki kez: dosyaya yazarken stdout tam tamponludur; "A" fork anında tamponda olduğu için çocuğa da kopyalanır.- Hayır; önce Hazır'a geçer, sonra çizelgeleyici seçer.
- Zombi bitmiş ama biçilmemiş; yetim çalışıyor ama atası yok (init evlat edinir). Biriken zombiler PID tüketir.
- Hiçbir zaman; satır yalnız exec başarısız olursa çalışır.
- 1 − 0,5³ = 0,875 → %87,5.
- Yazma ucu atada açık kaldığı için
readasla 0 döndürmez; çocuk sonsuza dek bekler, ata dawait'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”.