İçeriğe geç
academia.sh

Ders 03 / 22

Sinyaller

Yedi ağır sürece sonlandırma isteği gönderildiğinde 2'si kabul ediyor, 5'i yok sayıyor; zorlayan sinyalde 7/7 bitiyor ama 2 sürecin temizliği yarıda kesiliyor. Komut her durumda 0 döndürdüğü için beklemeden verilen tanı yedi hedefin yedisinde de yanlış.

İçindekiler

Önceki ders bir sinyalin kenarından geçti: uçtermin kapandığında kapanma sinyali oturumun başına iletildi, kabuk onu kendi işlerine yaydı ve işlerin bir bölümü sinyali yok saydı. Yok saymanın kendisi ölçülmedi. Bu ders sinyalleri doğrudan ele alır.

Ölçtüğü cümle tektir: sonlandırma isteği bir istektir. Komutun hatasız dönmesi hedefin bittiği anlamına gelmez, ve aradaki fark sayılabilir.

Sinyal Nedir, Ne Değildir

Sinyal, çekirdeğin bir sürece ilettiği kesme bildirimidir. İçinde veri taşımaz; yalnız kimliği vardır. Süreç bir sinyali aldığında üç şeyden biri olur: sürecin o sinyal için tanımladığı işleyici çalışır, sinyal yok sayılır, ya da sinyalin varsayılan eylemi uygulanır. Varsayılan eylem sinyalden sinyale değişir — sonlandırmak, durdurmak, sürdürmek ya da hiçbir şey yapmamak.

Kabuk Programlama kursundaki trap tam olarak birinci seçeneği kurar: betiğin sinyal işleyicisini tanımlar. Buradaki fark bakış yönüdür — orada sinyali alan taraf yazıldı, burada gönderen taraf ölçülür.

Sinyal Varsayılan eylem Yakalanabilir mi Ne için
SIGHUP sonlandır evet denetleyen uçtermin gitti; birçok servis bunu yeniden okuma isteği sayar
SIGINT sonlandır evet uçterminden kesme (Ctrl+C)
SIGQUIT sonlandır ve döküm al evet uçterminden çıkış isteği
SIGTERM sonlandır evet sonlandırma isteği — nazik biçim, varsayılan
SIGKILL sonlandır hayır zorlayan sinyal — süreç söz sahibi değildir
SIGSTOP durdur hayır süreci dondurur
SIGCONT sürdür evet durdurulmuş süreci sürdürür
SIGUSR1, SIGUSR2 sonlandır evet anlamı programın kendi sözleşmesindedir
SIGPIPE sonlandır evet okuyan ucu kapanmış boru hattına yazma
SIGCHLD yok say evet bir çocuk süreç bitti ya da durdu

İki satır ötekilerden ayrıdır. SIGKILL ve SIGSTOP yakalanamaz, yok sayılamaz ve engellenemez; kararı çekirdek verir, süreç haberdar bile olmaz. Bunun bedeli tam olarak budur: zorlayan sinyalle ölen bir süreç, kendi kapanış yordamını hiç çalıştıramaz.

Sinyallerin numaraları da vardır ama numaralar her mimaride aynı değildir. Taşınabilir yazım adla yapılır: kill -TERM yazılır, sayı yazılmaz.

Sinyal Göndermek

Sinyal gönderen komutun adı kill’dir ve adı yanıltıcıdır: komut süreç öldürmez, sinyal iletir. Hangi sinyalin iletildiği seçeneğe bağlıdır, seçenek verilmezse sonlandırma isteği gönderilir.

$ kill -TERM 1128
$ echo $?
0
$ kill -0 1128           # sinyal gonderme , yalnizca ulasilabilir mi diye bak
$ echo $?
0
$ pkill -TERM -u uygulama kuyruk-isleyici
$ kill -TERM -1014       # eksi isaretli sayi: sinyal SUREC OBEGINE gider

