国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Linux > 正文

Linux進程內存布局(翻譯)

2024-06-28 13:24:32
字體:
來源:轉載
供稿:網友
linux進程內存布局(翻譯) 2014-08-26 11:29 by 燃燒吧安菲拉爾, ... 閱讀, ... 評論, 收藏, 編輯

Anatomy of a PRogram in Memory

在一個多任務OS中,每個進程都運行在它自己的內存沙箱中。這個沙箱就是虛擬地址空間,在32位下就是一塊容量為4GB的內存地址。內核將這些虛擬地址按頁表(page table)映射為物理內存,并交由CPU訪問。每個進程有自己的頁表集,但有一點要注意。虛擬地址一旦被啟用,就會應用到機器上所有運行的程序上,也包括內核自己。因此虛擬地址空間必須為內核預留一部分(否則就沒辦法和內核交互了):

給內核預留那么多空間,并不是說內核真的使用了那么多物理內存,只是內核可以這部分虛擬地址自由的映射到某片物理內存上(而不用和其它虛擬地址遵循相同的規則)。內核空間在頁表中被標記為專屬的特權代碼(ring 2或更低),用戶態的程序訪問它時就會產生頁錯誤(page fault)。Linux的內核空間在所有進程中都映射到相同的物理內存上。內核代碼和數據總是可尋址的(總是可以找到的),任意時刻都可處理中斷和系統調用。作為對比,當進程切換時,用戶態地址空間的映射就會發生改變:

藍色區域表示映射到物理內存的虛擬地址空間,而白色區域則表示未映射的空間。在這個例子中,Firefox驚人的內存需求讓它使用的虛擬地址遠遠超過了其自身的地址空間。內存地址空間是按堆、棧這樣的內存段進行管理的。要記住內存段就是簡單的一個內存地址范圍,而且與Intel風格的段沒有任何關系。下面是一個Linux進程的標準內存段布局:

如果計算過程輕松愉快、準確無誤,那么上圖顯示的內存段起始虛擬地址在幾乎每個進程中都是一樣的。這導致了遠程利用安全漏洞變得非常容易。一次漏洞探測通常需要引用內存的絕對地址:一個棧上地址,一個庫函數的地址,等等。遠程攻擊者只能盲目的選擇這樣的地址,指望地址空間都是一樣的。如果這種情況真發生了,用戶就悲劇了。因此地址空間隨機化變的很流行。Linux會給棧、mmap段、和堆的起始地址一個隨機偏離。不幸的是,32位地址空間實在是太緊湊了,只給隨機化很小的空間,妨礙了它的效果。

進程地址空間的最上面是棧,棧里保存了局部變量,以及大多數編程語言中的函數參數。一次方法或函數調用就會向棧增加一個棧幀(stack frame)。當函數返回時棧幀就會被銷毀。因為數據遵循嚴格的“后入先出”順序,這種簡單的設計意味著不需要復雜的數據結構就能追蹤到棧的上下文——棧頂的一個指針就搞定。棧的Push和pop操作因此變得快速且確定。同時,棧區域的穩定重用也有助于棧內存在CPU緩存中保持活躍,加快了訪問速度。進程中的每個線程都有自己的棧。

如果推入棧的數據過多,可能會耗盡棧映射的地址區域。這會導致一次頁錯誤,Linux將其處理為一次expand_stack()調用,實際上是調用acct_stack_growth()檢查當前是否可以增加棧大小。如果棧大小小于RLIMIT_STACK(通常8MB)就可以繼續增長,程序會正常繼續,不會察覺到什么。這是棧大小調整的默認處理。但是,如果棧大小達到了上限,就會發生棧溢出,程序會接到一次段錯誤(Segmentation Fault)。相對的,當棧變小時,不會縮減棧大小。這就像聯邦預算,只增不減(笑)。

在訪問到未映射內存區(上圖中的白色部分)時,只有動態棧增長可能是合法的。其它方式訪問到未映射區域時都會引發一次頁錯誤,進而導致段錯誤。一些映射區是只讀的,對它們的寫操作也會導致段錯誤。

