Linux proseslerin hafıza ihtiyacını nasıl karşılar?

Bu flood’da Stack ve Heap üzerinden Linux'un proseslerin hafıza ihtiyacını nasıl karşıladığına yüzeysel olarak değineceğiz.

Stack prosesin, çalıştırdığı kodun lokal değişkenleri, çağrılan fonksiyonların adresleri ve fonksiyonlarına geçirilen parametreleri tutabilmesi için işletim sistemi tarafından sağlanan hafıza alanıdır.

Aşağıdaki şekilde görüldüğü gibi yeni bir fonksiyon çağrıldığında öncelikle çağrılacak fonksiyona geçirilen parametreler Stack'e eklenir. Sonra mevcut fonksiyona geri dönebilmek üzere çağıran fonksiyonun akışta kaldığı adres Stack'e eklenerek akış yeni fonksiyona bırakılır. Yeni fonksiyonun kullanacağı lokal değişkenler de Stack'e ilave edilir. Fonksiyon tamamlandığında ise yerel değişkenleri ile birlikte parametreleri Stack'ten çıkartılıp varsa fonksiyonun geri dönüş değeri Stack'e konarak akış önceki fonksiyonda son kalınan yere verilir.

Stack yönetimi için programcının ekstra bir işlem yapmasına gerek yoktur. Stack'teki lokal değişkenler, parametreler ve geri dönüş değerlerinin hafıza yönetimi (oluşturulma ve iade edilme) otomatik olarak derleme ve -işletim sistemince- çalıştırma zamanında yapılmaktadır.

Linux programları başlatırken her bir Thread için bir miktar (8MB) Stack alanı ayırmaktadır. Prosesin çalışma zamanında ihtiyaç duyduğu Stack daha fazla olursa proses hepimizin aşina olduğu Stack Overflow adı verilen hataya sebep olur ve SIGSEGV sinyali ile sonlanır.

Prosesin SIGSEGV sinyalini alma sebebi Stack'e ayrılan alandan taşması ve kendisine ait olmayan bir alana yazmaya çalışırken işletim sistemince durdurulmasıdır. Aşağıda özyinelemeli olarak çağrılan fonksiyon dolayısıyla oluşan bir Stack Overflow örneği gösterilmiştir.

Proses tarafından kullanılacak hafıza alanı işletim sistemi tarafından çalışma zamanında belirlenir. Stack boyutundan (8MB) daha büyük bir yerel değişken tanımlandığında -yani aslında Stack'in taşacağı belli de olsa- default Stack limiti kullanılır ve Stack taşar.

Stack boyutu ulimit -s <size> komutu ile artırılabilir. Yukarıda verilen senaryoda yüksek Stack ihtiyacı olan program Stack limiti artırılarak çalıştırılabilir.

Heap, prosesin dinamik olarak ihtiyaç duyduğu hafıza alanlarına verilen isimdir. Stack prosesin hafıza alanı içerisindeyken Heap bütün prosesler tarafından ortak kullanılan hafıza havuzundadır. Birçok dilde new anahtar kelimesi ile üretilen objeler Heap'e konulur. Garbage Collection olan dillerle yazılan programlarda oluşturulan objeler, işleri bitince GC tarafından otomatik olarak serbest bırakılarak Heap'ten silinirler. GC olmayan dillerde ise Heap'ten alınan hafıza alanını iade etmek programcının görevidir.

Stack'teki objeler otomatik olarak temizlenirler. Heap'te oluşturulan objelerin Heap'e manuel olarak iade edilmesi gerektiği için iade etmenin unutulduğu durumlarda Memory Leak problemi ortaya çıkar. Neyse ki bu problemlerin kaynağını takip etmek için birçok araç vardır.

Anlaşılacağı üzere ne kadar hafıza alanına ihtiyacımız olduğunu ancak çalışma zamanında bilebildiğimiz verileri tutmak için Heap kullanma zorunluluğumuz vardır.

Şimdi Linux’ta Heap'in nasıl çalıştığını anlamak için 128 MB'lık bloklar halinde Heap'ten hafıza almaya çalışan aşağıdaki örnek programı inceleyelim.

free komutu ile sistemin hafıza özetini alıp programı çalıştırdığımızda programın sistemdeki bütün hafızayı (1920 MB) bitirdiğini sonra da Killed mesajı ile öldürüldüğünü görmekteyiz. Programı strace ile çalıştırarak prosesin nasıl öldüğünü (SIGKILL) görebilirsiniz.

Linux, Memory Overcommit opsiyonu aktive edildiğinde proseslerin aslında sahip oldukları bütün hafıza alanlarını yüzde yüz kullanmayacakları tahminiyle proseslere aslında tahsis edebileceğinden daha fazla hafıza alanını tahsis eder.

En kötü senaryoda, yani prosesler tahminden fazla hafıza alanını kullandıklarında (OOM - Out of Memory durumunda) OOM Killer bir prosesi, aslında (/proc/<proc_id>/oom_score) değeri en yüksek prosesi, seçerek o prosesi sonlandırır ve sisteme hafıza alanı kazandırır.

Son olarak yukarıdaki nahoş durumun kabul edilebilir olmadığı misyon-kritik sistemlerde Memory Overcommitting aşağıdaki örnekteki gibi devre dışı bırakılarak OOM Killer'ın prosesleri öldürmesi engellenebilir.