Ders 02 / 22
Ön Plan ve Arka Plan İşleri
Oturum kapandığında kabuğun 13 işinden `&` ile arka plana alınanların tamamı ölüyor, disown edilenlerin 9'u ölüyor, nohup ve ayrı oturum 13'ünü de yaşatıyor; `jobs` 13 satır gösterirken gerçekte ilerleyen iş sayısı 8.
İçindekiler
Önceki ders süreçlere makinenin dışından baktı: hangileri var, hangileri ağır. Süreçlerin bir de operatörle kurdukları bağ vardır. Bir betik bir uçterminden başlatıldığında yalnız bir süreç doğmaz; o süreç bir süreç öbeğine, öbek bir oturuma, oturum bir denetleyen uçtermine bağlanır. Uçtermin kapandığında bu zincir boyunca olaylar yürür.
Kabuk Programlama kursu iş denetimini kullanım düzeyinde kurmuştu: &, wait, çıkış kodu.
Bu ders o tarafı tekrarlamaz. Ölçtüğü şey tek bir sorudur: oturum kapandığında hangi
süreçler hayatta kalır ve jobs çıktısı bunu ne kadar önceden söyler.
Oturum, Öbek ve Denetleyen Uçtermin
Boru hattındaki komutlar tek bir süreç öbeğine konur; kabuk sinyalleri öbek başına gönderir, tek tek süreçlere değil. Bir uçterminden açılan kabuk ve onun bütün çocukları aynı oturumu paylaşır, oturumun başında kabuk durur ve o uçtermin oturumun denetleyen uçterminidir. Bu üç kimlik listelenebilir:
$ ps -eo pid,ppid,pgid,sid,tty,stat,comm
PID PPID PGID SID TTY STAT COMMAND
1000 998 1000 1000 pts/4 Ss bash
1014 1000 1014 1000 pts/4 R rapor-uretici
1021 1000 1014 1000 pts/4 R olcum-toplayici
1043 1000 1043 1000 pts/4 T yedek-gorevi
1092 1 1092 1092 ? Ss kuyruk-isleyici
Bu döküm örnektir ve çalıştırılmamıştır. Okunacak üç şey var. İlk iki süreç aynı PGID
değerini taşıyor: bir boru hattının iki ucu. Son sürecin SID değeri kendi süreç numarasına
eşit ve TTY sütunu boş — o süreç ayrı bir oturumdadır ve hiçbir uçtermine bağlı
değildir. Üçüncü sürecin durumu T: durdurulmuş.
Uçtermin kapandığında iki ayrı olay yürür ve ikisi de öldürebilir. Birincisi kapanma sinyalidir: çekirdek onu oturumun başına iletir, kabuk da kendi işlerine yayar. İkincisi daha sessizdir — uçtermin yok olduğu için ona yazmaya çalışan süreç bir girdi/çıktı hatası alır ve çoğu program bu hatada durur. İkinci olay hiçbir sinyal kaydı bırakmaz.
- SY8. Kurgu sunucunun 24 sürecinin bir bölümü operatörün kabuğundan başlatılmıştır; gerisi başka oturumlara aittir.
- SY9. Her sürecin iki ek özelliği vardır: çıktısı hâlâ uçtermine gidiyor mu (
yazar) ve uçterminden girdi bekliyor mu (okur). - SY10. Beş ayrılma yöntemi ölçülür: ön plan, arka plan (
&), disown, nohup ve ayrı oturum. - SY11. Bir yöntem üç şeyi belirler: süreç oturumda mı, kabuk ona kapanma sinyali
gönderir mi, süreç o sinyali yok sayar mı. Yönlendirme yapan yöntemler
yazarözelliğini de kapatır. - SY12. Hayatta kalma kuralı: oturum dışındaki süreçlere hiçbir olay ulaşmaz; oturum içindekiler önce sinyalle, sonra yazma hatasıyla elenir.
- SY13. Yaygın beklenti şudur: ön plandaki iş ölür, öteki dört yöntemin hepsi işi yaşatır. Yanlış tanı, bu beklenti ile gerçeğin ayrıldığı süreç sayısıdır.
- SY14. İkinci tohum 20260219’dur.
# --- ortak tanim, birinci dersteki bicimiyle: kurgu sunucu ve kahin TOHUM, IKINCI_TOHUM = 20260218, 20260219 KULLANICI = ("kok", "uygulama", "yedek", "izleme") KOMUT = ("veri-alici", "rapor-uretici", "yedek-gorevi", "olcum-toplayici", "kuyruk-isleyici", "onbellek-temizleyici") def uretec(tohum): d = tohum def sonraki(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return sonraki def surecler(tohum=TOHUM, sayi=24): r = uretec(tohum) liste = [] for i in range(sayi): tur = r(10) desen = "duz" if tur < 5 else ("ani" if tur < 8 else "agir") liste.append({"pid": 1000 + i * 7 + r(5), "kullanici": KULLANICI[r(len(KULLANICI))], "komut": KOMUT[r(len(KOMUT))], "desen": desen, "oncelik": r(11) - 5, "bellek": 20 + r(400), "sinyal_yok_sayar": r(10) < 2}) return liste # --- bu dersin katmani: oturum bagi YONTEM = ("on plan", "arka plan", "disown", "nohup", "ayri oturum") def oturum_dagit(surecler_, tohum=TOHUM): """Kabugun kendi isleri makinenin geri kalanindan ayrilir. `yazar`: ciktisi hala uctermine gidiyor. `okur`: ucterminden girdi bekliyor.""" r = uretec(tohum) for s in surecler_: s["kabuk_isi"] = r(10) < 5 s["yazar"] = r(10) < 6 s["okur"] = r(10) < 3 return surecler_ def uygula(surec, yontem): """Bir yontem, surecin uc ozelligini belirler. Kalani surecin kendisinden gelir.""" oturumda = yontem != "ayri oturum" kabuk_gonderir = yontem in ("on plan", "arka plan") korumali = yontem in ("nohup", "ayri oturum") return {"oturumda": oturumda, "kabuk_gonderir": kabuk_gonderir, "yok_sayar": korumali or surec["sinyal_yok_sayar"], "yazar": surec["yazar"] and not korumali} def hayatta_mi(d): """Ucterminin kapanmasi IKI ayri olay uretir , ikisi de oldurebilir.""" if not d["oturumda"]: return True # oturum disinda: hicbiri ulasmaz if d["kabuk_gonderir"] and not d["yok_sayar"]: return False # birinci olay: kapanma sinyali if d["yazar"]: return False # ikinci olay: uctermin gidince yazma hatasi return True S = oturum_dagit(surecler()) KABUK = [s for s in S if s["kabuk_isi"]] print("toplam surec:", len(S), "| kabugun isi:", len(KABUK), "| baska oturuma ait:", len(S) - len(KABUK)) print("kabuk isleri: sinyali yok sayan", sum(1 for s in KABUK if s["sinyal_yok_sayar"]), "| uctermine yazan", sum(1 for s in KABUK if s["yazar"]), "| ucterminden okuyan", sum(1 for s in KABUK if s["okur"]))
toplam surec: 24 | kabugun isi: 13 | baska oturuma ait: 11 kabuk isleri: sinyali yok sayan 4 | uctermine yazan 9 | ucterminden okuyan 5
İşi Arka Plana Almak
İş denetiminin komutları kabuğun kendi komutlarıdır ve süreç tablosunda görünmezler. &
komutu arka planda başlatır, Ctrl+Z ön plandaki işi durdurur, bg durdurulmuş işi arka
planda sürdürür, fg ön plana geri getirir. jobs kabuğun iş tablosunu listeler:
$ jobs -l [1]- 1014 Running ./rapor-uretici --gun 7 & [2]+ 1043 Stopped ./yedek-gorevi --tam [3] 1021 Running ./olcum-toplayici | ./ozet &
Bu döküm de örnektir ve çalıştırılmamıştır. İşlere %1, %2 gibi belirteçlerle atıf
yapılır; fg %2 ikinci işi ön plana alır. Köşeli parantez içindeki sayı süreç numarası
değildir — kabuğun kendi sayacıdır ve başka bir kabukta anlamı yoktur.
jobs tablosunun iki sınırı vardır ve ikisi de bu dersin ölçtüğü şeyi üretir. Birincisi,
tablo yalnız bu kabuğun işlerini taşır: başka bir oturumdan başlatılan ya da servis
yöneticisinin başlattığı hiçbir süreç burada görünmez. İkincisi, tablo şu andaki bağı
gösterir, gelecekteki sonucu değil.
Bu iki sınırın pratik sonucu şudur: bir işin oturumdan gerçekten ayrıldığını doğrulamanın yolu
jobs değildir. Doğrulama ikinci bir oturum açıp süreci orada aramaktır; ps çıktısında
TTY sütununun boş, PPID sütununun 1 olması ayrılmanın kanıtıdır. jobs çıktısı bu
soruya hiçbir zaman yanıt veremez, çünkü sorulan şey tam olarak jobs çıktısının kapsamı
dışındadır.
Kabuk Programlama kursunda kurulan çıkış kodu sözleşmesi burada da geçerlidir: wait %1 işin
bitmesini bekler ve onun çıkış kodunu döndürür. Bir iş sinyalle sonlandırıldığında kod
128 artı sinyal numarası olur; arka plandaki bir işin sessizce ölüp ölmediği bu koddan
anlaşılır. Kod okunmadan wait çağrılmazsa bilgi kaybolur — kabuk kapandığında iş tablosu da
gider.
Süreci oturumdan ayırmanın üç yolu vardır. disown işi kabuğun tablosundan siler; kabuk artık
ona kapanma sinyali göndermez. nohup komutu süreci kapanma sinyalini yok sayacak biçimde
başlatır ve çıktısını bir dosyaya yönlendirir. setsid süreci yeni bir oturuma koyar;
oturumun denetleyen uçtermini yoktur, dolayısıyla kapanacak bir uçtermin de yoktur. Üç yöntem
aynı işi yapmaz ve fark ölçülebilir.
Beş Yöntem, Bir Kapanma
# Onceki blogun uzerine: KABUK, YONTEM, uygula, hayatta_mi oradan gelir. # Yaygin beklenti: on planda birakilan is olur, otekilerin hepsi yasar. BEKLENTI = {"on plan": False, "arka plan": True, "disown": True, "nohup": True, "ayri oturum": True} print("yontem uygulanan beklenen gercekte olen yanlis tani") for y in YONTEM: gercek = {s["pid"] for s in KABUK if hayatta_mi(uygula(s, y))} beklenen = {s["pid"] for s in KABUK} if BEKLENTI[y] else set() print(f" {y:11s} {len(KABUK):9d} {len(beklenen):8d} {len(gercek):8d}" f" {len(KABUK) - len(gercek):4d} {len(beklenen ^ gercek):11d}") print("kahin: her satirda `gercekte` sutunu , kurguyu biz urettik")
yontem uygulanan beklenen gercekte olen yanlis tani on plan 13 0 0 13 0 arka plan 13 13 0 13 13 disown 13 13 4 9 9 nohup 13 13 13 0 0 ayri oturum 13 13 13 0 0 kahin: her satirda `gercekte` sutunu , kurguyu biz urettik
İkinci satır bu dersin ana sonucudur. & hiçbir şey yaşatmıyor: arka plana alınan 13 işin
13’ü de ölüyor, yanlış tanı 13. Ampersan işi arka plana alır, oturumdan çıkarmaz; kabuk
kapanma sinyalini kendi işlerine yayarken arka plandakileri de kapsar. “Arkasına & koydum,
çıkışta sürer” cümlesi kurgudaki her iş için yanlıştır.
Üçüncü satır daha ince bir sonuç verir. disown kapanma sinyalini gerçekten engelliyor — ama
13 işten yalnız 4’ü hayatta kalıyor, 9’u ölüyor. Ölenler ikinci olayın kurbanıdır:
çıktıları hâlâ uçtermine gidiyordu ve uçtermin yok olunca ilk yazma denemesinde durdular.
disown sinyali keser, akışı kesmez. Bu, iki ayrı olayı tek bir önlemle karşılamaya
çalışmanın bedelidir.
Son iki satırda yanlış tanı sıfırdır. nohup iki olayı birden karşılar: sinyali yok saydırır
ve çıktıyı dosyaya yönlendirir. Ayrı oturum ise sorunu ortadan kaldırır; kapanacak bir bağ
yoktur. Kalıcı işler için doğru düzen budur — ya ayrı bir oturum, ya da işi hiç kabuktan
başlatmayıp bir servis birimi hâline getirmek. İkincisi bu kursun sonraki konusudur.
Kabuklar arasında ayrıntı değişebilir; bazı kabuklarda kapanma sinyalinin işlere yayılması bir
seçenekle denetlenir. Ölçülen şey yaygın davranıştır, ve yaygın davranışta & tek başına
bir koruma değildir.
Listede Duran, İlerlemeyen İş
Ayrılma sorununun bir eşi vardır. Arka plandaki bir süreç uçterminden okumaya kalkarsa
çekirdek onu durdurur; süreç ölmez, bekler. Durdurulmuş süreç ps çıktısında T durumuyla,
jobs çıktısında “Stopped” olarak görünür. İkisi de sürecin var olduğunu doğrular; hiçbiri
ilerleme olmadığını söylemez.
# Onceki bloklarin uzerine: KABUK, uygula, hayatta_mi, oturum_dagit, surecler. duran = [s for s in KABUK if s["okur"]] print("arka plana alinan is icinde ucterminden okumaya calisan:", len(duran)) print(" bunlar DURDURULUR: `jobs` listeler , `ps` gosterir , ilerleme sifirdir") print(" `jobs` satiri:", len(KABUK), "| gercekte ilerleyen:", len(KABUK) - len(duran), "| yanlis tani:", len(duran)) print() for tohum in (TOHUM, IKINCI_TOHUM): S2 = oturum_dagit(surecler(tohum), tohum) K2 = [s for s in S2 if s["kabuk_isi"]] d = {y: len({s["pid"] for s in K2} ^ {s["pid"] for s in K2 if hayatta_mi(uygula(s, y))}) for y in ("arka plan", "disown", "nohup")} print(f"tohum {tohum}: kabuk isi {len(K2):2d} | yanlis tani {d}" f" | duran {sum(1 for s in K2 if s['okur'])}")
arka plana alinan is icinde ucterminden okumaya calisan: 5
bunlar DURDURULUR: `jobs` listeler , `ps` gosterir , ilerleme sifirdir
`jobs` satiri: 13 | gercekte ilerleyen: 8 | yanlis tani: 5
tohum 20260218: kabuk isi 13 | yanlis tani {'arka plan': 13, 'disown': 9, 'nohup': 0} | duran 5
tohum 20260219: kabuk isi 11 | yanlis tani {'arka plan': 11, 'disown': 6, 'nohup': 0} | duran 2
jobs 13 satır gösteriyor, gerçekte ilerleyen iş 8. Beş iş bir parola ya da onay
bekliyor ve kimse onlara yazamıyor; listede varlar, ilerlemede yoklar. Yanlış tanı 5.
Bunun pratik karşılığı, arka plana alınacak her işin girdisinin de kapatılmasıdır — çıktı
yönlendirmesi tek başına yetmez. Girdisi boş bir dosyaya bağlanan bir süreç durdurulmaz;
okuma denemesi hemen dosya sonu döndürür ve süreç kendi hata yolundan ilerler. Durmanın
kendisi de görünür bir olay değildir: iş tablosundaki durum alanı dışında hiçbir yere yazılmaz.
İkinci tohumda kabuğun işi 11’e, duran iş 2’ye iniyor; & için yanlış tanı yine bütün
işler, disown için 9 yerine 6, nohup için yine 0. Sayılar kurguya bağlıdır;
sıralama ve yön değişmiyor — & en kötü, disown yarım koruma, nohup ve ayrı oturum
tam koruma.
Öksüz ve Zombi Süreçler
Oturum kapandığında hayatta kalan süreçlerin ana süreci ölmüştür. Çekirdek bunları
öksüz bırakmaz: ana süreç numaralarını yeniden bağlar ve ps çıktısında PPID sütunu
1 olur. Öksüzlük bir arıza değildir; ayrılma yöntemlerinin üçü de bu yolla çalışır.
Asıl karışıklığı yaratan durum bunun tersidir. Bir süreç bittiğinde çıkış kodu, ana süreç onu
toplayana kadar süreç tablosunda saklanır. Ana süreç bu toplamayı yapmazsa çocuk zombi
durumunda kalır: kodu yürütmüyor, bellek tutmuyor, ama tablo girdisi duruyor. ps çıktısında
durumu Z, komut adı köşeli parantez içindedir.
Zombiye sinyal göndermenin hiçbir etkisi yoktur — sinyal, kodu yürüten bir sürece iletilir ve
zombi kod yürütmez. Kabuk Programlama kursunda wait ile toplanan şey tam olarak budur;
toplamayan bir ana süreç yazıldığında sonuç zombi birikmesidir. Çözüm zombileri sonlandırmak
değil, ana süreci düzeltmek ya da sonlandırmaktır; ana süreç gidince zombiler yeniden
bağlanır ve orada toplanırlar.
Birikmenin bedeli sayılabilir bir kaynaktır: her zombi bir süreç tablosu girdisi ve bir süreç numarası tutar. Yeterince biriktiğinde yeni süreç yaratılamaz. Bu sınırın hangi hata iletisiyle göründüğü bu konunun beşinci dersinin konusudur.
Kalıcı işler için nohup ve setsid doğru araçlardır ama tek seçenek değildir. Uçtermin
çoklayıcı (terminal multiplexer) sınıfındaki araçlar oturumu kendileri taşır: iş, bağlantı
koptuğunda ölmez, çünkü işin bağlı olduğu uçtermin operatörünkü değil çoklayıcınınkidir.
Aradaki fark ölçüm değil, sahiplik farkıdır — çoklayıcının kendisi kapandığında aynı iki olay
yeniden yürür.
Özet
- Bir süreç üç kimliğe bağlıdır: süreç öbeği, oturum ve denetleyen uçtermin. Kabuk sinyalleri
süreç öbeğine gönderir;
psçıktısındaPGID,SIDveTTYsütunları bu bağı gösterir. - Uçtermin kapandığında iki ayrı olay yürür: kapanma sinyali ve uçtermine yazmaya çalışan sürecin aldığı girdi/çıktı hatası. İkinci olay hiçbir sinyal kaydı bırakmaz.
&oturumdan ayırmaz: arka plana alınan 13 işin 13’ü ölüyor, yanlış tanı 13.disownsinyali keser ama akışı kesmez; 13 işten 4’ü kalıyor, 9’u çıktısı uçtermine gittiği için ölüyor.nohupve ayrı oturum iki olayı birden karşılar, yanlış tanı 0.jobs13 satır gösterirken gerçekte ilerleyen iş 8’dir; uçterminden okumaya çalışan 5 iş durdurulmuştur ve listede çalışıyor görünür.
Sonraki Adım
Bu ders bir sinyalin süreçlere nasıl ulaştığını kenardan gördü: kapanma sinyali kabuktan işlere yayıldı, kimileri onu yok saydı. Yok saymanın kendisi henüz ölçülmedi. Sonraki ders sinyalleri doğrudan ele alır ve tek bir cümleyi sayar: sonlandırma isteği bir istektir, ve komutun hatasız dönmesi hedefin bittiği anlamına gelmez.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.