在棧的下面就是mmap段,內核在這里將文件內容映射為內存。任何應用都可以通過Linux下的mmap()或Windows下的CreateFileMapping()/MapViewOfFile()申請一片mmap區域。mmap是一種高效便捷的文件I/O方式,被用于加載動態鏈接庫。我們同樣可以創建一塊與文件沒有關系的mmap區,用來存放程序的數據。在Linux中,如果你通過malloc()申請一大塊內存,glibc會返回一塊匿名的mmap內存塊,而不是用堆內存。“大”意思是比MMAP_THRESHOLD大,通常是128KB,可以調用mallopt()修改。

接下來我們開始說堆。堆提供了運行時的內存分配,這點和棧類似;數據的生命期與執行分配的函數生命期不一致,這點與棧不同。大多數語言都提供了堆的管理。因此滿足內存請求需要語言的運行環境和內核協作完成。C語言中分配堆的接口是malloc()族函數,而在有gc的語言(例如C#)其接口則是關鍵字new。

如果堆內存足夠,語言運行時環境就可以處理內存請求,不需要內核介入,或者可以通過brk()系統調用去增大堆內存。堆的管理很復雜,在面對程序雜亂的分配方式時,需要使用在速度和內存使用率上努力做平衡的微妙算法。滿足一次堆請求的時間差異可以非常大。實時系統需要特殊用途的分配器來處理這個問題。使用時堆也會變的很碎片化,見下圖:

最后,我們看一下最下面的內存段:BSS、data、代碼段。在C里面BSS和data段都是存儲靜態變量(全局)的數據。區別是BSS段存儲沒有初始化的靜態變量,即在代碼中沒有初始值的靜態變量。BSS區是匿名的:不映射自任何文件。如果代碼中有static int cntActiveUsers;,那么cntActiveUsers就在BSS段。

而data段則存放代碼中顯示初始化了的靜態變量。這塊內存區不是匿名的,它映射自程序鏡像中包含對應靜態變量的文件。這種映射是私有映射,即內存中的變化不會反映到文件中。如果不這樣,改變一個全局變量的值就會修改你磁盤中的鏡像文件,這就太荒謬了!

下圖中的data示例有些取巧,使用了一個指針。指針gonzo的內容——4字節的地址——就存在data段。而它指向的字符串則不是,字符串存放在text段。text段是只讀的,存放字符串字面值等不會執行的代碼。text段也會映射程序文件,但對它的寫操作會引發段錯誤。這會避免一些指針bug,但不像第一時間在C代碼中避免指針bug那么有效。下圖顯示了這些段和示例變量:

你可以讀/proc/pid_of_process/maps來檢查一個Linux進程的內存區。一個內存段可能包含多個內存區。例如,每個mmap映射的文件都會在mmap段有一個單獨的內存區,而動態庫還會有在BSS和data段的內存區。下篇文章會剖析“內存區”意味著什么。有時人們也會用“data段”代指整個data + BSS + 堆。

你可以用nm和objdump命令檢查鏡像文件中的符號、它們的地址、所屬的段,等等。最后,上面說的虛擬地址布局是Linux的“靈活”布局,也是近年來Linux的默認布局。它假設RLIMIT_STACK有值。否則Linux會回到下圖的“經典”布局:


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 金华市| 杨浦区| 鞍山市| 玉溪市| 江阴市| 天柱县| 贵州省| 丹江口市| 金沙县| 石台县| 双流县| 桦南县| 清丰县| 开阳县| 华蓥市| 德格县| 红原县| 蓬溪县| 同仁县| 共和县| 佳木斯市| 偃师市| 应用必备| 山阴县| 祁东县| 宁津县| 河间市| 武夷山市| 南投市| 揭西县| 河南省| 江口县| 太和县| 武宁县| 大丰市| 浦县| 阳江市| 莱西市| 精河县| 北安市| 洛阳市|