waitid ve waitpid Sistem Çağrıları ile Süreç Tamamlanma Durumunu İzleme ve Yönetme
Süreç yönetimi deyince akla ilk gelen şeyler genellikle ps, top, ya da kill gibi araçlar olur. Ama işin mutfağında, yani çekirdek seviyesinde, süreçlerin nasıl başladığı ve nasıl bittiği meselesi çok daha derin bir konu. waitid ve waitpid sistem çağrıları, bu derinliğin tam ortasında duruyor. Özellikle daemon yazıyorsanız, özel bir süreç yöneticisi geliştiriyorsanız ya da zombie süreç sorunlarıyla boğuşuyorsanız, bu iki sistem çağrısını kavramak hem sorunlarınızı anlamamızı hem de çözümleri doğru inşa etmenizi sağlıyor.
Temel Kavramlar: Neden wait() Gerekli?
Linux’ta bir süreç fork() ile çocuk süreç oluşturduğunda, çocuk süreç işini bitirip exit() çağırdıktan sonra hemen yok olmaz. Çekirdek, o sürecin çıkış durumunu (exit status) ebeveyn süreç tarafından okunana kadar process table’da tutar. Bu aşamaya “zombie” denir. Ebeveyn süreç bu durumu okumadıkça, o giriş orada kalır.
İşte wait ailesi tam burada devreye giriyor. waitpid ve waitid, ebeveyn sürecin çocuklarının durumunu sorgulamasını, beklenmesini ve zombie temizlenmesini sağlıyor.
Bunu bir örnekle görelim. Aşağıdaki C kodu kasıtlı olarak wait çağrısı yapmadan çocuk süreç oluşturuyor:
# Zombie sürecin nasıl oluştuğunu görmek için basit bir test
cat > /tmp/zombie_test.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// Cocuk surecin hemen cikmasi
exit(42);
}
// Ebeveyn 30 saniye bekliyor ama wait() cagirmiyor
printf("Ebeveyn PID: %d, Cocuk PID: %dn", getpid(), pid);
sleep(30);
return 0;
}
EOF
gcc -o /tmp/zombie_test /tmp/zombie_test.c
/tmp/zombie_test &
# Baska bir terminalde zombie'yi gormek icin:
ps aux | grep -E 'Z|zombie'
Bu testi çalıştırdığınızda, çocuk sürecin Z (zombie) durumunda göründüğünü fark edeceksiniz. Production ortamlarında binlerce zombie biriken servislerde ben bunu çok yaşadım, özellikle eski CGI tabanlı uygulamalarda.
waitpid: Kontrollü Süreç Bekleme
waitpid, wait() fonksiyonunun daha gelişmiş versiyonu. Hangi çocuk süreci beklediğinizi, nasıl beklediğinizi kontrol altına almanızı sağlıyor.
Fonksiyon imzası şöyle:
# strace ile waitpid sistem cagrisini gormek
strace -e trace=wait4 ls /tmp 2>&1 | head -20
# Genel kullanim formatini anlamak icin man sayfasi:
man 2 waitpid
waitpid‘in pid parametresi birkaç farklı değer alabilir:
- pid > 0: Tam olarak o PID’e sahip çocuk süreci bekle
- pid == -1: Herhangi bir çocuk süreci bekle (klasik
wait()davranışı) - pid == 0: Çağıran süreçle aynı process group’taki herhangi bir çocuğu bekle
- pid < -1: Mutlak değeri verilen process group ID’sindeki herhangi bir çocuğu bekle
waitpid‘in options parametresi ise davranışı şekillendiriyor:
- WNOHANG: Beklenecek çocuk hazır değilse hemen dön, bloklanma
- WUNTRACED: Durdurulmuş (stopped) çocukları da raporla
- WCONTINUED: SIGCONT ile devam ettirilen çocukları da raporla
Şimdi bunu pratik bir senaryo ile kullanalım. Diyelim ki birden fazla paralel iş başlatıp hepsinin tamamlanmasını beklemeniz gerekiyor:
# Bash'te waitpid benzeri davranis gosteren bir script
# Paralel isler baslatip hepsini takip etme
#!/bin/bash
declare -a PIDS=()
declare -a JOB_NAMES=()
# 5 paralel is baslatiyoruz
for i in $(seq 1 5); do
sleep $((RANDOM % 5 + 1)) &
PIDS+=($!)
JOB_NAMES+=("Job-$i")
echo "Baslatildi: Job-$i (PID: $!)"
done
# Her süreci ayrı ayrı bekle ve durumunu kontrol et
for idx in "${!PIDS[@]}"; do
pid=${PIDS[$idx]}
name=${JOB_NAMES[$idx]}
wait $pid
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "BASARILI: $name (PID: $pid) tamamlandi"
else
echo "HATA: $name (PID: $pid) hata kodu: $exit_code"
fi
done
echo "Tum isler tamamlandi"
waitid: Daha Hassas Kontrol Mekanizması
waitid, waitpid‘den daha yeni ve daha esnek bir arayüz sunuyor. POSIX.1-2001 ile geldi. Temel fark, döndürdüğü bilginin çok daha zengin olması.
waitid sistem çağrısının parametreleri:
- idtype: Ne tür bir ID kullandığımızı belirtiyor
– P_PID: Belirli bir süreç ID’si – P_PGID: Belirli bir process group ID’si – P_ALL: Herhangi bir çocuk süreç
- id: idtype’a göre değişen ID değeri
- infop:
siginfo_tyapısı, sonuç buraya yazılıyor - options: Davranış bayrakları
waitid‘in options değerleri:
- WEXITED: Normal çıkış yapan süreçleri bekle
- WSTOPPED: Durdurulmuş süreçleri bekle
- WCONTINUED: Devam eden süreçleri bekle
- WNOHANG: Bloklanmadan dön
- WNOWAIT: Süreci zombie durumundan çıkarma, sadece bilgiyi al
siginfo_t yapısı sayesinde çok daha fazla bilgiye erişebilirsiniz. Örneğin hangi sinyalle öldürüldüğünü, hangi exit kodunu döndürdüğünü, hatta bazı sistemlerde CPU kullanımını bile öğrenebilirsiniz.
# waitid kullanan C programini test etme
cat > /tmp/waitid_demo.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
void check_child(pid_t child_pid) {
siginfo_t info;
memset(&info, 0, sizeof(info));
int ret = waitid(P_PID, child_pid, &info, WEXITED | WSTOPPED);
if (ret == -1) {
perror("waitid hatasi");
return;
}
printf("Cocuk PID: %dn", info.si_pid);
printf("Kullanici ID: %dn", info.si_uid);
switch(info.si_code) {
case CLD_EXITED:
printf("Normal cikis, kod: %dn", info.si_status);
break;
case CLD_KILLED:
printf("Sinyal ile olduruldu: %d (%s)n",
info.si_status, strsignal(info.si_status));
break;
case CLD_STOPPED:
printf("Durduruldu, sinyal: %dn", info.si_status);
break;
default:
printf("Bilinmeyen durum: %dn", info.si_code);
}
}
int main() {
pid_t pid = fork();
if (pid == 0) {
sleep(1);
exit(37); // Ozel cikis kodu
}
printf("Cocuk (PID: %d) bekleniyor...n", pid);
check_child(pid);
return 0;
}
EOF
gcc -o /tmp/waitid_demo /tmp/waitid_demo.c
/tmp/waitid_demo
Gerçek Dünya Senaryosu: Basit Bir Process Pool
Sistem yöneticileri olarak zaman zaman bash script’leri içinde paralel iş yönetimi yapmamız gerekiyor. Aşağıdaki örnek, waitpid mantığını bash’te uygulayan ve başarısız işleri yeniden deneyen bir yapı kuruyor:
#!/bin/bash
# process_pool.sh - Paralel is yonetimi ve hata takibi
MAX_PARALLEL=4
RETRY_COUNT=2
declare -A PID_TO_JOB
declare -A JOB_RETRY_COUNT
FAILED_JOBS=()
run_job() {
local job_id=$1
local command=$2
# Islemi arka planda calistir
eval "$command" &
local pid=$!
PID_TO_JOB[$pid]="$job_id:$command"
echo "[$(date '+%H:%M:%S')] Baslatildi: Job-$job_id (PID: $pid)"
echo $pid
}
wait_any_job() {
# Herhangi bir cocuk surecin bitmesini bekle
local pid
wait -n 2>/dev/null
local exit_code=$?
# Hangi PID bittigini bul
for pid in "${!PID_TO_JOB[@]}"; do
if ! kill -0 $pid 2>/dev/null; then
echo "$pid:$exit_code"
unset PID_TO_JOB[$pid]
return
fi
done
}
# Ornek isler listesi (gercekte farkli komutlar olabilir)
JOBS=(
"sleep 2 && echo 'Job 1 tamam'"
"sleep 1 && exit 1" # Basarisiz olacak
"sleep 3 && echo 'Job 3 tamam'"
"sleep 1 && echo 'Job 4 tamam'"
"sleep 2 && echo 'Job 5 tamam'"
)
active_count=0
job_index=0
# Maksimum paralel sayisi kadar is baslatiyoruz
while [ $job_index -lt ${#JOBS[@]} ] || [ $active_count -gt 0 ]; do
# Slot bos ve is varsa yeni is baslat
while [ $active_count -lt $MAX_PARALLEL ] && [ $job_index -lt ${#JOBS[@]} ]; do
run_job $job_index "${JOBS[$job_index]}" > /dev/null
((active_count++))
((job_index++))
done
# Bir isin bitmesini bekle
if [ $active_count -gt 0 ]; then
wait -n
((active_count--))
fi
done
echo "Process pool tamamlandi"
WNOHANG ile Non-Blocking Kontrol
Production sistemlerde en kritik pattern’lerden biri, bloklanmadan çocuk süreç durumunu kontrol etmek. Özellikle event loop’lar veya daemon’lar yazarken bu şart oluyor:
# Non-blocking wait ornegi - C ile
cat > /tmp/nonblocking_wait.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
int main() {
pid_t child_pid = fork();
if (child_pid == 0) {
// Cocuk surecin 3 saniye calismasi
sleep(3);
exit(0);
}
// Ebeveyn event loop'u simule ediyor
int loop_count = 0;
while (1) {
int status;
// WNOHANG: hemen don, bloklama
pid_t result = waitpid(child_pid, &status, WNOHANG);
if (result == 0) {
// Cocuk hala calisiyor
printf("Loop %d: Cocuk hala calisiyor, baska isler yapiliyor...n",
++loop_count);
sleep(1);
} else if (result == child_pid) {
// Cocuk bitti
if (WIFEXITED(status)) {
printf("Cocuk normal cikis yapti, kod: %dn",
WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Cocuk sinyal ile oldu: %dn", WTERMSIG(status));
}
break;
} else {
// Hata
perror("waitpid hatasi");
break;
}
}
return 0;
}
EOF
gcc -o /tmp/nonblocking_wait /tmp/nonblocking_wait.c
/tmp/nonblocking_wait
Macro’larla Exit Status Analizi
waitpid döndürdüğü status değerini doğrudan yorumlamak yerine, standart macro’lar kullanmanız gerekiyor. Bu macro’lar taşınabilirlik sağlıyor ve okunabilirliği artırıyor:
- WIFEXITED(status): Süreç normal çıkış yaptıysa true döner
- WEXITSTATUS(status): Normal çıkışta exit kodunu verir (0-255 arası)
- WIFSIGNALED(status): Süreç bir sinyal tarafından öldürüldüyse true döner
- WTERMSIG(status): Öldüren sinyalin numarasını verir
- WIFSTOPPED(status): Süreç durdurulduysa true döner
- WSTOPSIG(status): Durduran sinyali verir
- WIFCONTINUED(status): SIGCONT ile devam ettirildiyse true döner
# Bash'te exit status analizi - gercek bir deployment scripti ornegi
#!/bin/bash
analyze_exit() {
local pid=$1
local job_name=$2
wait $pid
local status=$?
# Bash'te 128+N formatinda sinyal kayiplarini tespit etme
if [ $status -eq 0 ]; then
echo "OK: $job_name basariyla tamamlandi"
elif [ $status -gt 128 ]; then
local signal=$((status - 128))
echo "SINYAL: $job_name sinyal $signal ile sonlandi"
# Kritik sinyal kontrolu
case $signal in
9) echo " SIGKILL - zorla sonlandirildi (OOM Killer?)" ;;
11) echo " SIGSEGV - segmentation fault!" ;;
15) echo " SIGTERM - nazik kapatma istegi" ;;
esac
else
echo "HATA: $job_name hata kodu $status ile cikti"
fi
}
# Test senaryosu
echo "Deployment kontrol testi basliyor..."
# Basarili is
sleep 1 &
analyze_exit $! "Veritabani baglanti kontrolu"
# Basarisiz is (exit code 1)
(exit 1) &
analyze_exit $! "API endpoint sagligi kontrolu"
# Sinyal ile sonlandirilan is
sleep 100 &
bg_pid=$!
kill -TERM $bg_pid
analyze_exit $bg_pid "Uzun sureli background gorevi"
strace ile Sistem Çağrılarını Gözlemleme
Teorik bilgi güzel ama sistemin gerçekte ne yaptığını görmek çok daha öğretici oluyor. strace ile hem waitpid hem de waitid çağrılarını canlı izleyebilirsiniz:
# Bash'in kendi wait mekanizmasini strace ile izleme
strace -e trace=wait4,waitid -f bash -c 'sleep 1 & wait' 2>&1
# Daha okunakli cikti icin timestamp ekleyerek
strace -T -e trace=wait4,waitid -f bash -c
'for i in 1 2 3; do sleep 0.5 & done; wait' 2>&1 |
grep -E 'wait|exit'
# Var olan bir sureci izlemek icin (örnek: nginx worker'lari)
# Oncelikle nginx master PID'ini bul
NGINX_PID=$(pgrep nginx | head -1)
strace -p $NGINX_PID -e trace=wait4 2>&1 | head -50
strace çıktısında dikkat etmeniz gereken şey, Linux çekirdeğinin waitpid çağrısını aslında wait4 olarak uyguladığıdır. wait4, waitpid‘in rusage (resource usage) desteği eklenmiş halidir.
Zombie Süreç Temizleme: Pratik Yaklaşımlar
Gerçek sistemlerde zombie süreçlerle karşılaştığınızda ne yapacağınızı bilmek kritik önem taşıyor:
# Sistemdeki zombie surecleri tespit etme
ps aux | awk '$8 == "Z" {print $0}'
# Zombie sayisini izleme - threshold alarmı icin
zombie_count=$(ps aux | awk '$8 == "Z"' | wc -l)
if [ $zombie_count -gt 10 ]; then
echo "UYARI: $zombie_count zombie surecler tespit edildi!"
echo "Ebeveyn PID'leri:"
ps aux | awk '$8 == "Z" {print $2}' | while read zpid; do
ppid=$(cat /proc/$zpid/status 2>/dev/null | grep PPid | awk '{print $2}')
echo " Zombie PID: $zpid -> Ebeveyn PID: $ppid"
if [ -n "$ppid" ]; then
echo " Ebeveyn komutu: $(cat /proc/$ppid/cmdline 2>/dev/null | tr '' ' ')"
fi
done
fi
# Ebeveyn sürece SIGCHLD gondererek wait cagrisi yapmaya zorla
# DIKKAT: Bu her zaman islemez, uygulamanin SIGCHLD handle etmesi gerekir
kill -CHLD $PARENT_PID
# Eğer ebeveyn sürec yanit vermiyorsa ve yeniden baslatilabiliyorsa:
# systemctl restart uygulama_adi
# init/systemd'in benimseyecegi orphan surecleri icin:
# Ebeveyn oldugunda init otomatik olarak wait() cagirarak zombieleri temizler
SIGCHLD ile Asenkron Süreç Yönetimi
Profesyonel daemon geliştirmede waitpid‘i doğrudan çağırmak yerine, SIGCHLD sinyal işleyicisi içinde çağırmak yaygın bir pattern. Bu yaklaşım, event-driven mimarilerle çok daha uyumlu:
# SIGCHLD handler ile otomatik zombie temizleme
cat > /tmp/sigchld_demo.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
volatile int children_done = 0;
void sigchld_handler(int signo) {
int status;
pid_t pid;
// Tum bitmis cocuklari temizle (WNOHANG ile loop)
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
// Sinyal guvensiz oldugu icin sadece sayaci artiriyoruz
children_done++;
}
}
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
// 5 cocuk surecin baslatilmasi
for (int i = 0; i < 5; i++) {
if (fork() == 0) {
sleep(1 + (i % 3));
exit(0);
}
}
// Ana dongu - gercek uygulamada baska isler yapilir
while (children_done < 5) {
printf("Calisiyor... Biten cocuk sayisi: %d/5n", children_done);
sleep(1);
}
printf("Tum cocuklar tamamlandi!n");
return 0;
}
EOF
gcc -o /tmp/sigchld_demo /tmp/sigchld_demo.c
/tmp/sigchld_demo
Bir detay önemli: SIGCHLD handler içinde waitpid‘i WNOHANG ile döngüye almak zorunlu. Birden fazla çocuk eş zamanlı biterse, Linux birden fazla sinyal biriktirmeyebilir, tek sinyal gönderir. Döngüsüz yazarsanız bazı zombie’leri kaçırırsınız.
Sonuç
waitpid ve waitid sistem çağrıları, Linux süreç modelin en temel taşlarından ikisi. Sysadmin olarak bu çağrıları doğrudan her gün yazmıyor olabilirsiniz, ama ne zaman ve neden çalıştıklarını anlamak, zombie süreç sorunlarını çözmekten OOM Killer vakalarını analiz etmeye kadar pek çok durumda sizi doğru yönlendiriyor.
Özet olarak şunu söyleyebilirim: waitpid, belirli bir çocuğu veya herhangi bir çocuğu beklemek için hızlı ve pratik bir araç. waitid ise çok daha ayrıntılı bilgi istediğinizde, özellikle sinyal kaynaklı çıkışları ayırt etmeniz gerektiğinde tercih edilmesi gereken daha modern alternatif. WNOHANG flag’i ise her ikisinin de event-driven sistemlerde kullanılabilmesini sağlayan kritik parametre.
Bash script yazıyorsanız, yerleşik wait komutunun arkasında bu mekanizmaların yattığını bilmek, neden bazı durumlarda beklenmedik davranışlar gördüğünüzü açıklıyor. strace ile bu çağrıları kendi sistemlerinizde gözlemlemek, soyut kavramları somutlaştırmanın en etkili yolu.