Bu döküm örnektir ve çalıştırılmamıştır. Dört ayrıntı taşıyor. kill -0 hiçbir sinyal göndermez; yalnız hedefin var olup olmadığını ve ona sinyal gönderme yetkisinin bulunup bulunmadığını sınar — doğrulamanın aracı budur. pkill hedefi ada ve kullanıcıya göre seçer; birinci dersteki ölçü burada geçerlidir, ad bir kimlik değil etikettir. Eksi işaretli sayı hedefi süreç öbeğine çevirir: bir boru hattının tek üyesine değil, tamamına gönderir. killall da ada göre çalışır ve davranışı sistemler arasında değişebildiği için, taşınabilir betiklerde pkill yeğlenir.

En kritik ayrıntı ikinci satırdadır. kill komutunun çıkış kodu sinyalin iletilip iletilmediğini bildirir. Hedefin sinyali nasıl karşıladığı, işleyicisinin ne yaptığı, sürecin bitip bitmediği bu koda hiç yansımaz. Kodu 0 gören operatörün “iş bitti” demesi, ölçülmemiş bir tanıdır.

  • SY15. Hedef küme, kurgu sunucudaki sürekli ağır desenli süreçlerdir.
  • SY16. Süreçlerin bir bölümü sonlandırma isteğini yok sayar; ortak tanımdaki sinyal_yok_sayar alanı bunu taşır. Zorlayan sinyal yok sayılamaz.
  • SY17. İsteği kabul eden bir süreç hemen bitmez: önce kendi temizliğini yapar. Temizlik süresi süreç başına 0 ile 39 saniye arasındadır.
  • SY18. kill komutunun çıkış kodu her denemede 0’dır; sinyal iletildi demektir.
  • SY19. Beklemeden verilen tanı şudur: “komut döndü, hedefler bitti.” Yanlış tanı sayısı, o anda gerçekte bitmemiş hedeflerin sayısıdır.
  • SY20. Zorlayan sinyal bütün hedefleri bitirir; isteği kabul edip temizliği süren süreçlerin temizliği yarıda kesilir.
  • SY21. İkinci tohum 20260219’dur.
# --- ortak tanim, birinci dersteki bicimiyle
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: sinyal ve temizlik
def sinyal_gonder(surecler_, sinyal, hedefler):
    """Ortak tanim: `sonlandir` yoksayilabilir , `zorla` yoksayilamaz."""
    biten, direnen = [], []
    for s in surecler_:
        if s["pid"] not in hedefler:
            continue
        if sinyal == "zorla" or not s["sinyal_yok_sayar"]:
            biten.append(s["pid"])
        else:
            direnen.append(s["pid"])
    return {"biten": len(biten), "direnen": len(direnen),
            "direnen_pid": sorted(direnen)}


S = surecler()
HEDEF = {s["pid"] for s in S if s["desen"] == "agir"}
print("hedef:", len(HEDEF), "agir surec")
print("sonlandir:", sinyal_gonder(S, "sonlandir", HEDEF))
print("zorla    :", sinyal_gonder(S, "zorla", HEDEF))
hedef: 7 agir surec
sonlandir: {'biten': 2, 'direnen': 5, 'direnen_pid': [1014, 1043, 1092, 1108, 1128]}
zorla    : {'biten': 7, 'direnen': 0, 'direnen_pid': []}

Yedi ağır sürecin yalnız 2’si sonlandırma isteğini kabul ediyor, 5’i direniyor. Zorlayan sinyalde 7’sinin 7’si bitiyor. Direnen süreçlerin listesi çıktıda görünmez — kill komutu bu bilgiyi vermez, veremez. Bu, kursun ortak tanımının ikinci bağlayıcı okumasıdır.

Beklemenin Ölçüsü

Yukarıdaki iki satır sinyalin nihai sonucudur. Operatör ise sonucu değil, bir andaki durumu görür. Sonlandırma isteğini kabul eden süreç hemen bitmez; açık dosyalarını kapatır, tamponunu boşaltır, kilidini bırakır. O sırada süreç hâlâ listede durur.

# Onceki blogun uzerine: S, HEDEF, uretec, TOHUM oradan gelir.
def temizlik_ata(surecler_, tohum=TOHUM):
    """Sonlandirma istegini kabul eden surec hemen bitmez: once temizlik yapar."""
    r = uretec(tohum)
    for s in surecler_:
        s["temizlik"] = r(40)              # saniye
    return surecler_


def sonlandirma_denemesi(surecler_, hedefler, bekleme):
    """Istek gonderildi , `bekleme` saniye sonra bakildi. `kill` cikis kodu her
    durumda 0: sinyal ILETILDI demek , hedef BITTI demek degil."""
    biten, temizleniyor, yok_sayan = [], [], []
    for s in surecler_:
        if s["pid"] not in hedefler:
            continue
        if s["sinyal_yok_sayar"]:
            yok_sayan.append(s["pid"])
        elif s["temizlik"] <= bekleme:
            biten.append(s["pid"])
        else:
            temizleniyor.append(s["pid"])
    return len(biten), len(temizleniyor), len(yok_sayan)


temizlik_ata(S)
print("hedef:", len(HEDEF), "surec | `kill` cikis kodu her satirda 0")
print("bekleme  biten  temizleniyor  yok sayan  hayatta  yanlis tani")
for b in (0, 1, 5, 10, 30, 60):
    biten, tmz, yok = sonlandirma_denemesi(S, HEDEF, b)
    print(f"{b:7d}  {biten:5d}  {tmz:12d}  {yok:9d}  {tmz + yok:7d}"
          f"  {len(HEDEF) - biten:11d}")
hedef: 7 surec | `kill` cikis kodu her satirda 0
bekleme  biten  temizleniyor  yok sayan  hayatta  yanlis tani
      0      0             2          5        7            7
      1      0             2          5        7            7
      5      0             2          5        7            7
     10      1             1          5        6            6
     30      2             0          5        5            5
     60      2             0          5        5            5

İlk satır dersin en sert sayısıdır. Komut döndüğü anda hedeflerin hiçbiri bitmemiştir; yanlış tanı 7/7. Beş süreç isteği yok saydı, ikisi de henüz temizliğini bitirmedi. Çıkış kodu yine de 0’dır — komut işini yapmıştır, sinyal iletilmiştir.

İkinci okuma bekleme sütunundadır. Beş saniye beklemek hiçbir şey değiştirmiyor; onuncu saniyede 1, otuzuncu saniyede 2 süreç bitiyor. Otuzdan sonra hiçbir şey değişmiyor: kalan 5 süreç isteği yok sayıyor ve ne kadar beklenirse beklensin bitmeyecek. Beklemek temizliği tamamlar, direnci çözmez. Bu ayrım doğru yordamı belirler.

Doğru sıra dört adımdır: isteği gönder, bekle, kill -0 ile doğrula, ancak hâlâ duranı zorla. Servis yöneticileri de bunu yapar ve bekleme süresi birim tanımında ayarlanır bir değerdir; o taraf bu kursun sonraki konusudur.

Bekleme süresinin seçimi bu tabloya bakılarak yapılır ve iki yönde de bedeli vardır. Kısa seçilirse temizliğini sürdüren süreçler gereksiz yere zorlanır; tabloda beş saniyede zorlamak, onuncu ve otuzuncu saniyede kendiliğinden bitecek iki süreci kesmek demektir. Uzun seçilirse direnen süreçler boşuna beklenir: otuzuncu saniyeden sonra tabloda hiçbir sütun değişmez, altmış saniye beklemek yalnız arızayı uzatır. Ölçülmüş bir eşik yoksa seçilen süre bir varsayım olarak kalır ve o varsayım her iki yönde de yanlış tanı üretir.

Ulaşmayan ve Bekleyen Sinyal

