17 Ağustos 2017 Perşembe

Segmentation Fault

Not : Bu yazı da Address Space Layout Randamization konusuna değinmedim, ileride değinmeyi düşünüyorum.

Segmentation Fault Neden Oluşur
Segmentation Fault oluşmasının en önemli iki sebebi şunlardır.
  1. İmtiyaz seviyemizin dışında olan bir makine talimatını çalıştırmaya çalışmak
  2. Olmayan bir veriye erişmeye teşebbüs ederek Page Fault ve dolayısıyla Segmentation Fault almak
Bu yazının ağırlık noktası 2. madde olacak. Paging işletim sistemlerinde başlı başına bir konu. o yüzden yazının bir çok noktaya değinmesi kaçınılmaz oldu.

İmtiyazlı Talimatı Çalıştırmaya Çalışmak

page fault and segmentation fault sorusu konuyu çok güzel açıklıyor ve bir örnek te veriyor. Ayrıca yazının devamında CPU ring seviyelerini de okuyabilirsiniz.

Page Fault

Kernel Segmentation Fault'u nereden biliyor ?

Protected mode'da çalışan bir CPU fiziksel RAM'e erişmek için iki kademeli bir çevirim işlemine tabi tutulur. İlk kademeye Segmentation, ikincisine ise Paging denilir.

Paging, Segmentation kavramına göre daha kolaydır. Bu yüzden yazıda ilk olarak bu kavramı anlatılacak. Daha sonra da Segmentation kavramına değinilecek.


1. Paging
Memory Management Unit Nedir - MMU yazısına taşıdım.

Translation Lookaside Buffer (TLB)

TLB yukarıda bahsedilen page table veri yapısına erişimi hızlandırmak için kullanılan bir önbellek. Operating System - paging cevabına göz atmak faydalı olabilir.

TLB ile dönüşüm için kullanılan bir hafıza parçası. MIPs ve x86 üzerinde farklı şekillerde çalışabilir. Örneğin How do x86 page tables work? sorusunda TBL'nin MIPS mimarisi üzerinde yazılımla yönetilirken, x86 üzerinden donanıma bağlı olduğu açıklanmış.

En Basit Dönüşüm

Bu hesaplamayı temsil eden bir resimi buradan aldım ve aşağıya ekledim. Sanal adres iki parçaya bölünür ve ilk parça bir indeks gibi kullanılır. İkinci parça ise offset (sayfa eklemesi) olarak kullanılır.

İşlemi gösteren ve mutlaka okunması gereken bir diğer şekli ise Ulrich Drepper'in "What Every Programmer Should Know About Memory" isimli belgesinden aldım.


Bulunan sayfa numarası sayfa tablosunda aranır. Eğer sayfa hafızada mevcut ise memory bus kullanılarak erişilir. Sayfa disk üzerinde ise önce ana hafızaya yüklenir ve daha sonra erişilir. The Basics of Page Faults başlıklı yazıda anlatıldığına göre iki çeşit page fault var. Hard page fault ve Soft page fault. Hard page fault aranılan sayfanın page file'tan yüklenmesi demek. Soft page fault ise zaten RAM üzerinde bulunan bir sayfanın bizim uygulamamız tarafından da kullanılabilecek hale getirilmesi demek. Soft page fault ile örneğin yüklü bulunan bir DLL dosyasının bir kısmı bizim uygulamamız tarafından da kullanılabilir.

Bazen yeni bir sayfanın yüklenmesi için mevcut bir sayfanın diske yazılması gerekir. Bu durumda hangi sayfanın çıkarılacağını seçmek için bir algoritma kullanmalıdır. Page replacement algorithm başlıklı yazıda kullanlabilecek algoritmalarla ilgili bilgi mevcut.

Not : major page fault handler in Linux kernel sorusunda Linux çekirdeğindeki Hard page fault handler'ın nerede bulunduğuna dair bir açıklama var.



Şekil - Hard Page Fault Örneği
Linux  üzerinde bulunan /usr/bin/time komutu ile bir uygulamanın kaç tane hard/soft page fault'a maruz kaldığını görmek mümkün.



MMU tarafından verilen fiziksel adrese erişim için memory bus kullanılır.


MMU çalışan bir program tarafından istenen sanal bir adresi fiziksel adrese çeviremez ise işletim sistemini "Page Fault Exception" ile haberdar eder. İşletim sistem Page Fault Exception gelince buradan aldığım aşağıdaki akışı takip eder.

Erişilmeye çalışılan adresin uygulama adresleri içinde bulunmamasını gösteren bir şekil de aşağıda bulabilirsiniz.





İşletim sistemi bu hatayı yakalar ve o program için segmentation fault oluşturur. Bu hata genellikle core dump dosyasının oluşması ile sonlanır. Yani Invalid Page Fault oluşur.



Not : Windows'ta virtual memory istemek için VirtualAlloc metodu kullanılır.


2. Segmentation


Hafıza bir çok sayfadan oluşsa bile düz bir tabloymuş gibi de düşünülebilir. Segmentation ile ilgili olarak C Programlarının Hafıza Yerleşimi başlıklı yazıya göz atabilirsiniz.

