İçeriğe geç
edumintsBlog
Backend3 dk okuma

malloc Aslında Ne Yapıyor?

Kaynak: Hacker News

Paylaş
BackendEdumints Blog

C programlama dilinde bellek yönetimi denildiğinde akla gelen ilk fonksiyon olan malloc, son derece yalın ve kullanışlı bir arayüze sahiptir: Belirli miktarda bellek talep edersiniz ve size sanki yoktan var edilmiş gibi ardışık baytlardan oluşan bloğunuza işaret eden bir void işaretçisi (void pointer) verilir. İşiniz bittiğinde aynı işaretçi ile free fonksiyonunu çağırırsınız ve bellek adeta boşluğa karışarak kaybolur. Peki arka planda gerçekte ne oluyor?

Bir kullanıcı alanı (userspace) programı olarak çalıştığımız için, malloc fonksiyonunun sistem çağrıları (syscalls) aracılığıyla işletim sistemi çekirdeğinden (kernel) bellek istemesi gerekir. Ancak programlarımızda çok sayıda küçük bellek bloğu tahsis ediyor olabiliriz ve sistem çağrıları oldukça maliyetlidir (boş bir sistem çağrısı bile yaklaşık 575 ns sürer). Bu nedenle arka planda ciddi bir kayıt tutma ve bellek organizasyonu (bookkeeping) yapılır.

GNU libc (glibc) üzerinde malloc(16) çağrısını bin kez yapan aşağıdaki kod parçacığını inceleyelim:

static void *ptrs[1000];
int main(void) {
    for (int i = 0; i < 1000; i++)
        ptrs[i] = malloc(16);
}

strace aracını kullanarak bu programı izlediğimizde, tüm döngünün yalnızca iki adet brk sistem çağrısı yaptığını görürüz:

  • İlk brk(NULL) çağrısı, süreç yığınının (heap) bittiği adresi yani program sınırının (program break) nerede olduğunu sorar.
  • İkinci brk çağrısı ise yığını (heap) tek seferde 132 KiB büyütür.
  • Bu iki işlem yalnızca ilk malloc çağrısında gerçekleşir; kalan 999 çağrı ise doğrudan bu önceden tahsis edilmiş hazır bellek havuzundan pay alır.

Tartışmalarda hem brk hem de sbrk ifadeleriyle karşılaşabilirsiniz. brk, program sınırını kendisine verilen adrese ayarlayan doğrudan bir sistem çağrısıdır; sbrk ise bir değişim miktarı (delta) alarak program sınırını bu miktar kadar artıran kullanışlı bir libc sarmalayıcısıdır (wrapper). Yazılım geliştiriciler için buradaki temel pratik çıkarım şudur: Standart bellek ayırıcılar, çekirdek geçiş maliyetini minimize etmek için belleği işletim sisteminden büyük parçalar halinde alır ve kullanıcı alanında kendileri yönetir.

Adres Alanı (The Address Space)#

Çalışan her süreç (process), çeşitli bölgelere ayrılmış kendine ait bir adres alanına (address space) sahip olur. Bu bölgeler düşük adreslerden yüksek adreslere doğru sırasıyla şunları içerir:

  • text: Çalıştırılabilir makine kodu.
  • data: İlk değer atanmış (initialised) global değişkenler.
  • bss: İlk değer atanmamış (uninitialised) global değişkenler.
  • heap: malloc(16) gibi dinamik bellek tahsislerinin yapıldığı ve program break (sbrk(0)) ile sınırlandırılan bölge.
  • libc ve paylaşımlı kütüphaneler: Örneğin printf fonksiyonunun bulunduğu bellek alanı.
  • stack: Yerel (local) değişkenlerin tutulduğu yığın bölgesi.

Program çalıştıkça bu bölgelerin bazıları dinamik olarak değişebilir. Örneğin malloc(16) çağrısı heap bölgesini büyütür ve program break sınırını yukarı taşır. Örnek bir çalıştırmada program break (0x59c090338000) ile libc içindeki printf (0x711dcf7c15b0) adresi arasında 23.4 TiB fark bulunur; bu değer, makinenin sahip olduğu 15.6 GiB fiziksel RAM miktarının yaklaşık 1.530 katıdır. Sanal bellek mimarisi sayesinde bu devasa aralığın neredeyse tamamı eşlenmemiştir (unmapped), dolayısıyla fiziksel RAM üzerinde hiçbir maliyet oluşturmaz.

Kaynak ve atıf

Bu yazı, Hacker News üzerinde yayımlanan içerikten yararlanılarak Edumints tarafından yapay zekâ desteğiyle Türkçe hazırlandı. Ayrıntılar ve yazarların kendi anlatımı için orijinaline bakabilirsin:

  • benmandrew.com/articles/what-is-malloc-doing-anyway