İçeriğe geç
academia.sh

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ında PGID, SID ve TTY sü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.
  • disown sinyali keser ama akışı kesmez; 13 işten 4’ü kalıyor, 9’u çıktısı uçtermine gittiği için ölüyor. nohup ve ayrı oturum iki olayı birden karşılar, yanlış tanı 0.
  • jobs 13 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat