Mert Özen Her Satırda Daha İleri
DevOps Linux ve Shell (geliştirici için) — #04

Process'ler ve Signal'ler: ps, top/htop, kill, nice, Process Ağacı ve Zombie'ler

Mert Özen 14 Kas 2026 15 dk 4 görüntülenme
Process'ler ve Signal'ler: ps, top/htop, kill, nice, Process Ağacı ve Zombie'ler

Bir .NET uygulamasını Linux'ta SIGTERM ya da SIGINT ile kapatırsan yarıdaki istekleri bitirip temiz çıkar; SIGHUP ve SIGKILL ise onu anında öldürür (gerçek denemeyle). ps ve top çıktısını (VIRT/RES, STAT, load average) okumayı, process ağacını, jobs/fg/bg ile SSH kopunca hangi işlerin öldüğünü, nohup, disown ve setsid farkını, SigIgn'ı, nice/renice'i, /proc ve lsof ile açık dosyaları ve zombie'lerin neden kill ile ölmediğini gösteriyoruz.

Sürüm notu: Bu yazıdaki komutlar Ubuntu Server 24.04.5 LTS (kernel 6.8, procps 4.0, bash 5.2) çalıştıran bir test VM'inde; kod .NET SDK 10.0.402 ile derlenip ASP.NET Core 10.0.15 runtime'ıyla çalıştırılmıştır.

Önce Tahmin Et

Nginx'le çalışmış biri kill -HUP'ı "configuration'ı yeniden oku" diye bilir; Nginx gerçekten öyle yapar. Sunucuda sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) API'si çalışıyor ve o an 5 saniye süren bir rapor isteğini işliyor. Biri alışkanlıkla .NET process'ine de aynı signal'i gönderiyor:

$ sudo kill -HUP 4250

Ne olur: .NET configuration'ı yeniden mi okur, signal'i yok mu sayar, yoksa process kapanır mı? Kapanırsa yarıdaki rapor isteği tamamlanır mı? Cevap yazının ortasında, aynı denemeyi dört farklı signal'le yaptığımız yerde.

Üçüncü yazıda Bookstore'u kendi service kullanıcısıyla çalıştırdık. Bu yazıda o process'e dışarıdan bakmayı, ps ve top çıktısını okumayı, signal'lerle .NET uygulamasını düzgün kapatmayı, SSH (uzak bağlantı protokolü) kopunca neden process'lerin öldüğünü, önceliği ve zombie'leri ele alıyoruz. Process'in işletim sistemi tarafındaki teorisi (address space, scheduling) İşletim Sistemi Temelleri serisinde; burada sunucuda komut satırından yaptığımız işe odaklanıyoruz.

Deneme İçin Bir Yavaş Endpoint

Kapanma davranışını görebilmek için Bookstore.Api'ye istenen süre kadar bekleyen bir rapor endpoint'i ve yaşam döngüsü log'ları ekledim:

// Yavaş rapor: kapanma sırasında yarıdaki bir isteğe ne olduğunu görmek için
app.MapGet("/api/reports/sales", async (int seconds) =>
{
    await Task.Delay(TimeSpan.FromSeconds(seconds));
    return Results.Ok(new { report = "sales", seconds });
});

app.Lifetime.ApplicationStarted.Register(() =>
    app.Logger.LogInformation("Bookstore.Api started, PID {Pid}", Environment.ProcessId));
app.Lifetime.ApplicationStopping.Register(() =>
    app.Logger.LogInformation("Stopping: draining in-flight requests"));
app.Lifetime.ApplicationStopped.Register(() =>
    app.Logger.LogInformation("Stopped cleanly"));

Yeni release'i deploy kullanıcısıyla /opt/bookstore/releases/2026-11-14-3'e koyup current'ı çevirdim ve uygulamayı sudo -u bookstore ile başlattım. Yaşam döngüsü event'leri ve IHostApplicationLifetime ASP.NET Core Web API serisinde anlatıldı; burada onları yalnızca log satırı olarak kullanıyoruz.

Process, PID ve PPID: ps ile Bakmak

Her process'in bir PID'si (process kimliği) ve onu başlatan process'in PID'si olan bir PPID'si (parent process'in kimliği) var. Bir process'i bulmanın en hızlı yolu pgrep, ayrıntı için ps:

$ pgrep -a dotnet
1578 dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000
$ ps -o pid,ppid,user,stat,etime,cmd -C dotnet
    PID    PPID USER     STAT     ELAPSED CMD
   1578    1575 booksto+ Sl         00:06 dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000
