操作系統中棧與堆的理解

  在數據結構中棧具有先進後出的(First in Last Out FIFO)的特性,而在計算機系統中,棧是一個具有以上屬性的動態內存區域。程序可以將數據壓入棧中,也可以將數據從棧中彈出。壓棧的操作使得棧增大,而彈出操作使得棧變小。在一般的操作系統中。棧是向下增長的。在i386機器上,棧頂由稱爲esp的寄存器進行定位。壓棧的操作使得棧頂的地址減小,彈出的操作使得棧頂的地址增大。

此處棧底的地址是0xbfffff,而esp寄存器標明瞭棧頂,地址爲0xbffffff4.在棧上壓入數據會導致esp減小,彈出數據使得esp增大。相反,直接減小esp的值也等效於在棧上開闢空間,直接增大esp的值等下魚在棧上回收空間。

 在程序的運行中,棧保存了一個函數調用所需要的維護信息,通常稱爲堆棧幀(Stack Frame)或活動記錄(Activate Record)堆棧幀一般包括如下幾方面內容

:*函數的返回值和參數

*臨時變量--包括函數的非靜態局部變量以及編譯器自動生成的其他臨時變量

*保存的上下文--包括函數調用前後需要保持不變的寄存器

在i386中,一個函數的活動記錄用ebp和esp這兩個寄存器劃定範圍。esp寄存器始終指向棧的頂部,同時也就指向了當前函數的活動記錄的頂部。而相對的,ebp寄存器執行了函數活動記錄的一個固定位置,ebp寄存器又被稱爲幀指針。

在參數之後的數據(包括參數)既是當前函數的活動記錄,ebp固定在圖中所示的位置,不隨這個函數的執行而變化,相反的esp始終指向棧頂,因此隨着函數的執行,esp會不斷變化。固定不變的ebp可以用來定位感受活動記錄中的各個數據。在ebp之前首先是這個函數的返回值,他的地址是ebp-4,再往前是壓入棧中的參數,他們的地址分別是ebp-8、ebp-12等,視參數數量和大小而定。ebp所直接指向的數據是調用該函數前ebp的值,這樣函數在返回的時候,ebp可以通過讀取這個值恢復到調用前的值。採用這樣的結構原因在於函數調用如下(i386):

*把所有或一部分參數壓入棧中,如果有其他參數沒有入棧,那麼使用某些特地的寄存器傳遞

*把當前指令的下一條指令的地址壓入棧中

*跳轉到函數體執行

其中第2、3步是由指令call一起執行。跳轉到函數體之後開始執行函數。而i386函數體的標準開頭是這樣的(特別情況下也有可能不一樣)

*push ebp--把epb壓入棧中(稱old ebp)

*move ebp,esp--ebp=esp(這時ebp指向棧頂,而此時棧頂就是old ebp)

*【可選】 sub esp,XXX在棧上分配XXX字節的臨時空間

*【可選】push XXX --如有必要,保存名爲XXX寄存器(可重複多個)

把ebp壓入棧中,是爲了在函數返回的時候便於恢復以前的ebp值。而之所以可能要保存一些寄存器,在於編譯器可能要求某些寄存器在調用前後保持不變,那麼函數就可以在開始時將這些寄存器的值壓入棧中,在結束後再取出。不難想象,在函數返回時,所進行的“標準”結尾與“標準”開頭正好相反:

*【可選】pop XXX--如有必要,恢復保存過的寄存器(可重複多個)

*mov esp,ebp--恢復esp同時回收局部變量空間

*pop ebp--從棧中恢復保存的ebp的值

*ret--從棧中取得返回地址,並跳轉到該位置

-----------------------------------------------------------------------------------------------------------------------------------堆---------------------------------------------------------------------------------------------------------------------------------

堆是一塊巨大的內存空間,常常佔據整個虛擬空間的絕大部分。在這片空間裏,程序可以請求一塊連續內存,並自由的使用,這塊內存在程序主動放棄之前都會一直保持有效。

malloc實現的兩種方法:1)把進程的內存管理交給操作系統內核去做,由於內核管理着進程的地址空間,當其提供一個系統調用,使程序可以通過這個系統調用申請內存。該方法理論可行,但是性能比較差(系統調用時性能開銷很大,頻繁調用的話會嚴重影響程序性能)。2)程序向操作系統申請一塊適當大小的堆空間,然後程序自己管理這塊空間,具體來說,管理着堆空間分配的往往是程序的運行庫。

 在進程的地址空間中,除了可執行文件、共享庫和棧之外。剩餘的未分配的空間都可以用來作爲堆空間。

在Linux系統下,其提供兩種堆空間分配方式,即兩個系統調用:一個是brk()系統調用,另一個是mmap()。brk()的C語言形式聲明如下:

