Linux’ta Sinyal Yönetimi: sigaction ile Süreç Sinyallerini Kontrol Etme
Bir production sunucusunda çalışan servisin aniden durduğunu fark etmişsinizdir. systemctl status çıktısına bakarsınız, “killed” yazar. Kernel mi durdurdu? Kullanıcı mı elle öldürdü? Servis kendi kendine mi çöktü? İşte bu soruların cevabı çoğunlukla sinyallerde gizlidir. Linux süreç yönetiminin belki de en az anlaşılan ama en kritik parçasından bahsediyoruz: signal mekanizması.
Linux Sinyalleri Nedir, Neden Önemlidir?
Sinyaller, süreçler arasında ya da kernel ile süreç arasında iletişim kurmak için kullanılan asenkron bildirimlerdir. Bir süreç çalışırken dışarıdan “dur”, “devam et”, “temizle ve çık”, “yapılandırmayı yeniden yükle” gibi mesajlar alabilir. Bu mesajların her biri bir sinyal türüne karşılık gelir.
Sysadmin olarak günlük işlerimizde kill -9, Ctrl+C, Ctrl+Z kullanırız. Ama bu işlemlerin arka planda ne tetiklediğini, uygulamanın bu sinyalleri nasıl işlediğini anlamak hem sorun gidermeyi kolaylaştırır hem de daha sağlam servisler yazmanızı sağlar.
Sistemdeki mevcut sinyalleri listelemek için:
kill -l
Bu komut çıktısında SIGTERM, SIGKILL, SIGHUP, SIGCHLD gibi onlarca sinyal görürsünüz. Her birinin numarası ve davranışı farklıdır.
Temel Sinyaller ve Anlamları
Hepsini ezberlemek gerekmez, ama şu temel sinyalleri bilmek günlük işlerinizde fark yaratır:
- SIGHUP (1): Terminal kapandığında ya da daemon’lara “yapılandırmayı yeniden yükle” mesajı olarak gönderilir. Nginx’e
kill -HUPgönderirseniz, servisi durdurmadan yapılandırmayı yeniden yükler. - SIGINT (2): Klavyeden
Ctrl+Cbastığınızda oluşur. Nazik bir durdurma isteğidir. - SIGQUIT (3):
Ctrl+ile tetiklenir. Core dump oluşturur ve çıkar. - SIGKILL (9): İşlemciye gönderilir, uygulamanın handle etmesi mümkün değildir. Kernel direkt öldürür. Temizlik yapılamaz.
- SIGTERM (15): Standart sonlandırma sinyali. Uygulama bu sinyali yakalayabilir, temizlik yapıp düzgünce çıkabilir.
- SIGUSR1 (10) ve SIGUSR2 (12): Kullanıcı tanımlı sinyaller. Uygulamalar bunlara istediği anlamı yükleyebilir.
- SIGCHLD (17): Bir child süreç durduğunda parent’a gönderilir.
- SIGSTOP (19): Süreci dondurur.
Ctrl+Z‘nin kernel versiyonu. Handle edilemez. - SIGCONT (18): Dondurulmuş süreci devam ettirir.
kill ve killall ile Sinyal Gönderme
En basit kullanım senaryosuyla başlayalım. Bir sürecin PID’ini biliyor ve ona sinyal göndermek istiyorsunuz:
# SIGTERM gönder (varsayılan)
kill 1234
# SIGKILL gönder
kill -9 1234
# SIGHUP gönder (sinyal adıyla)
kill -HUP 1234
# Sinyal numarasıyla
kill -s 15 1234
# İsme göre tüm eşleşen süreçlere sinyal gönder
killall -HUP nginx
# killall ile SIGTERM (konfig reload için)
killall -USR1 php-fpm
Burada önemli bir detay var: kill -9 kullanmayı alışkanlık haline getirmeyin. Evet hızlı çalışır, ama uygulama açık dosya tanıtıcılarını, geçici dosyaları, database bağlantılarını temizleyemez. Production’da gereksiz SIGKILL kullanımı veri tutarsızlıklarına yol açabilir. Önce SIGTERM deneyin, bekleyin, sonuç yoksa SIGKILL‘e geçin.
pkill ve pgrep ile Daha Akıllı Sinyal Yönetimi
# Süreç adına göre sinyal gönder
pkill -TERM nginx
# Belirli kullanıcının süreçlerine sinyal gönder
pkill -u webuser -TERM
# Tam eşleşme ile (kısmi eşleşmeyi önler)
pkill -x "my-daemon"
# Sinyal göndermeden önce hangi süreçlerin etkileneceğini gör
pgrep -l nginx
# Detaylı PID ve komut listesi
pgrep -a "python"
Bir production senaryosu: Birden fazla PHP-FPM worker’ı var ve tamamını graceful şekilde yeniden başlatmak istiyorsunuz. kill -9 kullanırsanız süreçler aktif istekleri keser. Bunun yerine:
# PHP-FPM master process'e graceful reload sinyali gönder
pkill -USR2 php-fpm
# Sonucu kontrol et
systemctl status php-fpm
/proc Üzerinden Sinyal Durumu İnceleme
Bir sürecin hangi sinyalleri bloklamakta, hangilerini ignore etmekte olduğunu görmek mümkün:
# Sürecin sinyal maskesi bilgisi
cat /proc/1234/status | grep -E "Sig(Blk|Ign|Cgt|Pnd)"
Çıktıda şöyle satırlar görürsünüz:
- SigPnd: Bekleyen sinyaller
- SigBlk: Bloklanmış sinyaller
- SigIgn: Ignore edilen sinyaller
- SigCgt: Yakalanan (handler atanmış) sinyaller
Bu değerler hexadecimal bitmask formatındadır. Decode etmek için:
# Python ile decode
python3 -c "
mask = 0x0000000000000002 # SigIgn değerini buraya yapıştır
for i in range(1, 65):
if mask & (1 << (i-1)):
print(f'Signal {i}')
"
Bu bilgi özellikle bir daemon’ın neden belirli bir sinyale tepki vermediğini anlamaya çalışırken çok işe yarıyor.
strace ile Sinyal İzleme
Bir sürecin gerçek zamanlı olarak hangi sinyalleri aldığını izlemek için strace kullanabilirsiniz:
# Mevcut bir sürece bağlan ve sinyalleri izle
strace -e trace=signal -p 1234
# Hem sinyal hem de sistem çağrılarını izle
strace -p 1234 2>&1 | grep -E "signal|kill|SIG"
# Bir komut başlatırken izle
strace -e signal ./my-application
Özellikle “neden bu servis duruyor” sorusunu araştırırken strace çıktısı altın değerinde olabiliyor.
C ile sigaction Kullanımı
Şimdi işin yazılım tarafına geçelim. Sistem yöneticisi olsanız bile, yönettiğiniz servislerin signal handler’larını nasıl yazdığını anlamak önemli. Üstelik script veya küçük araçlar yazarken de işinize yarar.
Eski signal() fonksiyonu yerine sigaction() kullanmak modern ve taşınabilir yaklaşımdır. signal() davranışı sistem bağımlıyken, sigaction() deterministik davranış garantisi verir.
Temel bir örnek:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
// Signal handler - async-signal-safe fonksiyonlar kullanılmalı
void handle_sigterm(int signo) {
// write() async-signal-safe, printf() değil
const char *msg = "SIGTERM alindi, temizlik yapiliyor...n";
write(STDOUT_FILENO, msg, strlen(msg));
// Gerçekte burada cleanup yapılır
_exit(0);
}
void handle_sighup(int signo) {
const char *msg = "SIGHUP alindi, yapilandirma yeniden yukleniyor...n";
write(STDOUT_FILENO, msg, strlen(msg));
}
int main() {
struct sigaction sa_term, sa_hup;
// SIGTERM handler
memset(&sa_term, 0, sizeof(sa_term));
sa_term.sa_handler = handle_sigterm;
sigemptyset(&sa_term.sa_mask);
// Handler çalışırken SIGTERM tekrar gelirse blokla
sigaddset(&sa_term.sa_mask, SIGTERM);
sa_term.sa_flags = 0;
if (sigaction(SIGTERM, &sa_term, NULL) == -1) {
perror("sigaction SIGTERM");
exit(1);
}
// SIGHUP handler
memset(&sa_hup, 0, sizeof(sa_hup));
sa_hup.sa_handler = handle_sighup;
sigemptyset(&sa_hup.sa_mask);
sa_hup.sa_flags = SA_RESTART; // Sistem çağrılarını otomatik yeniden başlat
if (sigaction(SIGHUP, &sa_hup, NULL) == -1) {
perror("sigaction SIGHUP");
exit(1);
}
printf("Surecin PID'i: %dn", getpid());
printf("SIGTERM veya SIGHUP gonderin...n");
while (1) {
pause(); // Sinyal gelene kadar bekle
}
return 0;
}
Bu kodu derleyip çalıştırdıktan sonra başka bir terminalde test edebilirsiniz:
gcc -o signal_demo signal_demo.c
./signal_demo &
kill -HUP $!
kill -TERM $!
SA_RESTART Bayrağı ve Neden Önemlidir
sigaction yapısındaki sa_flags alanı kritik davranışları kontrol eder. En sık karşılaşılan sorun şu: bir sinyal handler çalışırken devam eden bir read() veya write() sistem çağrısı EINTR hatasıyla kesilir. Bunu ya elle handle edersiniz ya da SA_RESTART bayrağını kullanırsınız.
// SA_RESTART olmadan - sistem çağrısı EINTR döndürebilir
sa.sa_flags = 0;
// SA_RESTART ile - sistem çağrıları otomatik yeniden başlatılır
sa.sa_flags = SA_RESTART;
// SA_SIGINFO ile - detaylı sinyal bilgisi alırsınız
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handle_with_info; // sa_handler yerine sa_sigaction kullanılır
// SA_SIGINFO handler örneği
void handle_with_info(int signo, siginfo_t *info, void *context) {
// info->si_pid: sinyali gönderen sürecin PID'i
// info->si_uid: gönderen kullanıcının UID'i
// info->si_code: sinyal kodu (SI_USER, SI_KERNEL, vb.)
char buf[128];
snprintf(buf, sizeof(buf), "Sinyal %d, gonderen PID: %dn",
signo, info->si_pid);
write(STDOUT_FILENO, buf, strlen(buf));
}
Bash Script’lerde trap ile Sinyal Yönetimi
C kodu yazmak zorunda değilsiniz. Bash script’lerinde trap komutu ile sinyalleri yakalayabilirsiniz. Özellikle uzun çalışan bakım script’lerinde bu kritik öneme sahiptir:
#!/bin/bash
TMPFILE=$(mktemp /tmp/backup.XXXXXX)
LOCKFILE="/var/run/mybackup.lock"
# Temizlik fonksiyonu
cleanup() {
echo "[$(date)] Temizlik yapiliyor..."
rm -f "$TMPFILE"
rm -f "$LOCKFILE"
echo "[$(date)] Cikiliyor."
exit 0
}
# Hata durumu handler
error_handler() {
echo "[$(date)] HATA: Script beklenmedik sekilde sonlandi" >&2
cleanup
exit 1
}
# Sinyal handler'larını kaydet
trap cleanup SIGTERM SIGINT SIGHUP
trap error_handler ERR
# Kilit dosyası oluştur
if [ -f "$LOCKFILE" ]; then
echo "Script zaten calisıyor (lockfile mevcut)"
exit 1
fi
touch "$LOCKFILE"
echo "Yedekleme basladi, PID: $$"
echo "Durdurmak icin: kill -TERM $$"
# Uzun süren işlem simülasyonu
for i in $(seq 1 100); do
echo "Adim $i/100 isleniyor..."
sleep 2
echo "veri_$i" >> "$TMPFILE"
done
cleanup
Bu pattern’ı production backup script’lerinde, veritabanı bakım script’lerinde, toplu veri işleme script’lerinde mutlaka kullanın. Ctrl+C veya servis durdurma sinyali geldiğinde yarım kalan işlemlerin temizlenmesi sistem tutarlılığı açısından şart.
Daemon Geliştirirken Sinyal Stratejisi
Kendi daemon’ınızı veya uzun çalışan servisinizi yazıyorsanız, şu sinyal stratejisini benimsemenizi tavsiye ederim:
#!/bin/bash
# Örnek: Production ortamında çalışan bir veri işleme daemon'u
RUNNING=true
RELOAD=false
handle_term() {
echo "$(date): Graceful shutdown baslatildi"
RUNNING=false
}
handle_hup() {
echo "$(date): Yapilandirma yeniden yukleme istegi"
RELOAD=true
}
handle_usr1() {
# Durum raporu - log dosyasına yaz
echo "$(date): === DURUM RAPORU ===" >> /var/log/mydaemon.log
echo "Islenen kayit sayisi: $PROCESSED_COUNT" >> /var/log/mydaemon.log
echo "Hata sayisi: $ERROR_COUNT" >> /var/log/mydaemon.log
}
trap handle_term SIGTERM SIGINT
trap handle_hup SIGHUP
trap handle_usr1 SIGUSR1
PROCESSED_COUNT=0
ERROR_COUNT=0
load_config() {
# Yapilandirma dosyasini yeniden oku
source /etc/mydaemon/config.conf
echo "$(date): Yapilandirma yuklendi"
RELOAD=false
}
load_config
echo "$(date): Daemon baslatildi, PID: $$"
while $RUNNING; do
if $RELOAD; then
load_config
fi
# Ana iş döngüsü
if process_next_item; then
((PROCESSED_COUNT++))
else
((ERROR_COUNT++))
fi
sleep 1
done
echo "$(date): Daemon duzgunce sonlandi. Toplam islenen: $PROCESSED_COUNT"
Bu yaklaşımla systemctl reload mydaemon çalıştırdığınızda SIGHUP tetiklenir ve daemon servisi kesmeden yapılandırmayı yeniler. systemctl stop ise SIGTERM gönderir, daemon mevcut işini tamamlar ve temiz çıkar.
Gerçek Hayat Senaryosu: Nginx Sinyal Yönetimi
Nginx, sinyal yönetiminin en güzel örneklerinden birini sunar. Tüm bu teorinin production’da nasıl işlediğini görmek için:
# Nginx master PID'ini bul
cat /var/run/nginx.pid
# veya
pgrep -f "nginx: master"
NGINX_PID=$(cat /var/run/nginx.pid)
# Yapılandırmayı yeniden yükle (sıfır downtime)
kill -HUP $NGINX_PID
# Binary'yi graceful olarak yükselt (zero-downtime upgrade)
kill -USR2 $NGINX_PID
# Worker'ları graceful kapat (yeni bağlantı kabul etme, mevcutları bitir)
kill -WINCH $NGINX_PID
# Tüm logları yeniden aç (log rotation sonrası)
kill -USR1 $NGINX_PID
# Nginx'in aldığı sinyalleri izle
strace -e trace=signal -p $NGINX_PID 2>&1 | head -50
Nginx mimarisini incelediğinizde göreceksiniz ki her sinyal belirli bir operasyona karşılık gelir ve bu operasyonlar yıllarca titizlikle test edilmiştir. Kendi servisleriniz için de böyle bir sinyal haritası oluşturmanızı öneririm.
systemd ve Sinyal İlişkisi
systemd ile çalışıyorsanız (ki büyük ihtimalle öyledir), unit dosyasındaki bazı direktifler doğrudan sinyal gönderimini kontrol eder:
# Unit dosyasında sinyal ayarları
# /etc/systemd/system/myservice.service
# [Service] bölümüne eklenecekler:
# KillSignal=SIGTERM # Durdurma sinyali (varsayılan SIGTERM)
# KillMode=mixed # Master'a SIGTERM, worker'lara SIGKILL
# TimeoutStopSec=30 # SIGTERM sonrası SIGKILL için bekleme süresi
# SendSIGKILL=yes # Timeout sonrası SIGKILL gönder
# Mevcut bir servisin kill ayarlarını görüntüle
systemctl show nginx | grep -E "Kill|Timeout"
# Servis durdurulurken hangi sinyalin gönderildiğini izle
journalctl -u myservice -f &
systemctl stop myservice
TimeoutStopSec değerini servisinizin graceful shutdown süresine göre ayarlamak önemlidir. Varsayılan 90 saniyedir, ama bazı servisler daha hızlı kapanabilir ya da büyük veritabanları daha uzun süre gerektirebilir.
Sonuç
Sinyal yönetimi, Linux’ta süreç kontrolünün temel taşıdır. Sysadmin olarak bu mekanizmayı anlamak size birkaç somut kazanım sağlar: problem giderme süreniz kısalır, production olaylarında daha doğru müdahale edersiniz ve yönettiğiniz servislerin davranışını daha iyi tahmin edebilirsiniz.
Özet olarak:
- SIGTERM ile dene, SIGKILL son çare: Uygulamalara temizlik yapma fırsatı verin
- strace ile izle: Bir sürecin hangi sinyalleri aldığını görmek sorun gidermede mucizeler yaratır
- Bash script’lerinizde trap kullan: Bakım script’leri yarım kalmasın, kaynak sızıntısı olmasın
- sigaction tercih et, eski signal() fonksiyonundan kaçın: Deterministik davranış için
- SA_RESTART bayrağını değerlendirin: EINTR kaynaklı sürpriz hatalardan kaçının
- systemd unit dosyalarındaki sinyal ayarlarını inceleyin: TimeoutStopSec ve KillSignal direktiflerini bilin
- /proc/PID/status’u okumayı öğrenin: Bir sürecin hangi sinyalleri bloklamakta olduğunu anlamak çok değerli
Bu konuya harcadığınız her saat, ilerleyen dönemde production sorunlarını çözerken size katkat geri döner. Sinyaller sessiz sedasız çalışır, ama sistemi ayakta tutan mekanizmanın önemli bir parçasıdır.