$ ps aux | head -1; ps aux | grep -E "[d]otnet|[s]shd:"
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         767  0.3  0.8  12028  8204 ?        Ss   14:48   0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        1526  4.4  1.0  14744 10428 ?        Ss   14:50   0:00 sshd: ada [priv]
ada         1573  1.0  0.7  15000  7096 ?        S    14:50   0:00 sshd: ada@notty
root        1575  1.8  0.7  17308  7100 ?        S    14:50   0:00 sudo -u bookstore nohup dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000
booksto+    1578 85.1  7.1 273908036 70636 ?     Sl   14:50   0:06 dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000

ps -o ile hangi sütunları istediğini seçersin; -C dotnet komut adına göre filtreler. ps aux herkesin bütün process'lerini klasik Unix sütunlarıyla gösterir; grep deseninde [d]otnet yazmak, grep'in kendi satırının sonuca düşmesini engelleyen eski bir alışkanlık. STAT sütunu en çok işine yarayacak olan: R çalışıyor ya da sırada, S bir şey bekleyerek uyuyor (network, timer), D kesilemez uyku (genellikle disk ya da network file system I/O'su), Z zombie, T durdurulmuş. Ek harfler: s session lideri, l çok thread'li, N düşük öncelikli, + terminalin ön planındaki process grubu.

Process'lerin kim kimin child'ı olduğunu görmek için ağaç görünümü:

$ ps -ef --forest | grep -E -A1 "[s]shd|[s]udo -u bookstore" | head -20
root         767       1  0 14:48 ?        00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        1526     767  4 14:50 ?        00:00:00  \_ sshd: ada [priv]
ada         1573    1526  1 14:50 ?        00:00:00      \_ sshd: ada@notty
ada         1574    1573  2 14:50 ?        00:00:00          \_ bash /tmp/04a.sh
root        1575    1574  1 14:50 ?        00:00:00              \_ sudo -u bookstore nohup dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000
booksto+    1578    1575 85 14:50 ?        00:00:00              |   \_ dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000

Zincir: systemd'nin başlattığı SSH listener'ı, benim SSH oturumum, oturumdaki bash, onun başlattığı sudo ve en altta bookstore kullanıcısıyla çalışan dotnet. Uygulama benim SSH oturumumun torunu; oturum kapanınca ne olacağı bu yüzden önemli.

04-process-tree-signals-tr.png

top ve htop: Sütunları Okumak

$ top -b -n 1 -o %MEM | head -12
top - 14:50:23 up 3 min,  1 user,  load average: 0.88, 0.84, 0.36
Tasks: 123 total,   1 running, 122 sleeping,   0 stopped,   0 zombie
%Cpu(s): 43.5 us, 17.4 sy,  0.0 ni, 39.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :    960.1 total,    490.4 free,    292.2 used,    330.3 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    668.0 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   1578 booksto+  20   0  261.2g  71160  61376 S  81.2   7.2   0:06.91 dotnet
    364 root      rt   0  288960  27300   8768 S   0.0   2.8   0:00.93 multipa+
    732 root      20   0  110016  23116  13660 S   0.0   2.4   0:01.28 unatten+
    309 root      19  -1   66836  17724  16544 S   0.0   1.8   0:05.98 systemd+
    682 root      20   0  468936  13588  11436 S   0.0   1.4   0:01.56 udisksd

-b -n 1 top'u bir kez çalıştırıp çıktıyı metin olarak basar; etkileşimli kullanımda M belleğe, P CPU'ya göre sıralar, 1 çekirdekleri ayrı gösterir, q çıkar. htop aynı bilgiyi renkli ve fareyle gezilebilir gösterir; Ubuntu'da sudo apt install htop.

Ürkütücü olan sütun VIRT: .NET process'i 261 GB virtual memory kullanıyor görünüyor, VM'in belleği 1 GB. VIRT, process'in rezerve ettiği adres alanı; .NET runtime ve GC (kullanılmayan object'leri bellekten temizleyen garbage collector) başlangıçta büyük adres aralıkları ayırır ama onları fiziksel belleğe yazmaz. Gerçekte kullanılan fiziksel bellek RES: 71 MB. SHR, bunun başka process'lerle paylaşılabilen kısmı (paylaşılan kütüphaneler, belleğe map edilen .dll dosyaları). Bellek alarmlarını ve container limitlerini VIRT'e değil RES'e göre düşün; virtual memory ve page'lerin nasıl çalıştığı İşletim Sistemi Temelleri serisinin virtual memory yazısında.

