4 Nisan 2012 Çarşamba

Virtual Memory nedir? - II - segfault

Bir önceki yazıda uygulamaların gerçek memory adreslerinden haberdar olmadığını, her bir uygulamanın sürekli ve adresleyebileceği en büyük memory alanında çalıştığını sandığını söylemiştik. Şimdi memory managera inanan uygulamaların nasıl hatalar yapabileceğine bakalım.

Başlamadan önce bir bilgilendirme; yazıda bol bol stack ve heap kelimelerini kullandım. Executableların bölümlerini anlatan ayrı bir yazı yazacağım ama basit olarak stacki fonksiyonların içinde doğrudan tanımladığınız değişkenlerin tutulduğu, heapi de malloc ile dinamik olarak bellek isteyip kullandığınız alanlar olarak düşünebilirsiniz. Stackin boyutu sınırlı olduğundan alloc() tipi komutları hiç kullanmadan büyük işler yapmaya kalkarsanız segfault ile karşılaşmanız kaçınılmazdır.

Segfault 1 


C'yi yeni öğrenirken sık yapılan hatalardandır, &'ler *'lar karışır, unutulur.

Pointer oluştururken değişkenin adının önüne & koymayı unuttuğumuz şöyle bir kod düşünelim:
 int x=5;
 int *p=x;  //int *p = &x; olacakti
 
 printf("pointerin tuttugu adres %x \n", p);
 printf("pointerin adresindeki deger %d \n");


Bu durumda zaten kodu derlerken compiler bir hata ya da uyarı verir. 

Eğer programı derleyebildiysek programı çalıştırdığımızda hata ile karşılaşırız. Neden? Çünkü x'in adresine değil, 0x5 adresine gidip bir değere ulaşmaya çalıştık. İkinci printf'i kaldırın; sorun yok.

Hata taa CPU üzerindeki MMU'dan geliyor ve işletim sisteminin uygulamayı durdurmasına yol açıyor. Process 5 adresine ulaşmak istediğinde memory manager bu processin kullandığı sayfalar içinde 0x5 adresinin bulunduğu sayfanın gerçek adresine öğrenmeye çalışıyor, MMU ise bu processe 0x5 adresini içeren bir sayfa vermediğini söyleyerek exception gönderiyor.

MSVC 2010 yukarıdaki kodu derlemeye izin vermediği için daha genel bir örnek verelim.



Segfault 2


Pagelerin boyutu genelde 4KB yani 4096 byte demiştik. Bu durumda stackteki bir değişkenden belli bir mesafe uzaklaştığımızda stackin tutulduğu sayfanın dışına çıkacağız.
 int x = 1234;
 int *pointer = &x;
 int pagesize = 4096;
 
 printf("pointerin tuttugu adres (stackte) %x \n", pointer);
 int addr_offset = 0;
 int i = 0;
 for (i=0;i<10;i++) {
  while (addr_offset < pagesize*i) {
   pointer += 1; //int pointer oldugu icin 4 byte
   addr_offset += 4;
   printf("pointerin tuttugu adresin %d byte otesi %x \n", addr_offset, pointer);
   printf("pointer adresindeki deger %d \n", *pointer);
  } 
 }

(Tabii stack bir kaç sayfa uzunluğunda fazladan alana sahip olabilir. Executablelar güvenlik ve exception handling için main() işlevi çalışmadan önce tonla iş yapar, hem executable'ın içine gömülmüş hem de muhtelif dış kütüphanelerdeki kodları çalıştırır. Bu yüzden 3 satırlık kodun derlenmiş hali bir kaç kilobyte boyutunda olur. Bizim kodumuz da çalışmadan önce işletim sistemi tarafından fazladan bir iki sayfa processe stackte kullanmak üzere tahsis edilmiş olabilir. Özetle hatayı almadan önce 2-3 sayfa doldurursanız şaşırmayın.)

C'de erişim kontrolü olmadığını, 10 karakterlik dizi oluşturup 11. ya da 111. karakteri print ettirebileceğimizi biliyoruz. Peki bu kod neden çakılıyor o zaman? Sebebi bir yerden sonra stack için ayrılan son sayfanın dışına çıkmamız. Normalde programın kullandığı memory miktarı arttıkça virtual memory manager yeni fiziksel sayfaları programın sanal adres boşluğu ile ilişkilendirir. Biz henüz memory managerın arkaplanda gerçek bir sayfa bağlamadığı bir adrese erişmeye çalıştığımızdan MMU "uygulama adres alanındaki bu adrese karşılık gelen gerçek bir sayfa yok" diyerek işletim sistemine exception gönderiyor.

Segfault 3


Benzer bir örneği stackte değil de heapte malloc işlevi ile yapabiliriz.
 void *dynmem = malloc(sizeof(int));
 int *pointer = (int*) dynmem;
 *pointer = 5;
 
 int pagesize = 4096;
 
 printf("pointerin tuttugu adres (heapte) %x \n", pointer);
 int addr_offset = 0;
 int i = 0;
 for (i=0;i<10;i++) {
  while (addr_offset < pagesize*i) {
   pointer += 1; //int pointer oldugu icin 4 byte
   addr_offset += 4;
   printf("pointerin tuttugu adresin %d byte otesi %x \n", addr_offset, pointer);
   printf("pointer adresindeki deger %d \n", *pointer);
  } 
 }

malloc işlevini çalıştırdığımızda yalnızca 4 byte memory istemiş gibi görünsek de processin heap için ayrılmış adres boşluğuna 4 kb boyutunda bir sayfa atanır. (Yine kod çalışmadan önce de atanmış ve kullanılmış olabilir.) Biz de elde ettiğimiz 4 bytelık boşluğun en fazla 4kb ötesine kadar erişebiliriz.

Bunun ötesine erişmeye çalıştığımızda yine MMU ve işletim sistemi yanlışlığı farkeder ve erişmeye çalıştığımız adres için fiziksel memoryde bir eşleştirme yapılmadığını söyleyerek (eğer bu hataya özel exception handler yazmadıysak) programı sonlandırır.

Bu arada Linux'ta yukarıdaki kodun başına
 mallopt(M_TOP_PAD,0);
yazmazsanız malloc kullanıldığında birden fazla sayfa atandığını göreceksiniz. Bunun sebebi sık sık malloc kullanılan programlarda sürekli system call yapmamak için bir kaç boş sayfanın her zaman uygulamanın kullanımına hazır bekletilmesidir.

Aklıma gelmişken söyleyeyim 0x0 adresi de process içi adres boşluğunda kullanılmamaktadır, p = 0 ya da p = NULL diyip *p'ye erişmeye çalışırsanız aynı hatayı alırsınız.

Yaktın beni Blogger. Yazı tek posta sığmadığından ikiye bölmek zorunda kaldım. Devamı...

Hiç yorum yok:

Yorum Gönder