Çalışan Süreçlerin /proc Dosya Sistemi ile Dosya ve Bellek Haritasını Okuma
Prod ortamında bir process çöküyor ve siz neden çöktüğünü anlamaya çalışıyorsunuz. Core dump yok, log yok, sadece systemd’nin “Killed” yazdığı bir satır var. İşte tam bu noktada /proc dosya sistemi hayat kurtarıcı oluyor. Peki ya yedekleme tarafında? Çalışan bir sürecin tam o anki halini, açık dosyalarını, bellek haritasını ve ortam değişkenlerini kayıt altına almak için /proc‘u ne kadar etkin kullanıyoruz? Büyük ihtimalle yeterince değil.
Bu yazıda, /proc üzerinden çalışan süreçlerin dosya ve bellek haritasını nasıl okuyabileceğimizi, bunu nasıl yedekleyebileceğimizi ve gerçek kriz senaryolarında bu bilgileri nasıl kullanabileceğimizi ele alacağım. Saf teori değil, sahada öğrenilmiş şeyler bunlar.
/proc Dosya Sistemi Nedir, Neden Önemlidir?
/proc bir dosya sistemi değil aslında, bir illüzyon. Kernel’ın çalışma anındaki durumunu sanal dosyalar aracılığıyla userspace’e sunan bir arayüz. Disktte hiçbir şey yazmıyor, her okuduğunuzda kernel o anda üretiyor verileri.
Her çalışan process için /proc/[PID]/ altında ayrı bir dizin oluşturulur. Bu dizinin içindeki dosyalar o sürecin tam o andaki durumunu yansıtır. Process ölünce dizin de kaybolur. Dolayısıyla buradaki bilgileri kayıt altına almak istiyorsanız, process yaşarken yapmanız gerekiyor.
Sistemdeki aktif process dizinlerini görmek için:
ls /proc | grep -E '^[0-9]+$' | head -20
Bir process’in /proc dizininde neler olduğunu görmek için örnek olarak PID 1’e bakalım:
ls -la /proc/1/
Çıktıda göreceğiniz dosyaların her birinin ayrı bir hikayesi var. Şimdi bunları tek tek inceleyelim ve yedekleme perspektifinden nasıl değerlendirileceğini anlatalım.
Temel /proc/[PID] Dosyaları ve Okunması
cmdline: Process’in Tam Komutu
Bir process’in hangi argümanlarla başlatıldığını öğrenmek için cmdline dosyasını okuyoruz. Bu dosyadaki argümanlar null byte () ile ayrılmış halde gelir.
# Ham haliyle okumak
cat /proc/$(pgrep nginx | head -1)/cmdline
# Okunabilir formatta
tr '' ' ' < /proc/$(pgrep nginx | head -1)/cmdline
echo ""
# Tüm processler için toplu okuma
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
cmdline=$(tr '' ' ' < /proc/$pid/cmdline 2>/dev/null)
[ -n "$cmdline" ] && echo "$pid: $cmdline"
done
Yedekleme senaryosunda bu çok kritik. Bir servisi yeniden başlatmanız gerektiğinde tam parametreleri bilmeniz lazım. Systemd kullanan ortamlarda bu bilgi unit dosyasında olabilir, ama elle başlatılmış processlerde cmdline tek gerçek kaynaktır.
environ: Ortam Değişkenleri
Ortam değişkenleri de null byte ile ayrılmış halde gelir:
# Bir process'in ortam değişkenlerini oku
cat /proc/$(pgrep java | head -1)/environ | tr '' 'n' | sort
# Belirli bir değişkeni bul
cat /proc/$(pgrep java | head -1)/environ | tr '' 'n' | grep JAVA_HOME
Gerçek hayatta şuna defalarca tanık oldum: Java uygulaması yeni makinede çalışmıyor, bakıyorsunuz JAVA_OPTS veya heap ayarları environ dosyasında gizlenmiş, kimse nerede tanımlandığını bilmiyor. Eski sunucudaki process’in environ yedeklenmeseydi bu bilgiye ulaşmak mümkün olmazdı.
fd: Açık Dosya Tanımlayıcıları
/proc/[PID]/fd/ dizini, process’in şu an açık tuttuğu tüm dosyaları, soketleri ve pipe’ları gösterir:
# Açık dosyaları listele
ls -la /proc/$(pgrep postgres | head -1)/fd/
# Sadece gerçek dosyaları göster (soketler hariç)
ls -la /proc/$(pgrep postgres | head -1)/fd/ | grep -v socket | grep -v pipe
# Dosya sayısını öğren
ls /proc/$(pgrep postgres | head -1)/fd/ | wc -l
# Hangi dosyaların açık olduğunu yollarıyla göster
for fd in /proc/$(pgrep nginx | head -1)/fd/*; do
target=$(readlink "$fd" 2>/dev/null)
[ -n "$target" ] && echo "$(basename $fd) -> $target"
done
Bir process’in ulimit -n sınırına yaklaşıp yaklaşmadığını tespit etmek için fd sayısını izlemek çok değerli. Bunu periyodik olarak kayıt altına alırsanız, “ne zaman başladı bu sorun?” sorusuna yanıt verebilirsiniz.
maps: Bellek Haritası
İşte asıl güçlü kısım burası. /proc/[PID]/maps dosyası, process’in bellek adres uzayını gösterir. Her satır bir bellek segmentini temsil eder:
# Bellek haritasını oku
cat /proc/$(pgrep python3 | head -1)/maps
# Yalnızca yüklü shared library'leri göster
cat /proc/$(pgrep python3 | head -1)/maps | awk '{print $6}' | grep '.so' | sort -u
# Anonymous mapping'leri say (heap, stack büyümesi burada görünür)
cat /proc/$(pgrep python3 | head -1)/maps | grep -c 'anon'
maps dosyasının çıktısındaki sütunlar sırasıyla şunlardır:
- Adres aralığı: Başlangıç-bitiş adresi (hex)
- İzinler: r (okuma), w (yazma), x (çalıştırma), p (private) veya s (shared)
- Offset: Dosyadan offset
- Device: Major:minor device numarası
- İnode: Dosya inode numarası
- Pathname: Dosya yolu veya [heap], [stack], [vdso] gibi özel alanlar
smaps: Detaylı Bellek İstatistikleri
maps yüzeysel bilgi verirken, smaps her segment için detaylı bellek kullanım istatistikleri sunar:
# Toplam PSS (Proportional Set Size) hesapla
awk '/^Pss:/ {sum += $2} END {print sum " kB"}' /proc/$(pgrep nginx | head -1)/smaps
# RSS toplamı
awk '/^Rss:/ {sum += $2} END {print sum " kB"}' /proc/$(pgrep nginx | head -1)/smaps
# Tüm processler için bellek kullanımı özeti
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
name=$(cat /proc/$pid/comm 2>/dev/null)
pss=$(awk '/^Pss:/ {sum += $2} END {print sum}' /proc/$pid/smaps 2>/dev/null)
[ -n "$pss" ] && echo "$pid $name $pss kB"
done | sort -k3 -rn | head -20
Bu çıktı, gerçek anlamda hangi process’in ne kadar RAM tükettiğini gösterir. top ve htop‘un gösterdiği RSS değerinden farklı olarak, shared memory’nin her process’e orantılı olarak dağıtılmış halini gösterir. Memory leak analizi yaparken bu fark kritik önem taşır.
Kapsamlı Process Snapshot Scripti
Şimdi tüm bu bilgileri bir araya getiren gerçek bir yedekleme scripti yazalım. Amacımız: bir process’in tam o andaki durumunu disk’e kaydetmek.
#!/bin/bash
# process_snapshot.sh
# Belirtilen PID'deki process'in /proc bilgilerini yedekler
SNAPSHOT_DIR="/var/lib/process-snapshots"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
take_snapshot() {
local pid=$1
local proc_name=$(cat /proc/$pid/comm 2>/dev/null || echo "unknown")
local snapshot_path="$SNAPSHOT_DIR/${proc_name}_${pid}_${TIMESTAMP}"
mkdir -p "$snapshot_path"
echo "[*] Snapshot alınıyor: PID=$pid, Process=$proc_name"
# Temel dosyaları kaydet
for file in cmdline environ status stat statm comm; do
if [ -f "/proc/$pid/$file" ]; then
cat "/proc/$pid/$file" > "$snapshot_path/$file" 2>/dev/null
fi
done
# Okunabilir formatlar
tr '' ' ' < "/proc/$pid/cmdline" > "$snapshot_path/cmdline_readable" 2>/dev/null
tr '' 'n' < "/proc/$pid/environ" > "$snapshot_path/environ_readable" 2>/dev/null
# Bellek haritaları
cp "/proc/$pid/maps" "$snapshot_path/maps" 2>/dev/null
cp "/proc/$pid/smaps" "$snapshot_path/smaps" 2>/dev/null
# Açık dosya tanımlayıcıları
ls -la "/proc/$pid/fd/" > "$snapshot_path/fd_list" 2>/dev/null
for fd in /proc/$pid/fd/*; do
readlink "$fd" 2>/dev/null
done > "$snapshot_path/fd_targets"
# Ağ bağlantıları
cat "/proc/$pid/net/tcp" > "$snapshot_path/net_tcp" 2>/dev/null
cat "/proc/$pid/net/tcp6" > "$snapshot_path/net_tcp6" 2>/dev/null
# Thread bilgisi
ls /proc/$pid/task/ > "$snapshot_path/threads" 2>/dev/null
# Limits
cat "/proc/$pid/limits" > "$snapshot_path/limits" 2>/dev/null
echo "[+] Snapshot tamamlandı: $snapshot_path"
echo "$snapshot_path"
}
# Ana script
if [ -z "$1" ]; then
echo "Kullanim: $0 <PID veya process_name>"
exit 1
fi
mkdir -p "$SNAPSHOT_DIR"
# PID mi isim mi?
if [[ "$1" =~ ^[0-9]+$ ]]; then
take_snapshot "$1"
else
pids=$(pgrep "$1")
if [ -z "$pids" ]; then
echo "Process bulunamadi: $1"
exit 1
fi
for pid in $pids; do
take_snapshot "$pid"
done
fi
Bellek Haritasından Library Bağımlılıklarını Çıkarma
Bir uygulamayı farklı bir sunucuya taşırken hangi shared library’lerin gerektiğini bilmek kritik. maps dosyası bunun için mükemmel bir kaynak:
#!/bin/bash
# Çalışan bir process'in tüm .so bağımlılıklarını listele ve kopyala
pid=$1
output_dir="/tmp/libs_backup_$(date +%Y%m%d%H%M%S)"
mkdir -p "$output_dir"
echo "PID $pid için library bağımlılıkları toplanıyor..."
# maps'ten .so dosyalarını çek
libs=$(awk '{print $6}' /proc/$pid/maps | grep '.so' | sort -u)
for lib in $libs; do
if [ -f "$lib" ]; then
# Dizin yapısını koru
lib_dir="$output_dir$(dirname $lib)"
mkdir -p "$lib_dir"
cp "$lib" "$lib_dir/"
echo "Kopyalandi: $lib"
fi
done
# Bağımlılık listesini kaydet
echo "$libs" > "$output_dir/library_list.txt"
echo "Toplam $(echo "$libs" | wc -l) library kaydedildi: $output_dir"
Bu script’i production’da bir Java uygulaması için çalıştırdığımda, beklenmedik bir şekilde /opt/vendor/lib/ altında özel bir JNI library seti bulmuştum. Standart ldd çıktısında görünmüyordu çünkü dinamik olarak yüklenmişti. maps olmadan bu bağımlılığı asla bulamazdık.
/proc/[PID]/status ile Süreç Durumu Anlık Görüntüsü
status dosyası, process hakkında insan tarafından okunabilir özet bilgiler içerir:
# Tek bir process'in durumunu oku
cat /proc/$(pgrep mysql | head -1)/status
# Belirli alanları filtrele
grep -E 'Name:|Pid:|VmRSS:|VmSize:|Threads:|voluntary_ctxt_switches:'
/proc/$(pgrep mysql | head -1)/status
# Kritik sistemdeki tüm processlerin bellek kullanımını toplu izle
for service in nginx php-fpm mysql redis; do
pid=$(pgrep $service | head -1)
if [ -n "$pid" ]; then
vmrss=$(grep VmRSS /proc/$pid/status | awk '{print $2}')
echo "$service (PID: $pid): ${vmrss} kB RSS"
fi
done
Periyodik Monitoring ile Trend Analizi
Anlık snapshot almak bir şey, ama zaman içindeki değişimi izlemek çok daha değerli. Şu basit script’i cron’a ekleyerek bellek trendlerini kayıt altına alabilirsiniz:
#!/bin/bash
# proc_memory_trend.sh - /proc'tan periyodik bellek kayıtları alır
LOG_FILE="/var/log/process_memory_trend.log"
WATCH_PROCESSES="nginx java postgres redis"
log_memory() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
for proc_name in $WATCH_PROCESSES; do
for pid in $(pgrep "$proc_name" 2>/dev/null); do
# smaps'ten PSS oku (daha gerçekçi bellek kullanımı)
pss=$(awk '/^Pss:/ {sum += $2} END {print sum}'
/proc/$pid/smaps 2>/dev/null)
# VmRSS ve VmSwap değerlerini de al
vmrss=$(grep '^VmRSS:' /proc/$pid/status 2>/dev/null | awk '{print $2}')
vmswap=$(grep '^VmSwap:' /proc/$pid/status 2>/dev/null | awk '{print $2}')
# fd sayısını al (file descriptor leak takibi için)
fd_count=$(ls /proc/$pid/fd/ 2>/dev/null | wc -l)
echo "$timestamp,$proc_name,$pid,PSS:${pss}kB,RSS:${vmrss}kB,SWAP:${vmswap}kB,FD:$fd_count" >> "$LOG_FILE"
done
done
}
log_memory
Bu script’i 5 dakikada bir cron’a ekleyin:
# crontab -e
*/5 * * * * /usr/local/bin/proc_memory_trend.sh
Bir hafta sonra log dosyanızı awk veya basit bir Python script’iyle analiz ettiğinizde, memory leak’in ne zaman başladığını ve hangi FD sayısıyla ilişkili olduğunu görebilirsiniz. Ben bu yöntemi bir kez production’da geriye dönük analiz için kullandım, log dosyasına bakarak Java process’inin memory’sinin 3 günde %40 arttığını, FD sayısının ise sabit kaldığını gördüm. Bu bilgi, sorunun FD leak değil heap yönetimi sorunu olduğunu işaret ediyordu ve gerçekten de öyleydi.
/proc Tabanlı Yedekleme İçin Dikkat Edilmesi Gereken Noktalar
/proc ile çalışırken bazı tuzaklar var, bunları baştan bilmek zaman kaybını önler.
Race condition problemi: Process ölürken /proc/[PID]/ dizinini okumaya çalışırsanız TOCTOU (Time of Check to Time of Use) hatasıyla karşılaşırsınız. Script’lerinizde 2>/dev/null kullanmayı ve dönüş kodlarını kontrol etmeyi alışkanlık haline getirin.
Root yetkisi gereklilikleri: Başka bir kullanıcıya ait process’in /proc/[PID]/environ veya /proc/[PID]/mem dosyalarını okumak için root yetkisi gerekir. Kendi process’leriniz için bu kısıtlama yok ama monitoring scriptleri genellikle root olarak çalışmak zorunda kalır.
smaps performans etkisi: smaps dosyasını çok sık okumak (saniyede birden fazla) bazı kernel versiyonlarında CPU spike’a yol açabilir. Özellikle binlerce mapping’e sahip Java process’lerinde dikkatli olun. Monitoring aralığını en az 30 saniye tutun.
PID yeniden kullanımı: Linux PID’leri recycle eder. 32768 PID limitine ulaşınca tekrar 1’den başlar. Snapshot aldıktan sonra PID’i not edin, ama process adını ve başlangıç zamanını da (/proc/[PID]/stat dosyasındaki 22. alan) kaydedin. Böylece aynı PID’e farklı bir process geldiğinde karışıklık olmaz.
# Process başlangıç zamanını öğren
pid=1234
start_time=$(awk '{print $22}' /proc/$pid/stat 2>/dev/null)
boot_time=$(awk '/btime/ {print $2}' /proc/stat)
echo "Process başlangıç zamanı: $(date -d @$(( boot_time + start_time / 100 )) 2>/dev/null)"
Otomatik Yedeklemeyi Systemd ile Entegre Etmek
Kritik servisler için systemd’nin ExecStopPre direktifini kullanarak servis durduğunda otomatik snapshot alabilirsiniz:
# /etc/systemd/system/myapp.service dosyasına eklenecek
[Service]
ExecStartPre=/usr/local/bin/cleanup_old_snapshots.sh
ExecStopPre=/usr/local/bin/process_snapshot.sh %n
Ya da daha pratik bir yol olarak, process’i izleyen ayrı bir servis yazın:
# /etc/systemd/system/myapp-watchdog.service
[Unit]
Description=MyApp Process Watchdog
After=myapp.service
[Service]
Type=simple
ExecStart=/usr/local/bin/proc_watchdog.sh myapp
Restart=always
[Install]
WantedBy=multi-user.target
#!/bin/bash
# proc_watchdog.sh - Process'i izler, ölmeden önce snapshot alır
PROC_NAME=$1
SNAPSHOT_THRESHOLD_MB=4096 # 4GB üzerine çıkarsa snapshot al
CHECK_INTERVAL=30
while true; do
pid=$(pgrep -x "$PROC_NAME" | head -1)
if [ -n "$pid" ]; then
rss=$(grep VmRSS /proc/$pid/status 2>/dev/null | awk '{print $2}')
rss_mb=$((rss / 1024))
if [ "$rss_mb" -gt "$SNAPSHOT_THRESHOLD_MB" ]; then
echo "UYARI: $PROC_NAME (PID:$pid) ${rss_mb}MB kullanıyor, snapshot alınıyor"
/usr/local/bin/process_snapshot.sh "$pid"
fi
fi
sleep "$CHECK_INTERVAL"
done
Sonuç
/proc dosya sistemi, Linux’ta çalışan her sistem yöneticisinin derinlemesine bilmesi gereken bir araç. Yedekleme stratejilerinden bahsederken genellikle veritabanı dump’ları ve dosya sistemi snapshot’larına odaklanıyoruz, ama çalışan process’lerin durumunu kayıt altına almak da en az bunlar kadar kritik olabiliyor.
cmdline ile başlangıç parametrelerini, environ ile ortam değişkenlerini, maps ve smaps ile bellek haritasını, fd ile açık dosyaları kayıt altına almak; bir sistem krizi anında geri dönmek için ihtiyacınız olan her şeyi sağlıyor. Üstelik bu işlemlerin hiçbiri process’i durdurmayı gerektirmiyor, tamamen non-invasive bir yöntem.
Yukarıdaki script’leri kendi ortamınıza uyarlayarak başlayın. Önce kritik servisleri listeleyin, ardından periyodik monitoring’i devreye alın. Bir sorun yaşadığınızda “keşke snapshot almış olsaydım” demek yerine, sorunun tam haritasına bakıyor olacaksınız.