int brk(void * end_data_segment)

brk()的作用實際上是設置進程數據段的結束地址,即它可以擴大或者縮小數據段(Linux下數據段和BSS合併在一起統稱爲數據段)。如果我們將數據段的結束地址向高地址移動,那麼擴大的那部分空間就可以被我們使用,最常見的做法就是將這個空間視爲堆空間。

mmap()的作業和window系統下的VituralAlloc很相似,它的作用就是向操作系統申請一段虛擬地址空間,當然這塊虛擬地址空間可以映射到某個文件(這也是這個系統調用的最初作用),當它不將地址空間映射到某個文件時,我們又將這塊空間稱爲匿名空間(Anonymous),匿名空間就可以拿來作爲堆空間。其聲明如下

void * mmap(

void *start,

size_t length,

int prot,

int flags,

int fd.

off_t offset);

mmap的前兩個參數分別用於指定需要申請的空間的起始地址和長度,如果起始地址設置爲0,那麼Linux系統會自動挑選合適的其實地址。prot/flags這兩個參數分別用於設置申請空間的權限(可讀、可寫、可執行)以及映射類型(文件映射、匿名空間等),最後兩個參數是用於文件映射時指定文件描述符和文件偏移的。

對於malloc函數,處理用戶空間請求時:對於128kb的請求來說,其會在現有的堆空間裏面,按照堆分配算法爲其分配一塊空間並返回:對於大於128kb的請求來說,它會使用mmap()函數爲它分配一塊匿名空間,然後在這個匿名空間中爲用戶分配空間。當然也可以直接使用mmap()函數來實現malloc函數

void *malloc(size_t nbytes){
void * res=mmap(0,nbytes,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONMOUS,0,0);
if(ret==MAP_FAILED)
 {return 0;}
return ret;

}

在malloc中申請的內存,在進程結束會便不會存在。因爲當進程結束後,所有與進程相關的資源,包括進程的地址空間、物理內存、打開的文件、網絡鏈接等都被操作系統關閉或者收回、所以無論malloc申請了多少內存,進程結束後都不會存在

malloc申請的空間:如果是指虛擬空間的話,其爲連續的,即每次malloc分配後返回的空間都可以看作是一塊連續的地址;如果空間是指“物理空間”則並不一定聯繫,對於一塊連續的虛擬空間來說,其可能是由若干個不連續的物理頁拼湊而成。


堆分配算法:

1)空閒鏈表--其方法是把堆中各個空閒的塊按照鏈表的方式連接起來,當用戶請求一塊空間時,可以遍歷整個列表,直到找到合適大小的塊並且將它拆分。當用戶釋放空間時將它合併到空閒鏈表中。

2)位圖(Bitmap)--核心思想是將整個堆劃分爲大量的塊(block),每個塊大小相同。當用戶請求內存的時候,總是分配整數個塊的空間給用戶,第一個塊我們稱爲已分配區域的頭(Head),其餘的稱爲已分配區域的主體(Body).而我們可以使用一個整數數組來記錄塊的使用情況,由於每個塊只有頭/主體/空閒三種狀態,因而僅僅需要兩位即可表示一個塊,因此稱爲位圖。位圖的實現具有以下優點:(1)速度快--由於整個堆的空閒信息存儲在一個數組內,因此訪問該數組時cache容易命中(2)穩定性好--爲了避免用戶越界讀寫破壞數據,我們只需簡單的備份一下位圖即可。而且即使部分數據被破壞,也不會導致整個堆無法工作(3)塊不需要額外信息,易於管理;其缺點有:(1)分配時容易產生碎片(2)如果堆很大,或者設定的塊很小(可以減少碎片),那麼位圖會很大,可能失去cache命中率高的優勢,並且浪費一定的空間。此種情形出現時可以使用多級的位圖。

(3)對象池--其思路爲:如果每一次分配的空間大小都一樣,那麼可以按照這個每次請求分配的大小作爲一個單位,把整個堆空間劃分爲大量的小塊,每次請求的時候只需要找到一個小塊就可以了。對象池的管理方法可以採用空閒鏈表,也可以採用位圖,與他們的區別僅僅在於它假定每次請求的都是一個固定的大小,因此實現起來很容易,由於每次總是隻請求一個單位的內存,因此請求得到滿足的速度非常快,無需查找足夠大的空間。

  在現實應用中,堆分配算法往往是採取多種算法複合而成的。比如對與glibc來說,它對小於64字節的空間申請是採用類似於對象池的方法;而對於大於512字節的空間申請採用的是最佳適配算法,對於大於64字節而小於512字節的,會根據情況採用上述方法中的折衷策略;對於大於128kb的申請,它會使用mmap機制直接向操作系統申請空間。

發佈了18 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章