CPU açısından logical adreslerde yukarıdaki şekilde bölündüğü için MMU'ya verilen logical adresleri hesaplamak için segmentation denilen işlem yapılır. Aşağıda buradan aldığım ve her hafıza bölümünün başladığı logical adresi hesaplayabilmemizi sağlayan segmentation işleminin anlatıldığı şekil var.



Bu bölümlerden bazılarının büyüklüğü işletim sistemi ayarları ile atanabilir.

Örneğin stack büyüklüğünün nasıl ayarlanacağını görmek için Kernel ve Stack başlıklı yazıya göz atabilirsiniz.

Bu arada memory mapping segment linux üzerinde Dynamic Linker ile yapılır. Bu durum gösteren bir şekli aşağıya buradan ekledim.

Dynamic Linker'ın ayarlarını gösteren bir başka şekli ise buradan ekledim.

Linux üzerinde bu atamalar ulimit komutuyla yapılabilir. Aşağıda buradan aldığım bir şekilde ulimit ile atanmış değerleri görmek mümkün. Örneğin stack size 10420 kb olarak atanmış.

  • "ulimit -s" komutuyla da uygulamanın en fazla ne kadar stack (kilobyte cinsinden) kullanabileceği  ayarlanabilir.
  •  
  • "ulimit -m" komutuyla da uygulamanın en fazla ne kadar fiziksel hafıza (kilobyte cinsinden) kullanabileceği ayarlanabilir. Eğer fiziksel hafıza sınırı aşılırsa virtual memory kullanılmaya başlanır.



2.1 Kernel ve Virtual Memory
Segment ve Page yapılarına baktıktan sonra kernel'ın tüm bu yapıların nerede başladığını nerede bittiğini takip etmesi gerektiğini görüyoruz. Bu iş için de vm_area_struct denilen yapı kullanılıyor. Çalışan bir uygulamanın kullandığı tüm hafıza bölümlerini takip edebilmek için aşağıdaki şekil kullanılıyor. task_struct çalışan bir uygulamanın bilgilerini tutar. Bu yapının içindeki mm_struct hafıza bilgilerini tutar. bu yapının içindeki vm_area_struct ise bir linked list'tir ve sanal adreslere erişimi sağlar.


How to force a user program to block 16GB of physical RAM? sorusunda da cevaplandığı gibi kernel bazı hafıza adreslerini RAM üzerinde tutmaya ve swap alanını kullanmamaya zorlanabilir. Bu iş için mlock fonksiyonu kullanılabiliyor. mlock fonksiyonunun tersi ise munlock.

2.2 Stack Overflow Koruması

Not : Bu konu ile ilgili olan Posix çağrıları için Kernel ve Stack başlıklı yazıya göz atabilirsiniz.

Stack ve heap uygulamanın içinde büyüklüğü değişen iki bölümdür. Bu bölümlerdeki taşmalar programda Buffer Overflow sebep olur. Bazı derleyiciler Buffer Overflow hatalarını yakalamak için Heap ve Stack segmentleri arasına "Guard Page" denilen veri yapıları yerleştirirler. Böylece stack bölümündeki bir taşmayı yakalamak mümkün olur.
Aşağıdaki şekli buradan aldım ve "Guard Page"'in nereye yerleştiğini gösteriyor.


2.2 Kernel'in Yüklenmesi 

Programlar arasındaki izolasyona tek istisna  kernel'a (çekirdeğe) erişim tekniğidir. Çekirdeğin çoğu kısmı sadece bir defa yüklenir. MMU her bir program için çekirdeğe erişim satırlarını ekler.

Örneğin Windows üzerinde kernel tarafından kullanılan Non-Paged Pool (Disksiz Bellek) sayfaları asla RAM'den çıkarılmaz!.

Linux Virtual Memory yazısında da anlatıldığı gibi Linux üzerind kernel 3 GB'tan yukarıdaki adresleri kullanır.


Burada bu konu ile ilgili güzel bir yazı var. Aşağıdaki şekli ise buradan aldım ve sürekli RAM'de tutulması gereken bellek miktarını gösteriyor.



Konuyla uzaktan ilgili ancak yine de yazmanın faydalı olduğunu düşündüm. Linux'ta cat /proc/meminfo komutuyla görülen bilgilerde memtotal alanı için redhat'in sayfasında şöyle bir açıklama var:
MemTotal: Total usable ram (i.e. physical ram minus a few reserved bits and the kernel binary code)
MemTotal alanını gösteren şekil ise aşağıda. Yukarıdaki kernel'in yüklenmesi şeklini de göz önüne alınca MemTotal'in aslında tüm fiziksel RAM'i değil, RAM'de bulunan kernel kodu hariç geri kalan büyüklüğü gösterdiğini anlamak daha kolay oluyor.


Windows'ta toplam fiziksel belleği görmek daha kolay. Bunu "Görev Yöneticisi" gösteriyor ve örnek bir şekli buradan aldım.

Kernel sadece bir defa yüklense bile diğer uygulamalardan farklı bir güvenlik seviyesinde çalışmaktadır. Bu güvenlik seviyesi Ring 0 olarak anılır.

Dolayısıyla çekirdek kodu daha fazla erişime sahiptir. Bir kodun Ring 0 seviyesinde çalışması virtual memory kullanmadığı anlamına gelmez.


Hiç yorum yok:

Yorum Gönder