Direnç, sinyalin işe yaramamasının tek nedeni değildir. Üç durum daha vardır ve üçü de aynı görüntüyü üretir: komut 0 döndürür, hedef durur.

Birincisi bekleyen sinyaldir. Durdurulmuş bir sürece gönderilen sonlandırma isteği hemen işlenmez; süreç kod yürütmediği için sinyal bekler. Önceki dersteki durdurulmuş işler tam olarak bu durumdadır: istek iletilir, kuyruğa yazılır ve süreç SIGCONT ile sürdürülene kadar orada kalır. Zorlayan sinyal bu kuralın dışındadır; onu çekirdek uygular ve sürecin kod yürütmesini beklemez. Bir işi gerçekten durdurmanın taşınabilir yolu bu yüzden önce sürdürüp sonra istek göndermektir.

İkincisi engellenmiş sinyaldir. Bir süreç kritik bir bölüme girerken belirli sinyalleri geçici olarak engelleyebilir; engel kalkana kadar sinyal bekler. Dışarıdan bakan operatör engellenmiş sinyal ile yok sayılmış sinyali ayırt edemez — ikisinde de hiçbir şey olmaz.

Üçüncüsü yanlış hedeftir. Süreç numaraları yeniden kullanılır: bir süreç bittikten sonra aynı numara başka bir sürece verilebilir. Numarayı bir dosyadan okuyup gönderen betikler bu yüzden numarayı doğrulamadan kullanmamalıdır; kill -0 ile varlığı sınamak yeterli değildir, sınanan şey numaranın kime ait olduğudur.

Bu üç durumun ortak sonucu şudur: sinyal göndermek bir olay değil, bir istektir ve isteğin sonucu yalnız hedefe bakarak öğrenilir. Bir kapanış yordamının doğru yazılmış olması da gönderen tarafı ilgilendirir — servislerin yeniden yapılandırılmasında SIGHUP kullanılması bu yüzden yaygındır: servis yeniden başlatılmadan yapılandırmasını yeniden okur ve açık bağlantıları düşürmez. Aynı sinyalin başka bir programda varsayılan eylemi sonlandırmaktır; anlamı sinyalin değil, programın sözleşmesinindir.

Zorlamanın Bedeli

Bekleme adımını atlayıp doğrudan zorlayan sinyali göndermek çekici bir kısayoldur: sonuç kesindir, hedeflerin hepsi biter. Kesinliğin bir bedeli vardır ve o da sayılabilir.

# Onceki bloklarin uzerine: S, HEDEF, sonlandirma_denemesi, sinyal_gonder,
# temizlik_ata, surecler, IKINCI_TOHUM.
z = sinyal_gonder(S, "zorla", HEDEF)
kesilen = [s["pid"] for s in S if s["pid"] in HEDEF
           and not s["sinyal_yok_sayar"] and s["temizlik"] > 0]
print("dogrudan zorlayan sinyal:", z["biten"], "/", len(HEDEF), "bitti")
print("  yarida kesilen temizlik:", len(kesilen), "surec")
print("dogru sira (istek , bekle , dogrula , zorla):")
biten, tmz, yok = sonlandirma_denemesi(S, HEDEF, 30)
print("  30 saniye sonra kendiliginden biten:", biten,
      "| zorlanmasi gereken:", tmz + yok)
print()
for tohum in (TOHUM, IKINCI_TOHUM):
    S2 = temizlik_ata(surecler(tohum), tohum)
    H2 = {s["pid"] for s in S2 if s["desen"] == "agir"}
    g = sinyal_gonder(S2, "sonlandir", H2)
    b0 = sonlandirma_denemesi(S2, H2, 0)[0]
    b30 = sonlandirma_denemesi(S2, H2, 30)[0]
    print(f"tohum {tohum}: hedef {len(H2)} | istegi kabul eden {g['biten']}"
          f" | 0 sn sonra biten {b0} | 30 sn sonra biten {b30}"
          f" | yanlis tani {len(H2) - b0}")
