程序位址空間分布

2021-07-16 21:05:27 字數 3086 閱讀 8169

對於乙個程序,其空間分布如下圖所示:

c程式一般分為:

1.程式段:程式段為程式**在記憶體中的對映.乙個程式可以在記憶體中多有個副本.

2.初始化過的資料:在程式執行值初已經對變數進行初始化的

3.未初始化過的資料:在程式執行初未對變數進行初始化的資料

4.堆(stack):儲存區域性,臨時變數,在程式塊開始時自動分配記憶體,結束時自動釋放記憶體.儲存函式的返回指標.

5.棧(heap):儲存動態記憶體分配,需要程式設計師手工分配,手工釋放.

多工作業系統中的每乙個程序都執行在乙個屬於它自己的記憶體沙盤中,這個沙盤就是虛擬位址空間(virtual address space),在32位模式下,它總是乙個4gb的記憶體位址塊。這些虛擬位址通過頁表(page table)對映到物理記憶體,頁表由作業系統維護並被處理器引用。每個程序都擁有一套屬於它自己的頁表,但是還有乙個隱情,只要虛擬位址被使能,那麼它將會作用於這台機器上執行的所有軟體,包括核心本身,因此,有一部分虛擬位址必須保留給核心使用。程序記憶體空間分布如下圖所示:

但是這並不意味著核心使用了這麼多的物理記憶體,僅表示它可以支配這麼大的位址空間。可根據核心需要,將其對映到物理記憶體。核心空間在頁表中擁有較高的特權級(ring2或以下),因此,只要使用者態的程式試圖訪問這些頁,就會導致乙個頁錯誤(page fault)。在linux中,核心空間是持續存在的,並且在所有程序中都對映到同樣的物理記憶體,核心**和資料總是可定址的,隨時準備處理中斷和系統呼叫。與之相反,使用者模式位址空間的對映隨著程序切換的發生而不斷的變化,如下圖所示:

上圖中藍色區域表示對映到物理記憶體的虛擬位址,而白色區域表示未對映的部分。可以看出,firefox使用了相當多的虛擬位址空間,因為它占用記憶體較多。

linux程序標準的記憶體段布局,如下圖所示,位址空間中的各個條帶對應於不同的記憶體段(memory segment),如:堆、棧之類的。記住,這些段只是簡單的虛擬記憶體位址空間範圍,與intel處理器的段沒有任何關係。

幾乎每個程序的虛擬位址空間中各段的分布都與上圖完全一致。

程序位址空間中最頂部的段是棧,大多數程式語言將之用於儲存函式引數和區域性變數。呼叫乙個方法或函式會將乙個新的棧幀(stack frame)壓入到棧中,這個棧幀會在函式返回時被清理掉。由於棧中資料嚴格的遵守lifo的順序,這個簡單的設計意味著不必使用複雜的資料結構來追蹤棧中的內容,只需要乙個簡單的指標指向棧的頂端即可,因此壓棧(pushing)和退棧(popping)過程非常迅速、準確。程序中的每乙個執行緒都有屬於自己的棧。

通過不斷向棧中壓入資料,超出其容量就會耗盡棧所對應的記憶體區域,這將觸發乙個頁故障(page fault),而被linux的expand_stack()處理,它會呼叫acct_stack_growth()來檢查是否還有合適的地方用於棧的增長。如果棧的大小低於rlimit_stack(通常為8mb),那麼一般情況下棧會被加長,程式繼續執行,感覺不到發生了什麼事情。這是一種將棧擴充套件到所需大小的常規機制。然而,如果達到了最大棧空間的大小,就會棧溢位(stack overflow),程式收到乙個段錯誤(segmentation fault)。

動態棧增長是唯一一種訪問未對映記憶體區域而被允許的情形,其他任何對未對映記憶體區域的訪問都會觸發頁錯誤,從而導致段錯誤。一些被對映的區域是唯讀的,因此企圖寫這些區域也會導致段錯誤。

