1. 程式人生 > >LINUX程式(程序)在記憶體中的佈局

LINUX程式(程序)在記憶體中的佈局

   正常情況下每個程序的虛擬地址空間都基本上與上圖類似. 這導致很容易遠端利用安全漏洞攻擊,這種攻
擊往往需要知道程序的一個地址: 一個棧上的地址,或者一個lib庫的一個函式的地址等等. 遠端攻擊者必
須猜測到這個地址然後進行攻擊, 因為地址空間基本一樣,早期的攻擊比較容易,後來慢慢有了地址空間隨
機化技術,linux會隨機stack、mmap、heap在虛擬空間中的地址,一般通過在這些段空間起始地址加一個
隨機的offset. 但是32位的地址空間本來就很小,導致地址隨機化的效果並不明顯。

    在程序的虛擬地址空間中靠近上面是棧,棧儲存了本地變數、函式入參等. 呼叫一個新的函式會在棧上
建立一個新的棧幀,每當函式返回值這個棧幀會被自動銷燬,棧地址的管理非常簡單可能是因為資料嚴格遵循
LIFO的順序,不需要複雜的資料結構來跟蹤棧地址,只需要一個棧頂指標可以搞定.而且棧的push和pop操作
都非常快速和簡單. 另外棧空間一直重複使用(push\pop)有利於棧記憶體活躍在cpu cache中加快訪問速度.
對於執行緒來說每一個執行緒都有自己的棧空間.

    當棧空間用盡後繼續push資料會觸發棧空間的擴充套件. 這會觸發一個 page fault 然後在核心中呼叫
expand_stack()函式. 該函式呼叫acct_stack_growth()來判斷是否可以增長佔空間. 如果當前棧空間
的大小小於RLIMIT_STACK(8M),可以繼續增長棧空間. 該過程由核心完成程序不會感知到.  當用戶的佔
空間已經達到允許的最大值時,核心會給程序傳送一個Segmentation Fault訊號終止該程序.  程序的棧空
間只會增大不會縮小,有點像聯邦運算,只增不減.

    棧空間的動態增長是唯一一種可以訪問未對映的地址空間的情況. 其他任何訪問未對映地址空間的操作都
會觸發Segmentation Fault. 當然去寫一個只讀的地址空間也肯定會觸發Segmentation Fault.
    在棧空間下面是mmap區域, 在這些區域中核心將檔案直接對映到地址空間中. 應用程式可以顯示建立這些
區域通過呼叫mmap()/ CreateFileMapping()/MapViewOfFile(). 記憶體對映是一種高效和方便操作檔案的
一種方式, 所以記憶體對映通常被用來載入動態連結庫.  也可以建立一個匿名的mmap空間不指向任何檔案而用來
儲存程式資料. 在linux中如果使用者malloc 一個非常大塊的記憶體,標準c庫會通過mmap來建立這塊記憶體區間而
不使用Heap記憶體, 這裡的大塊是指超過MMAP_THRESHOLD( 128k), MMAP_THRESHOLD可以通過
mallopt()函式動態調整.

    說到堆記憶體,它一個我們下面要講述的重要的一個地址空間,堆提供了程式執行時的記憶體分配, 堆記憶體的生
命週期在函式之外. 大部分語言都提供了堆記憶體管理函式, 如C語言的malloc() free().
    如果當前堆的記憶體足夠程式使用,不許要與核心互動,在當前堆中尋找可用記憶體就行, 否則的話需要呼叫
brk()系統呼叫在核心中增大堆記憶體. 堆記憶體分貝的演算法非常複雜, 既要保證記憶體分配的實時性和快速,又要盡
量避免堆中出現過多碎片, 如下圖所示: