Linux'ta Blocking I/O ve Bekleme Sorunlarını Kernel Seviyesinde Anlamak
🇹🇷 Programınız kilitlendi mi? CPU %0 ama yanıt yok mu? select, poll ve epoll dünyasına girerek, programların bekleme nedenlerini 'Kernel Gözünden' inceliyor; Blocking I/O, Timeout ve Deadlock kavramlarını strace ile analiz ediyoruz.
Önceki yazımızda Linux’ta Network Sorunlarını Syscall Perspektifiyle Okumak başlığı altında, kabloların dünyasına inmiş, Connection Refused ile Timeout arasındaki farkı ve DNS tuzaklarını incelemiştik.
Peki ağda sorun yok, diskte yer var, RAM bol… Ama program yine de kaplumbağa gibi? Hatta bazen donup kalıyor?
Ekrana boş boş bakan bir terminal, %0 CPU kullanımı ama yanıt vermeyen bir servis… Bu sessizlik, bir sistem yöneticisi için gürültülü bir hata mesajından daha korkutucudur.
Siz: “Program çalışmıyor. Ama çökmedi de. Loglarda hata yok.”
Yazılımcı: “Benim kodumda sonsuz döngü olsa CPU tavan yapardı. Bu program bir şey bekliyor ama neyi?”
📚 Teknik Mola: Load Average Gerçeği Çoğu kişi
Load Averagedeğerini sadece “CPU kullanımı” sanır. Yanlış! Linux’ta Load Average = CPU üzerinde çalışanlar + Sırada bekleyenler (Waiting). Eğer Load yüksek ama CPU %0 ise, bu “Disk veya Ağ Bekleyen” (Uninterruptible Sleep - D state) süreçler var demektir. Yani trafik sıkışıklığı otobanda değil, gişelerdedir.
Bu yazıda, Linux’un “Bekleme Odası”na giriyoruz. Programlar neden bekler? select, poll ve epoll arkada ne iş çevirir? Ve en önemlisi, “Zaman”ı strace ile nasıl ölçeriz?
📌 Bu Yazıda Ne Öğreneceksin?
Bu yazı, sadece “performans iyileştirme” değil, sistemin nabzını tutma rehberidir. Okumayı bitirdiğinizde:
- Beklemenin Anatomisi: Blocking I/O ile Non-Blocking I/O arasındaki farkı ve CPU’nun neden uykuya daldığını anlayacaksınız.
- Trafik Polisi (Multiplexing): 10.000 bağlantıyı yöneten bir sunucunun
selectile neden boğulduğunu,epollile nasıl uçuşa geçtiğini (veio_uringile geleceğe nasıl hazırlandığını) göreceksiniz. - Zamanın Resmi:
strace -Tkullanarak, bir veritabanı sorgusunun veya API çağrısının kaç milisaniye sürdüğünü cerrah titizliğiyle ölçmeyi öğreneceksiniz. - Sessiz Katil (Deadlock): CPU kullanmadan programı öldüren
futexkilitlenmelerini nasıl teşhis edeceğinizi keşfedeceksiniz. - 60 Saniye Efsanesi: O meşhur “Timeout” hatalarının Kernel tarafındaki karşılığını göreceksiniz.
Kısacası; Programınızın “donduğunu” sandığınız anlarda, aslında onun size ne anlatmaya çalıştığını duyabileceksiniz.
⏳ 1. Beklemenin Anatomisi: Blocking vs Non-Blocking
Programlar, insanlara çok benzer. İş hayatında zamanlarının çoğunu aktif olarak çalışarak (CPU cycle harcayarak, kod çalıştırarak) değil, bir şeylerin olmasını veya birilerinin cevap vermesini bekleyerek geçirirler.
Bir web sunucusunun (nginx, apache, node.js) hayat döngüsüne bakalım:
- Diske “index.html dosyasını oku” der ve diskin dönüp veriyi getirmesini bekler (Milisaniyeler).
- Ağa “api.google.com’a bağlan” der ve karşı tarafın telefonu açmasını bekler (Hatta saniyeler).
- Veritabanına “SELECT * FROM users” der ve sonucun gelmesini bekler.
Linux çekirdeğinde (Kernel) bu bekleme eylemi, programın “Uyku Moduna” (Sleep State - S veya D) geçmesi demektir. Process durumunda R (Running) yerine S (Sleeping) görürsünüz. Bu kötü bir şey değildir; CPU’nun boşa dönmesini engeller. Enerji tasarrufu sağlar.
Klasik Yöntem: read() ve Bloklanma (Kahve Sırası)
En ilkel ve anlaşılır senaryoyu düşünelim. Tek bir istemciyle konuşan bir C programımız var.
Analoji: Küçük bir kahve dükkanı. Tek bir kasa var, kasada “Ahmet Amca” (Process) duruyor.
1
2
3
4
5
6
7
8
9
10
11
12
// Basit, bloklayan okuma
char buffer[1024];
// Ahmet Amca müşteriye soruyor: "Ne alırsın?"
ssize_t n = read(socket_fd, buffer, 1024);
// -- BURADA DÜNYA DURUR --
// Müşteri henüz karar vermemiş olabilir. Cüzdanını arıyor olabilir.
// Ahmet Amca, müşteri cevap verene kadar kasadan ayrılamaz.
// Arkadaki 50 kişi (diğer processler/threadler) beklemek zorundadır.
process_request(buffer);
Perde Arkasında Kernel Ne Yapar?
Kod read satırına geldiğinde ve sokette veri yoksa, Kernel şunları yapar:
- Context Switch: Programınızın “Çalışıyor” (
TASK_RUNNING) statüsünü alır,TASK_INTERRUPTIBLE(Kesilebilir Uyku) moduna çeker. - Schedule Out: Programı CPU’nun “Çalıştırılacaklar Listesi”nden (Run Queue) tamamen çıkarır.
- Wait Queue: Programın kaydını, o soketin “Bekleyenler Listesi”ne ekler.
Artık programınız CPU harcamaz. Ölü gibidir. Ta ki ağ kartından bir paket gelip, Kernel o soketi dürtüp (Interrupt), programı tekrar TASK_RUNNING yapana kadar.
📚 Teknik Terim: Context Switch (Bağlam Değişimi) CPU’nun o an çalıştırdığı programı durdurup, “Durumunu Kaydedip” (Register, Stack), başka bir programa geçmesi işlemidir. Çok maliyetlidir. CPU önbelleğini (Cache) soğutur. Çok fazla thread (örn: 10.000 tane) sürekli yer değiştirirse, CPU iş yapmayı bırakıp sadece “yer değiştirmekle” uğraşır. Buna “Thrashing” denir.
flowchart LR
subgraph Blocking["Blocking I/O (Ahmet Amca)"]
A1["Müşteri Geldi"] --> A2["Sipariş Ver (read() çağrıldı)"]
A2 --> A3["Kasada Bekleme Başladı"]
A3 -- "Müşteri Düşünüyor (I/O Wait)" --> A3
A3 --> A4["Sipariş Alındı (Data Ready)"]
A4 --> A5["İşlem Tamam / Sıradakine Geç"]
style A3 stroke:#f66,stroke-width:2px,stroke-dasharray: 5 5
end
Alternatif: “Busy Wait” (CPU Canavarı)
Peki ya beklemek istemezsek? Soketi O_NONBLOCK modunda açabiliriz. O zaman Ahmet Amca müşteriye “Ne alırsın?” der, müşteri susarsa Ahmet Amca hemen “Sıradaki!” diye bağırır.
1
2
3
4
5
6
7
8
9
10
11
12
// Non-blocking okuma
fcntl(socket_fd, F_SETFL, O_NONBLOCK);
while(true) {
ssize_t n = read(socket_fd, buffer, 1024);
if (n == -1 && errno == EAGAIN) {
// "Veri yok, sonra tekrar gel"
// CPU %100 olur çünkü sürekli soruyoruz!
continue;
}
break;
}
Bu sefer bloklanmazsınız ama sürekli “Veri var mı?” diye sormaktan (Polling) CPU’yu yakarsınız.
İşte “Blocking” (Uyku) ile “Non-Blocking” (Aşırı Hareket) arasındaki bu ikilem, bizi modern çözüme götürür: I/O Multiplexing.
🚦 2. Trafik Polisi Metaforu: select, poll ve epoll
10.000 masası olan dev bir restoran düşünün. Her masada bir müşteri (Socket) var. Garson (Program) tek başına. Müşteriler rastgele zamanlarda sipariş vermek (Veri göndermek) istiyor. Garson siparişleri en verimli şekilde nasıl toplayacak?
A. select() ve poll(): “Herkesi Tek Tek Kontrol Et”
Linux’un ilk zamanlarında (80’ler ve 90’larda) icat edilen yöntemlerdir. İkisi de benzer çalışır ama önemli bir farkları vardır:
select: En eskisidir. Sabit bir liste boyutu vardır (FD_SETSIZE, genellikle 1024). Yani 1025. müşteri gelirse kapıda kalır. Modern yüksek trafikli sistemlerde kullanılmaz.poll:select‘in 1024 limitini kaldırmak için yapılmıştır (Linked List kullanır). Ama temel verimsizlik sorunu (Lineer tarama) aynen devam eder.
Garson eline bir kağıt kalem (File Descriptor List) alır.
- Masaya gider: “Bir isteğin var mı?” Müşteri: “Yok.”
- Masaya gider. “Yok.”
- Masaya gider. “Yok.” … 10.000. Masaya gider. “Yok.”
Sonra tekrar koşarak 1. Masaya döner. Bu döngü sonsuza kadar sürer.
📚 Tarihçe: C10K Problemi 1999 yılında Dan Kegel tarafından ortaya atılan “Concurrent 10.000 Users” sorunu. O yıllarda donanımlar 10.000 kişiyi kaldırabilse bile, yazılımlar (Her kullanıcıya 1 Thread/Process) kaldıramıyordu. Çünkü 10.000 thread’in Context Switch maliyeti CPU’yu bitiriyordu.
epollvenginxbu sorunu çözmek için doğdu.
Teknik dilde select veya poll şudur: Program, Kernel’a 10.000 dosya numarasının listesini verir ve “Bunlardan hangisinde hareket var, bana söyle” der. Kernel, listeyi baştan sona (Lineer) tarar. Eğer 10.000. sokette veri varsa, Kernel 9.999 tanesini boşuna kontrol etmiştir. Bu işlem O(N) karmaşıklığındadır. Yani müşteri sayısı arttıkça, tarama süresi uzar, sistem hantallaşır. CPU, sadece “Kimse bir şey istiyor mu?” diye sormaktan yorulur (%100 System CPU kullanımı).
flowchart LR
subgraph Select["select() / poll() - O(N)"]
S1["Kernel"] -- "Tek Tek Sor" --> C1{"Masa 1?"}
C1 -- "Hayır" --> C2{"Masa 2?"}
C2 -- "Hayır" --> C3{"Masa ...?"}
C3 -- "Hayır" --> CN{"Masa 10.000?"}
CN -- "Evet!" --> S2["Veri Var!"]
style Select stroke:#ff9800,stroke-width:2px
end
B. epoll(): “Zili Çalanı Bana Söyle”
Modern yöntem (Linux 2.6 ile gelen devrim) şöyledir. Nginx, Node.js, Go, Redis gibi yüksek performanslı araçların gücü buradan gelir.
Garson mutfakta bir sandalye çeker ve oturur (Wait). Her masada bir elektronik buton vardır. Müşteri sipariş vermek istediğinde butona basar. Mutfaktaki ekrana “Masa 4532 Butona Bastı” bildirimi düşer. Garson yerinden kalkar, sadece Masa 4532’ye gider, siparişi alır ve tekrar yerine oturur.
Teknik dilde epoll (Event Poll) şudur:
epoll_create: Bir “Bildirim Listesi” oluşturulur.epoll_ctl: Kernel’a “Şu soketi bu listeye ekle, veri gelince haber ver” denir (Sadece bir kere yapılır).epoll_wait: Program uykuya yatar. Veri geldiğinde, Kernel doğrudan hangi soketin aktif olduğunu söyler. Listeyi taramaz. Bu işlem O(1) karmaşıklığındadır. İster 10 müşteri olsun, ister 1 milyon müşteri; Kernel’ın tepki süresi (neredeyse) değişmez.
flowchart LR
subgraph Epoll["epoll() - O(1)"]
E1["Masa 1"]
E2["Masa ..."]
E3["Masa 10.000 (Veri!)"] -- "Butona Bas (Interrupt)" --> EL["Hazır Liste (Ready List)"]
EL -- "Doğrudan Al" --> EK["Kernel"]
style Epoll stroke:#2196f3,stroke-width:2px
style EL stroke:#4caf50,stroke-width:2px
end
İşte bu yüzden top çıktısında %0 CPU gören ama strace ile baktığınızda sürekli epoll_wait içinde bekleyen bir Nginx, aslında gayet sağlıklıdır. İş yoktur, dinleniyordur.
C. io_uring: Oyunun Yeni Kuralı (ve Riskleri)
epoll harikadır ama kusursuz değildir. Hâlâ her işlem (read/write) için Kernel’a bir syscall (bağlam değişimi) yapmanız gerekir. Eski Linux AIO denemeleri ise sadece Asenkron Disk I/O desteklediği ve soketleri kapsamadığı için tutulmamıştı.
Modern Linux (5.1+), io_uring ile yüksek performansın yeni standardını belirledi:
- Shared Ring Buffers (SQ & CQ): Kernel ile User Space arasında paylaşılan iki halka (Queue) vardır. Veri kopyalaması (Zero Copy) yoktur.
- Zero Syscall (Polling Mode): Sistem çağrısı yapmaya bile gerek kalmaz. Kernel sürekli kuyruğu gözler.
⚠️ Güvenlik Notu
“Büyük güç, büyük sorumluluk getirir.” io_uring, saldırı yüzeyini genişlettiği için bazı güvenlik endişelerini de beraberinde getirmiştir.
- Google, 2022’deki Kernel açıklarının %60’ının
io_uringkaynaklı olduğunu belirterek Android ve ChromeOS’te bunu devre dışı bırakmıştır. - Docker, varsayılan güvenlik profilinde (seccomp)
io_uringçağrılarını engeller.
Hız gerektiren (Veritabanı, Proxy) sunucularda açıp, yüksek güvenlik gerektiren yerlerde dikkatli kullanmak gerekir.
🕵️♂️ 3. Vaka Analizi: “White Screen” (Beyaz Ekran) Sendromu
Bir e-ticaret sitesindesiniz. Sepeti onayladınız. “Ödeme Yap” butonuna bastınız. Ekran beyazladı. Tarayıcının sekmesindeki o yuvarlak simge dönüyor… dönüyor… Sunucu tarafında her şey sakin. CPU kullanımı düşük. Ama işlem bitmiyor. Müşteri 30 saniye sonra “Pes” edip sekmeyi kapatıyor.
Hadi strace masamıza geçelim ve backend sürecine (örneğin PHP-FPM, Python Gunicorn veya Java Tomcat) canlı bir röntgen çekelim.
1
2
3
# -T: Her syscall'ın ne kadar sürdüğünü göster (Time spent)
# -p: Çalışan sürecin PID'sine bağlan
strace -T -p 12345
Çıktıda şunun gibi satırlar akar:
1
2
3
4
5
# 1. Veritabanına sorgu atılıyor (Send)
sendto(5, "SELECT sleep(50) FROM users...", 30, 0, NULL, 0) = 30 <0.000050>
# 2. Cevap bekleniyor (Receive) - KRİTİK AN
recvfrom(5, 0x7fff..., 8192, 0, NULL, NULL) ...
İmleç tam bu recvfrom satırında durur. Terminal donar. 1 saniye… 3 saniye… 10 saniye… Siz ekrana bakarsınız, ekran size bakar. Sonunda tamamlanır:
1
recvfrom(5, "...", 8192, ...) = 150 <50.00231>
Satırın sonundaki, < > içindeki <50.00231> ifadesi, bu sistem çağrısının tam 50 saniye sürdüğünü gösterir. Program 50 saniye boyunca donmamış, çökmemiş, Bloke Olmuştur. Veritabanı (MySQL/PostgreSQL) o kadar yavaştır ki (belki “Slow Query”, belki kilitli bir tablo, belki Deadlock), cevap verememiştir. Siz Web Sunucusuna bakıp “CPU kullanmıyor, memory düşük, demek ki sorun yok” dersiniz. Oysa sunucu, veritabanının kapısında ağaç olmuştur.
💡 Hayat Kurtaran Ders: Düşük CPU kullanımı her zaman “performans sorunu yok” demek değildir. Çoğu zaman “Program çalışamıyor çünkü bir I/O bekliyor” demektir. Bunu anlamanın tek yolu
strace -Tile beklenen süreyi ölçmek veya APM (Application Performance Monitoring) araçlarıdır.
⏱️ 4. Gizemli Sayılar: 60 Saniye Efsanesi
Bazen program tam olarak 60. saniyede (veya 30’da) hata verir. Ne 59. saniye, ne 61. saniye. Tam 60.000 saniye. Doğada hiçbir süreç bu kadar “tam” ve simetrik olamaz. Bu, insan yapımı bir sınırdır: Timeout.
poll veya epoll_wait çağrılarını izlerken, son parametreye mikroskobunuzla bakın:
1
2
# poll(dosya_listesi, adet, milisaniye_cinsinden_timeout)
poll([{fd=4, events=POLLIN}], 1, 60000)
Veya:
1
epoll_wait(6, [], 1024, 30000)
Buradaki 60000 (60 saniye) veya 30000 (30 saniye), programcının (veya Nginx proxy_read_timeout, PHP max_execution_time configlerinin) Kernel’a verdiği kesin bir emirdir: “En fazla 60 saniye bekle. Eğer o zamana kadar cevap gelmezse, beni uyandır. Daha fazla beklemeyeceğim, ‘Gateway Timeout’ hatasını basıp gideceğim.”
Eğer loglarda “504 Gateway Timeout” görüyorsanız ve strace çıktısında poll çağrısı tam olarak verilen süre sonunda 0 (Timeout) dönüyorsa, sorunu kodun içinde değil, karşı tarafta (Upstream Server, Database, 3rd Party API) arayın. Sizin programınız masumdur; sadece çok sabırlıdır ve sabrı taşmıştır.
🕵️♂️ Gizli Timeout: SO_RCVTIMEO Bazen
pollveyaepollparametresinde timeout sonsuzdur veyaNULL‘dır. Ama program yine de belli bir sürede kesilir (Timeout yer). Suçlusetsockoptile ayarlanmış Soket Zaman Aşımı olabilir.straceçıktısında daha öncesetsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, ...)yapıldığını görebilirsiniz. Bu durumdaread/recvçağrısı, Kernel tarafından belirtilen süre sonunda zorla durdurulur veEAGAINhatası döndürülür.
🧵 5. Futex: Kilitlenip Kalmak (Deadlock)
Bazen I/O beklemesi yoktur. Ağ trafiği yoktur. Karşı sunucu çok hızlıdır. Ama program yine de ilerlemez. strace çıktısı tek bir satıra sıkışır ve asla değişmez. Akan bir şey yoktur.
1
futex(0x7f8a3c..., FUTEX_WAIT_PRIVATE, 0, NULL)
futex (Fast Userspace Mutex): Linux’ta “Kilit” (Lock) mekanizmasının taşıyıcı kolonudur. Multithread programlarda (Java, Go, C++, Rust), iki thread aynı veriye (değişkene) erişmeye çalıştığında biri kilidi alır, diğeri usulca sırasını bekler.
Eğer strace ekranında saatlerce bu futex satırını görüyorsanız ve yanında <unfinished ...> yazıyorsa, geçmiş olsun: Bir Deadlock (Ölümcül Kilitlenme) yakaladınız.
Senaryo:
- Thread A: Elinde “Kalem” var, yazı yazmak için “Kağıt” bekliyor.
- Thread B: Elinde “Kağıt” var, yazı yazmak için “Kalem” bekliyor.
- İkisi de elindekini bırakmıyor. İkisi de sonsuza kadar birbirini bekliyor.
- Kernel buna müdahale etmez. CPU kullanımı %0’dır. Ama program aslında bitkisel hayattadır.
Çözüm: Bu noktada strace yetersiz kalır (sadece beklediğini söyler). pstack <PID> veya gdb kullanarak hangi fonksiyonun kilidi tuttuğunu (“Stack Trace”) görmeniz gerekir.
⚙️ 6. Case Study: “Nginx Neden Yanıt Vermiyor?”
Gerçek bir vaka. Nginx sunucunuz yanıt vermeyi kesti. Restart attınız düzeldi. Ama neden oldu? O anın strace kaydına bakalım:
1
2
3
4
epoll_wait(6, [], 1024, 1000) = 0
epoll_wait(6, [], 1024, 1000) = 0
write(3, "Log verisi...", 50) = -1 EAGAIN (Resource temporarily unavailable)
epoll_wait(6, [], 1024, 1000) = 0
Nginx sürekli epoll_wait çağırıyor ve 0 dönüyor (Timeout). Yani kimse ona istek göndermiyor mu? Hayır. Biraz daha dikkatli baktığımızda arada başarısız bir write görüyoruz veya disk I/O’sunda takılan bir write görüyoruz.
Nginx asenkron (Non-blocking) bir sunucudur, ağ işlemlerinde asla bloklanmaz. AMA Linux’ta Disk I/O (Standart Buffered I/O) hala bloklayıcı olabilir. Eğer diskiniz ölüyse, IOPS limitine vurduysanız veya NFS mount’unuz koptuysa; Nginx o minicik access.log satırını diske yazmak için write() çağrısında takılır kalır. Ana süreç (Master/Worker) diskte takıldığı için, yeni gelen ağ isteklerini (accept) karşılayamaz.
Olay anında iostat -xz 1 ile diske bakmak, %100 %util görmek hayat kurtarır. Sorun Nginx’te değil, disktedir.
💀 Kernel Kabusu: D State (Uninterruptible Sleep) Bu sırada
topaçarsanız Nginx sürecinin durumunuDolarak görürsünüz. En korkuncu şudur: Bu sürecikill -9ile bile öldüremezsiniz. Neden? Çünkü Kernel der ki: “Şu an donanımla konuşuyorum (Diske yazıyorum). Eğer beni yarıda kesersen dosya sistemi bozulabilir. İşlem bitene kadar (veya disk yanıt verene kadar) hiçbir sinyali (SIGKILL dahil) kabul etmeyeceğim.” Çözüm? Disk düzelmezse, bazen tek çare sunucuyu reboot etmektir.
Çözüm? Disk düzelmezse, bazen tek çare sunucuyu reboot etmektir.
🎲 7. Görünmez Duvar: Entropy (Rastgelelik) Kıtlığı
Bazen ne ağda sorun vardır, ne diskte, ne de bir deadlock durumu söz konusudur. Program sadece “zar atmak” istiyordur ama evren ona zar vermiyordur.
Bilgisayarlar deterministik makinelerdir; yani kendi başlarına “rastgele” sayı üretemezler. Kriptografik işlemler (SSL anahtarları, session ID’ler) için sistemin dış dünyadan (klavye vuruşları, mouse hareketleri, disk gürültüsü) topladığı “gürültüye” (Entropy) ihtiyacı vardır.
Eğer başsız (Headless) bir sunucudaysanız (mouse/klavye yok) ve diskiniz SSD ise (mekanik gürültü yok), Linux çekirdeğinin “Entropy Havuzu” kuruyabilir.
Programınız /dev/random cihazından okuma yapmaya çalışırsa ve havuz boşsa, Kernel der ki: “Yeterli kaos toplanana kadar bekle.”
Vaka: Bir Java uygulaması (Tomcat/Jetty) ayağa kalkarken dakikalarca bekliyor. Strace Çıktısı:
1
2
3
# Dosya tanımlayıcısı 3, /dev/random'ı işaret ediyor
read(3, 0x7ffd..., 64) ...
# İmleç burada sonsuza kadar yanıp söner.
Bu satırın sonunda <unfinished ...> görürsünüz. Bu bir disk I/O beklemesi değildir. Bu, Kernel’ın “Yeterince rastgelelik toplayamadım, bekle” demesidir.
� Çözüm: Modern Linux çekirdeklerinde (ve çoğu uygulamada) bloklayan
/dev/randomyerine, bloklamayan (pseudo-random)/dev/urandomkullanımı standart hale gelmiştir. Ancak eski legacy sistemlerde veya paranoyak güvenlik ayarlarında bu sorunla karşılaşırsanız,havegedveyarng-toolsgibi “suni entropy” üreten servisleri kurarak havuzu doldurmanız gerekir.
🛑 8. Tıkanıklık: Pipe ve Buffer Doluluğu (Backpressure)
Şu ana kadar hep “Veri gelmesini bekleyenleri” konuştuk. Peki ya veri göndermeye çalışıp da gönderemeyenler?
Linux’ta bir pipe (|) veya socket üzerinden veri aktarırken, Kernel’ın arada bir tampon bölgesi (Buffer) vardır (Genellikle 64KB - 4MB arası). Senaryo şu: Hızlı bir üretici (örneğin log üreten bir app) ve yavaş bir tüketici (logları diske yazan hantal bir script).
Tüketici yavaş kaldığında, Kernel’daki buffer ağzına kadar dolar. O andan itibaren üretici program write() çağrısı yaptığında, Kernel der ki: “Dur! Karşı taraf henüz yediklerini sindiremedi. Yer açılana kadar bekle.”
Strace ile Teşhis: Program CPU kullanmıyor ama ilerlemiyor da. strace ile baktığınızda bu sefer read değil, write bloklanmıştır:
1
2
# fd 1 (stdout) dolu bir pipe'a yazmaya çalışıyor
write(1, "Log satiri...", 4096) ...
İmleç burada donar.
Bu, sistemin sağlıklı çalıştığını (veri kaybı olmadığını) gösterir ama performansın en yavaş halka (Weakest Link) kadar düştüğünün kanıtıdır. Buna “Backpressure” (Geri Basınç) denir. Çözüm kodu optimize etmek değil, tüketicinin hızını artırmaktır.
🧠 9. RAM Yalan Söyler: Page Fault ve Swap Cehennemi
Geldik listenin sonuna ve en sinsi maddesine. Programınız CPU kullanmıyor, Ağ beklemiyor, Disk I/O yapmıyor, Kilit (Lock) yok, Entropy tam… Ama program hala takılıyor. Kesik kesik ilerliyor.
Buradaki suçlu, modern işletim sistemlerinin en büyük yalanıdır: Virtual Memory (Sanal Bellek).
Siz kodunuzda malloc(1GB) dediğinizde, Linux size “Tamam, al sana 1GB” der ama aslında size fiziksel RAM vermez. Size sadece bir “söz” (Virtual Address) verir. Programınız o belleğe erişmeye çalıştığı an (ilk veri yazma anı), işlemci durur ve Kernel’a bağırır: “Hey! Bu adreste fiziksel bellek yok!” (Page Fault).
İki senaryo yaşanır:
- Minor Fault: Kernel boşta duran RAM’den hemen bir sayfa ayırır. (Hızlıdır, hissetmezsiniz).
- Major Fault (Swap Thrashing): Fiziksel RAM doludur. Kernel, yer açmak için başka bir programın hafızasını diske (Swap alanı) yazar, sonra sizin istediğiniz veriyi diskten okur.
Strace ile Teşhis: Normal bir strace çıktısında bunu doğrudan bir “syscall” olarak göremezsiniz. Çünkü Page Fault, bir syscall değildir; bir CPU istisnasıdır (Exception). Ancak strace -T çıktısındaki zaman boşluklarından yakalarsınız.
Basit bir memcpy veya bellek erişimi, milisaniyeler sürmesi gerekirken saniyeler sürüyorsa:
1
2
3
4
# İki satır arasında geçen süreye dikkat edin
gettimeofday(...) = 0 <0.00001>
# ... Burada hiçbir syscall yok ama 2 saniye geçiyor ...
write(1, "Veri...", 7) = 7 <0.00002>
Bu “sessiz boşluklar”, programınızın o sırada diskten RAM’e veri yüklenmesini (Swap-in) beklediğini gösterir.
💡 Kesin Kanıt:
straceyerinepsveyatopkomutundaMINFLTveMAJFLTsütunlarına bakın. EğerMAJFLT(Major Fault) sayısı artıyorsa, programınız RAM’de değil, Disk (Swap) üzerinde çalışmaya çalışıyordur. Bu, performansın ölümüdür.
🛠️ Blocking I/O Teşhis Matrisi
Sorunun kaynağını (CPU, Disk, RAM veya Network) hızlıca anlamak için bu tabloyu kullanabilirsiniz:
| Belirti (Semptom) | Olası Neden | Kontrol Aracı/Komutu |
|---|---|---|
| CPU %0, Uygulama Donuk | Blocking I/O (Disk/Net) | strace -p <PID> |
| CPU %100 | Busy Wait / Sonsuz Döngü | top, perf |
Sürekli futex() Dönüyor | Deadlock / Kilitlenme | pstack, gdb |
Sürekli epoll_wait() | İş yok (Normal) veya Timeout | strace -T |
read (/dev/random) | Entropy Kıtlığı (Randomness) | cat /proc/sys/kernel/random/entropy_avail |
write (Pipe/Socket) | Backpressure / Buffer Dolu | strace (write blocked) |
| Zaman Boşluğu (Gap) | Major Page Fault (Swap) | pidstat -r -p <PID> |
📋 Debugging Kontrol Listesi
Programınız “Donmuş” (Idle/Blocked) görünüyorsa, şu adımları izleyin:
- Zamanı Ölç (strace -T):
strace -T -p <PID>Hangi satırın sonunda< >içindeki süre uzun?readmi,connectmi,pollmu?read/recv: Veri gelmiyor. (Karşı taraf yavaş)connect: Bağlantı kurulamıyor. (Network/Firewall)write/send: Buffer dolu veya disk yavaş.read (/dev/random): Entropy havuzu boş.
- Özet Tablo Çıkar (strace -c):
strace -c -p <PID>Program zamanını en çok nerede harcıyor?futex: %90 ise -> Kilitlenme (Deadlock/Race Condition).epoll_wait: %90 ise -> İş yok, boşta bekliyor (Normal).read: %90 ise -> I/O darboğazı.
Process Stack Analizi (pstack): Eğer
futextepedeyse,pstack <PID>(veya gdb) ile Thread’lerin kodun hangi satırında (hangi fonksiyonda) kilitlendiğine bakın.- Network vs Disk Ayrımı: Soket (
recv) bekliyorsa sorun Ağ/DB/API tarafındadır. Dosya (write/fsync) bekliyorsa sorun Disk/Storage tarafındadır.
🔮 Bir Sonraki Adım: “Kayıp Aranıyor”
Disk beklemeyi çözdünüz. select ile binlerce bağlantıyı yönettiniz. Ama sabah geldiniz, süreç (process) yok. “Killed” yazıyor. Hata yok. “Core Dump” yok.
Sessizce ölen süreçlerin katili kim?
Bir sonraki yazıda, Linux Bellek Yönetimi, OOM Killer ve Sinyaller dünyasına giriyoruz. Kernel’ın “Seri Katili”nin kimi neye göre seçtiğini, kill -9 kullanmanın bedelini ve “Zombi” süreçleri inceliyoruz.
🚀 Serinin Devam Yazısı: Linux Bellek Yönetimi ve OOM Killer ile Yüzleşmek: Sinyaller, RAM ve İnfazlar