%CPU bir çekirdeğe göre ölçülür: iki çekirdekli bir makinede bir process en fazla %200 görünebilir. Buradaki 81, uygulamanın birkaç saniye önce açılmasından; başlangıçta runtime kodu derlerken CPU kısa süre yüksek görünür. Üst satırdaki wa, CPU'nun disk I/O beklerken boşta kaldığı süre; yavaşlık vakalarında bu sayı yüksekse sorun CPU değil disktir.

Load average son 1, 5 ve 15 dakikada çalışmak için sırada bekleyen ya da çalışan (R) ve kesilemez uykudaki (D) process sayısının ortalaması. Tek başına anlamı yok, çekirdek sayısıyla okunur: nproc 2 diyen bu makinede 0.88 rahat, 2 dolu, 4 ise her an bir işin sırada beklediği anlamına gelir. Linux'ta D state'teki process'ler de sayıldığı için, CPU boşta görünürken load average'ın yüksek olması genellikle diskin ya da bir NFS (network üzerinden paylaşılan file system) bağlantısının takıldığını gösterir. Onuncu yazıda bunu vmstat ve iostat ile açacağız.

Signal'ler: TERM, INT, HUP ve KILL

Signal, kernel'in bir process'e gönderdiği küçük bir bildirim. Process çoğu signal'i yakalayıp kendi bildiği gibi davranabilir; yakalamazsa varsayılan davranış uygulanır, ki bu çoğu signal için ölmek. İki istisna hiçbir zaman yakalanamaz: SIGKILL (öldür) ve SIGSTOP (durdur). kill komutunun adı yanıltıcı; aslında signal gönderir ve varsayılan signal'i SIGTERM'dür.

Tahminin cevabı için aynı denemeyi dört signal'le yaptım: uygulamayı başlat, 5 saniyelik rapor isteğini gönder, bir saniye sonra signal'i yolla, isteğin ve process'in ne olduğuna bak:

=== SIGTERM
$ sudo kill -TERM 4299
in-flight request: 200 after 5.474425s
process exited after 4.980515389s
=== SIGINT
$ sudo kill -INT 4157
in-flight request: 200 after 5.481157s
process exited after 4.919335640s
=== SIGHUP
$ sudo kill -HUP 4250
in-flight request: 000 after 1.350014s
curl exit=52
process exited after .747422686s
=== SIGKILL
$ sudo kill -KILL 1825
in-flight request: 000 after 1.266063s
curl exit=52
process exited after .459700473s

SIGTERM ve SIGINT'te uygulamanın log'u şöyleydi:

info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: Bookstore.Api[0]
      Stopping: draining in-flight requests
info: Bookstore.Api[0]
      Stopped cleanly

.NET'in host'u SIGTERM, SIGINT ve SIGQUIT'i yakalar ve graceful shutdown başlatır: Kestrel yeni bağlantı almayı bırakır, yarıdaki istekleri bitirmeleri için varsayılan olarak 30 saniye bekler, sonra process çıkar. Rapor isteği 200 ile tamamlandı ve process isteğin bitmesini bekleyip yaklaşık 5 saniyede kapandı. SIGHUP'ta ise .NET 10 hiçbir şey yakalamadı: varsayılan davranış uygulandı, process yarım saniyede öldü, istek yarıda kesildi (curl exit 52, sunucudan boş yanıt) ve log'a tek satır yazılmadı. Yani tahminin cevabı: Nginx'te "yeniden yükle" anlamına gelen signal, .NET uygulamasını kapatıyor. SIGKILL'de sonuç aynı ama sebebi farklı: process'e hiçbir şey sorulmadan kernel onu durdurur, log yazma, bağlantı kapatma, dosyayı flush etme şansı olmaz.

Bu yüzden sıra her zaman aynı: önce kill PID (TERM), uygulamanın shutdown süresi kadar bekle, hâlâ yaşıyorsa ancak o zaman kill -9. systemd de systemctl stop'ta aynısını yapar: önce SIGTERM, TimeoutStopSec dolunca SIGKILL. Bunu Web Server'lar ve Hosting serisinin systemd yazısında KillSignal ayarıyla birlikte görmüştük.