接下來的一塊記憶體空間是堆。與棧一樣,堆用於執行時記憶體分配;但不同的是,堆用於儲存那些生存期與函式呼叫無關的資料。大部分語言都提供了堆管理功能。在c語言中,堆分配的介面是malloc()函式。如果堆中有足夠的空間來滿足記憶體請求,它就可以被語言執行時庫處理而不需要核心參與,否則,堆會被擴大,通過brk()系統呼叫來分配請求所需的記憶體塊。堆管理是很複雜的,需要精細的演算法來應付我們程式中雜亂的分配模式,優化速度和記憶體使用效率。處理乙個堆請求所需的時間會大幅度的變動。實時系統通過特殊目的分配器來解決這個問題。堆在分配過程中可能會變得零零碎碎,如下圖所示:

最後,我們看看底部的記憶體段:bss,資料段,**段。

在c語言中,bss和資料段儲存的都是靜態(全域性)變數的內容。區別在於bss儲存的是未被初始化的靜態變數內容,他們的值不是直接在程式的原始碼中設定的。bss記憶體區域是匿名的,它不對映到任何檔案。如果你寫static intcntactiveusers,則cntactiveusers的內容就會儲存到bss中去。而資料段則儲存在源**中已經初始化的靜態變數的內容。資料段不是匿名的,它映**一部分的程式二進位制映象,也就是源**中指定了初始值的靜態變數。所以,如果你寫static int cntactiveusers=10,則cntactiveusers的內容就儲存在了資料段中,而且初始值是10。儘管資料段映**乙個檔案,但它是乙個私有記憶體對映,這意味著更改此處的記憶體不會影響被對映的檔案。

你可以通過閱讀檔案/proc/pid_of_process/maps來檢驗乙個linux程序中的記憶體區域。記住:乙個段可能包含許多區域。比如,每個記憶體對映檔案在mmap段中都有屬於自己的區域,動態庫擁有類似bss和資料段的額外區域。有時人們提到「資料段」,指的是全部的資料段+bss+堆。

你還可以通過nm和objdump命令來察看二進位制映象,列印其中的符號,它們的位址,段等資訊。最後需要指出的是,前文描述的虛擬位址布局在linux中是一種「靈活布局」,而且作為預設方式已經有些年頭了,它假設我們有值rlimt_stack。但是,當沒有該值得限制時,linux退回到「經典布局」,如下圖所示:

c語言程式例項分析如下所示:

[html]view plain

copy

print?

#include

<

stdio.h

>

#include <

malloc.h

>

void print(char *,int);  

int main()  

void print(char *str,intp)  

/* 棧和堆是在程式執行時候動態分配的,區域性變數均在棧上分配。棧是反向增長的,位址遞減;malloc等分配的記憶體空間在堆空間。堆是正向增長的,位址遞增。  

r,w變數在棧上(則&r>

&w),r,w所指內容在堆中(即r

<

w)。*/ 

位址空間分布

最近看了本書,突然對於位址空間有些疑惑。在深入理解linux核心中把位址分為三類 邏輯位址 組合語言中運算元位址或指令的位址,對於80x86的cup,邏輯位址是段 段內偏移位址 線性位址 也叫虛擬位址 和實體地址。但在stott maxwell的 linux core kernel commentr...

程序位址空間

這篇文章應該不能說是原創的,這裡的記錄都是我通過閱讀整理來的,並沒有太多的自己的想法。資料 現代作業系統 之所以去了解位址空間也是因為在學習dll的時候看到要將dll對映到程式的位址空間,不甚明了所以去查詢相關的資料。位址空間其實很好理解 當然針對早期的機器 早期的機器是沒有ram,rom,cach...

程序位址空間

kernel筆記 程序位址空間 2013 09 03 09 53 49 分類 linux 下圖是x86 64下linux程序的預設記憶體布局形式 下面逐一分析以上各個位址段的含義。text 段 段,從虛擬記憶體位址00400000開始,使用pmap 可以檢視到,這個位址是固定的 linux pmap...