dogrudan zorlayan sinyal: 7 / 7 bitti
  yarida kesilen temizlik: 2 surec
dogru sira (istek , bekle , dogrula , zorla):
  30 saniye sonra kendiliginden biten: 2 | zorlanmasi gereken: 5

tohum 20260218: hedef 7 | istegi kabul eden 2 | 0 sn sonra biten 0 | 30 sn sonra biten 2 | yanlis tani 7
tohum 20260219: hedef 5 | istegi kabul eden 5 | 0 sn sonra biten 0 | 30 sn sonra biten 4 | yanlis tani 5

Doğrudan zorlamak 7/7 sonuç veriyor, ve 2 sürecin temizliği yarıda kesiliyor. Kesilen temizlik soyut bir kayıp değildir: tamponu boşaltmamış bir süreç yazdığını sanan veriyi kaybeder, kilidini bırakmamış bir süreç geride bir kilit dosyası bırakır, geçici dizinini silmemiş bir süreç onu diskte bırakır. Bu üç sonucun hiçbiri kill çıktısında görünmez ve üçü de sonraki çalıştırmada başka bir arıza olarak ortaya çıkar.

Bekleyerek yapılan sıralamada aynı iki süreç kendiliğinden bitiyor ve yalnız 5 hedef zorlanıyor. Fark iki süreçlik değildir; iki temiz kapanışlıktır.

İkinci tohumda hedef sayısı 5’e iniyor ve isteği kabul edenlerin sayısı 2 yerine 5 oluyor — kompozisyon tamamen değişiyor. Değişmeyen tek şey ilk sütundur: her iki tohumda da komut döndüğü anda biten hedef sayısı 0’dır ve beklemeden verilen tanı bütün hedeflerde yanlıştır. Kaç sürecin direneceği kurguya bağlıdır; komutun dönmesinin hiçbir şey söylemediği değildir.

Modelin dışında kalan bir sınır da not edilmelidir: zorlayan sinyal burada her hedefi bitiriyor, ama gerçek bir sistemde çekirdek çağrısının içinde kesilemez uykuda bekleyen bir süreç ona da yanıt vermez. O durumda süreç D durumunda görünür ve tek çare bekleyişin nedenini gidermektir.

Özet

  • Sinyal veri taşımayan bir kesme bildirimidir; süreç onu yakalayabilir, yok sayabilir ya da varsayılan eyleme bırakabilir. SIGKILL ve SIGSTOP yakalanamaz, engellenemez.
  • kill komutunun çıkış kodu sinyalin iletildiğini bildirir, hedefin bittiğini değil. Doğrulamanın aracı kill -0’dır.
  • Yedi ağır hedeften sonlandırma isteğini yalnız 2’si kabul ediyor, 5’i yok sayıyor; zorlayan sinyalde 7/7 bitiyor.
  • Komut döndüğü anda biten hedef sayısı 0’dır: yanlış tanı 7/7. Onuncu saniyede 1, otuzuncu saniyede 2 süreç biter; otuzdan sonra hiçbir şey değişmez.
  • Doğrudan zorlamak 2 sürecin temizliğini yarıda keser; beklemeli sırada aynı iki süreç temiz kapanır ve yalnız 5 hedef zorlanır. Doğru sıra istek, bekleme, doğrulama, zorlamadır.

Sonraki Adım

Bu ders süreci durdurmayı ölçtü. Durdurmak her zaman doğru yanıt değildir: ağır bir süreç gerekli bir iş yapıyor olabilir ve istenen şey onu bitirmek değil, yavaşlatmaktır. Sonraki ders öncelik değerini ele alır ve tek bir soruyu sayar: bir sürecin önceliğini değiştirmenin gerçekten görülebilir etkisi ne kadardır, ve değişen sütun ile değişen davranış aynı şey midir.

İ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