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.

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.