Process'leri isme göre seçip signal göndermek için pkill var; pgrep ile aynı eşleştirmeyi kullanır. Bunu kullanmadan önce aynı deseni pgrep -a ile dene, çünkü -f bütün komut satırında arar ve düşündüğünden fazlasını yakalayabilir (aşağıdaki tuzaklarda bir örneği var).

Foreground, Background ve SSH Kopması

Shell'de bir komut normalde foreground'da çalışır: bitene kadar prompt dönmez, Ctrl-C ona SIGINT gönderir. Sona & koyarsan background'a gider. Etkileşimli bir SSH oturumunda:

$ sleep 300 &
[1] 4441
$ jobs -l
[1]+  4441 Running                 sleep 300 &
$ sleep 200
^Z
[2]+  Stopped                 sleep 200
$ jobs
[1]-  Running                 sleep 300 &
[2]+  Stopped                 sleep 200
$ bg %2
[2]+ sleep 200 &
$ fg %1
sleep 300
^C
$ disown %2
$ jobs
$

Ctrl-Z foreground'daki işi durdurur (SIGTSTP), bg durdurulan işi background'da devam ettirir, fg bir işi tekrar öne alır. disown işi shell'in iş listesinden çıkarır; process çalışmaya devam eder ama artık bu shell'in işi değildir. Bunun neden önemli olduğunu SSH bağlantısı koptuğunda görüyorsun. Bir oturumda dört farklı şekilde sleep başlattım ve istemci tarafındaki ssh process'ini kill -9 ile öldürerek ağ kopmasını taklit ettim:

$ sleep 601 &
$ nohup sleep 602 > /dev/null 2>&1 &
$ sleep 603 & disown
$ setsid sleep 604 < /dev/null > /dev/null 2>&1 &
$ ps -o pid,ppid,sid,stat,cmd -C sleep
    PID    PPID     SID STAT CMD
   4715    4706    4706 S    sleep 601
   4716    4706    4706 S    sleep 602
   4717    4706    4706 S    sleep 603
   4720       1    4720 Ss   sleep 604

Bağlantı koptuktan sonra yeni bir oturumdan baktım:

$ ssh bookstore-dev ps -o pid,ppid,sid,stat,cmd -C sleep
    PID    PPID     SID STAT CMD
   4716       1    4706 S    sleep 602
   4717       1    4706 S    sleep 603
   4720       1    4720 Ss   sleep 604

Düz & ile başlatılan iş öldü. Bağlantı kopunca sshd oturumun PTY'sini (terminal ile shell arasındaki sanal terminal) kapatır, bash SIGHUP alır ve iş listesindeki her işe SIGHUP'ı iletir; sleep bunu yakalamadığı için ölür. nohup process'i SIGHUP'ı yok sayacak şekilde başlatır, disown işi listeden çıkardığı için bash ona signal göndermez, setsid ise process'i yeni bir session'da, terminalden tamamen bağımsız başlatır. Hayatta kalan üçünün PPID'si 1 oldu: parent'ları olan bash öldü ve systemd onları sahiplendi.

Bir process'in hangi signal'leri yok saydığını /proc/<pid>/status'taki SigIgn satırı söyler. Bookstore.Api'yi bir script içinden nohup ... & ile başlattığımda bu değer 0000000000001007 idi: 1, 2, 3 ve 13 numaralı signal'ler, yani SIGHUP (nohup'tan), SIGINT ve SIGQUIT (etkileşimsiz shell'in background işleri için varsayılanı) ve SIGPIPE. Bu yüzden o process'e gönderdiğim kill -INT hiçbir şey yapmadı. "Ctrl-C ile kapanan uygulama sunucuda kill -INT'e tepki vermiyor" vakasının açıklaması budur.

Bunların hiçbiri kalıcı çözüm değil. nohup ile başlatılan bir uygulama sunucu reboot olunca kalkmaz, çökünce yeniden başlamaz, log'u bir dosyada büyür. Kalıcı çözüm systemd ve bir sonraki yazının konusu. Uzun süren bir migration'ı ya da import'u elle izlemek istiyorsan doğru araç tmux; Terminal ve Command Line serisinin tmux yazısında aynı kopma denemesini tmux ile yapmıştık.

nice ve renice

Aynı anda birden çok işin CPU istediği durumda nice değeri hangisinin öne geçeceğini etkiler: -20 en yüksek öncelik, 19 en düşük, varsayılan 0. Production'da bir yedek sıkıştırma ya da rapor üretme işini web uygulamasının önüne geçirmemek için kullanılır:

$ nice -n 10 gzip -c /usr/lib/dotnet/shared/Microsoft.NETCore.App/*/System.Private.CoreLib.dll > /dev/null &
$ ps -o pid,ni,pri,stat,cmd -C gzip,dotnet
    PID  NI PRI STAT CMD
   4954   0  19 Sl   dotnet Bookstore.Api.dll --urls http://127.0.0.1:5000
   4982  10   9 RN   gzip -c /usr/lib/dotnet/shared/Microsoft.NETCore.App/10.0.15/System.Private.CoreLib.dll
$ renice -n 15 -p $(pgrep -x gzip)
4982 (process ID) old priority 10, new priority 15
$ renice -n 5 -p $(pgrep -x gzip)
renice: failed to set priority for 4982 (process ID): Permission denied

Normal kullanıcı kendi process'inin önceliğini düşürebilir ama geri yükseltemez; bunun için root gerekir. STAT'taki N düşük öncelik demek. nice yalnızca CPU önceliğidir; disk için ionice var. Scheduler'ın bu değeri nasıl kullandığı İşletim Sistemi Temelleri serisinin scheduling yazısında.

/proc ve lsof ile Process'in İçine Bakmak

İkinci yazıda /proc/<pid>/environ ve cwd'ye bakmıştık. Açık dosyalar fd klasöründe:

$ PID=$(pgrep -f "^dotnet Bookstore.Api.dll")
$ sudo ls -l /proc/$PID/fd | head -5
total 0
lr-x------ 1 bookstore bookstore 64 Nov 14 15:01 0 -> /dev/null
l-wx------ 1 bookstore bookstore 64 Nov 14 15:01 1 -> /tmp/api.log
lrwx------ 1 bookstore bookstore 64 Nov 14 15:01 10 -> socket:[35937]
lr-x------ 1 bookstore bookstore 64 Nov 14 15:01 100 -> /usr/lib/dotnet/shared/Microsoft.AspNetCore.App/10.0.15/Microsoft.Extensions.Logging.Debug.dll
$ sudo ls /proc/$PID/fd | wc -l
203
$ sudo lsof -a -p $PID -i
COMMAND  PID      USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
dotnet  4954 bookstore  174u  IPv4  35216      0t0  TCP localhost:5000 (LISTEN)
$ grep -E "^(State|PPid|Threads|VmRSS|SigIgn)" /proc/$PID/status
State:	S (sleeping)
PPid:	4950
VmRSS:	   73392 kB
Threads:	18
SigIgn:	0000000000001000

0, 1 ve 2 stdin, stdout ve stderr: stdin /dev/null'a, stdout log dosyasına bağlı. Uygulama 203 dosya açmış durumda; çoğu runtime'ın .dll dosyaları ve socket'ler. "Too many open files" hatası aldığında önce bu sayıya, sonra /proc/$PID/limits'teki Max open files'a bakarsın. lsof -p aynı listeyi tip, boyut ve inode bilgisiyle verir; -a -p $PID -i yalnızca o process'in network bağlantılarını gösterir: uygulama yalnızca localhost:5000'i dinliyor. Bu, sekizinci yazıdaki "kim hangi port'u dinliyor" sorusunun process tarafından cevabı. Bu son çalıştırmayı script içinden ama job control açık başlattığım için SigIgn yalnızca SIGPIPE'ı gösteriyor.

Üretimde Dikkat

Graceful shutdown süresini deploy'una göre ayarla. .NET varsayılan olarak yarıdaki istekleri 30 saniye bekler; systemd'nin varsayılan durdurma süresi 90 saniye, Docker'ınki 10 saniye. Uzun süren rapor ya da dosya yükleme istekleri varsa bu süreleri birlikte düşün: orchestrator'ın süresi uygulamanınkinden kısaysa .NET daha bitirmeden SIGKILL gelir ve graceful shutdown hiç çalışmamış gibi olur. Ayrıntılar Web Server'lar ve Hosting serisinin systemd yazısında ve Docker serisinin container yaşam döngüsü yazısında.

Process'i ve davranışını ölçerek izle. top'ta anlık %CPU yanıltıcı olabilir, uygulamanın açılışı ve ilk istekler kısa süre yüksek gösterir; bir sorunu teşhis ederken birkaç örnek al ya da pidstat gibi aralıklı ölçen araçlara geç (onuncu yazı). .NET içindeki thread pool, GC ve exception sayıları için dotnet-counters gerekir; Debugging ve Problem Çözme serisinin hang ve yavaşlık yazısında anlatıldı.

Sık Düşülen Tuzaklar

Zombie'yi kill ile öldürmeye çalışmak. Zombie, çıkmış ama exit code'u parent'ı tarafından henüz okunmamış (reap edilmemiş) bir process'in process tablosunda kalan kaydı. Zaten ölü olduğu için signal'e cevap vermez:

$ bash -c "sleep 1 & exec sleep 60" &
$ ps -o pid,ppid,stat,cmd -p 5244 --ppid 5244
    PID    PPID STAT CMD
   5244    5216 S    sleep 60
   5246    5244 Z    [sleep] <defunct>
$ kill -9 5246; sleep 1; ps -o pid,ppid,stat,cmd -p 5246
    PID    PPID STAT CMD
   5246    5244 Z    [sleep] <defunct>
$ kill 5244; sleep 1; ps -o pid,stat,cmd -p 5246 || echo 'zombie gone'
    PID STAT CMD
zombie gone

Burada bash, child'ını başlattıktan sonra kendini sleep 60 ile değiştirdi (exec); sleep child'ını beklemeyi bilmediği için ölen child zombie kaldı. kill -9 işe yaramadı; parent'ı öldürünce zombie systemd'ye geçti ve systemd onu hemen temizledi. Gerçek hayatta çözüm parent'ı düzeltmek: child process başlatan kod (.NET'te Process.Start) WaitForExit ile ya da Exited event'iyle child'ın bitişini beklemeli. Birkaç zombie zararsızdır, sürekli artıyorsa PID'ler tükenene kadar giden bir hata vardır.

D state'teki process'i öldürmeye çalışmak. Kesilemez uykudaki bir process, kernel'in içinde bir I/O'nun bitmesini bekliyordur ve kill -9 dahil hiçbir signal o I/O bitene kadar işlenmez. Takılan bir NFS mount'u ya da arızalı bir disk tipik sebeplerdir. Process'e değil, beklediği kaynağa bak.

pkill -f ve pgrep -f'in fazla şey yakalaması. Bu yazıyı hazırlarken uzak sunucuda ssh bookstore-dev '... pkill -f "dotnet Bookstore.Api.dll"; rm -rf ...' gibi bir satır çalıştırdım ve rm hiç çalışmadı. Sebep: SSH'ın uzakta başlattığı bash -c'nin komut satırı da aynı metni içeriyordu, pkill uygulamayla birlikte kendi shell'ini de öldürdü. Deseni ^dotnet gibi bir başlangıçla sınırla, pgrep -a ile önce kontrol et ya da en iyisi PID'yi systemd'den al.

kill -9'u ilk tercih yapmak. Uygulama log'unu yazamaz, açık transaction'larını ve bağlantılarını düzgün kapatamaz, yarıdaki istekleri keser. Ölmeyen bir process için bile önce TERM gönder, birkaç saniye bekle.

Küçük Bir Deneme

★☆☆ Kendi VM'inde Bookstore.Api'yi sudo -u bookstore ile başlat, ps -ef --forest ile ağaçtaki yerini bul ve top'ta VIRT ile RES değerlerini not et. RES, ilk birkaç istekten sonra nasıl değişiyor?

★★☆ Yavaş rapor isteğini gönderip uygulamaya sırayla kill -TERM, kill -HUP ve kill -9 yolla. İsteğin HTTP kodunu ve uygulamanın log'undaki son satırları karşılaştır. Sonra uygulamayı nohup ile başlatıp /proc/<pid>/status'taki SigIgn'ı oku ve kill -HUP'ı tekrar dene.

★★★ SSH kopma denemesini kendin yap: bir oturumda düz &, nohup, disown ve setsid ile dört iş başlat, laptop'unda ssh process'ini öldür ya da ağ bağlantını kes. Yeni bir oturumdan hangilerinin yaşadığına ve PPID'lerine bak. Aynı denemeyi WSL (Windows içindeki Linux ortamı) üzerindeki bir shell'den bağlanarak yap.

Uygulamayı hâlâ elle başlatıyoruz: SSH kopunca yaşaması için nohup'a, reboot'ta kalkması için kimseye güvenemiyoruz. Bir sonraki yazıda Bookstore'u systemd'ye devrediyoruz: systemctl ile yönetmek, paketlerin unit dosyalarını drop-in override'larla değiştirmek, timer'lar ve cron.