linux系統程序的記憶體布局

2021-08-28 22:20:25 字數 3266 閱讀 6945

文章**:

記憶體管理模組是作業系統的心臟;它對應用程式和系統管理非常重要。今後的幾篇文章中,我將著眼於實際的記憶體問題,但也不避諱其中的技術內幕。由於不少概念是通用的,所以文中大部分例子取自32位x86平台的linux和windows系統。本系列第一篇文章講述應用程式的記憶體布局。

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

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

色區域表示對映到物理記憶體的虛擬位址,而白色區域表示未對映的部分。在上面的例子中,firefox使用了相當多的虛擬位址空間,因為它是傳說中的吃記憶體大戶。位址空間中的各個條帶對應於不同的記憶體段(memory segment),如:堆、棧之類的。記住,這些段只是簡單的記憶體位址範圍,與intel處理器的段沒有關係。不管怎樣,下面是乙個linux程序的標準的記憶體段布局:

最後,我們來看看最底部的記憶體段:bss,資料段,**段。在c語言中,bss和資料段儲存的都是靜態(全域性)變數的內容。區別在於bss儲存的是未被初始化的靜態變數內容,它們的值不是直接在程式的源**中設定的。bss記憶體區域是匿名的:它不對映到任何檔案。如果你寫static int cntactiveusers,則cntactiveusers的內容就會儲存在bss中。

另一方面,資料段儲存在源**中已經初始化了的靜態變數內容。這個記憶體區域不是匿名的。它映**一部分的程式二進位制映象,也就是源**中指定了初始值的靜態變數。所以,如果你寫static int cntworkerbees = 10,則cntworkerbees的內容就儲存在資料段中了,而且初始值為10。儘管資料段映**乙個檔案,但它是乙個私有記憶體對映,這意味著更改此處的記憶體不會影響到被對映的檔案。也必須如此,否則給全域性變數賦值將會改動你硬碟上的二進位制映象,這是不可想象的。

下圖中資料段的例子更加複雜,因為它用了乙個指標。在此情況下,指標gonzo(4位元組記憶體位址)本身的值儲存在資料段中。而它所指向的實際字串則不在這裡。這個字串儲存在**段中,**段是唯讀的,儲存了你全部的**外加零零碎碎的東西,比如字串字面值。**段將你的二進位制檔案也對映到了記憶體中,但對此區域的寫操作都會使你的程式收到段錯誤。這有助於防範指標錯誤,雖然不像在c語言程式設計時就注意防範來得那麼有效。下圖展示了這些段以及我們例子中的變數:

對任何乙個普通c++程式來講,它都會涉及到5種不同的資料段。常用的幾個資料段種包含有「程式**段」、「程式資料段」、「程式堆疊段」等。不錯,這幾種資料段都在其中,但除了以上幾種資料段之外,程序還另外包含兩種資料段。下面我們來簡單歸納一下程序對應的記憶體空間中所包含的5種不同的資料區。

**段:**段是用來存放可執行檔案的操作指令,也就是說是它是可執行程式在記憶體種的映象。**段需要防止在執行時被非法修改,所以只准許讀取操作,而不允許寫入(修改)操作——它是不可寫的。

資料段:資料段用來存放可執行檔案中已初始化全域性變數,換句話說就是存放程式靜態分配的變數和全域性變數。

bss段:bss段包含了程式中未初始化全域性變數,在記憶體中bss段全部置零。

堆(heap):堆是用於存放程序執行中被動態分配的記憶體段,它大小並不固定,可動態擴張或縮減。當程序呼叫malloc/new等函式分配記憶體時,新分配的記憶體就被動態新增到堆上(堆被擴張);當利用free等函式釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)

棧:棧是使用者存放程式臨時建立的區域性變數,也就是說我們函式括弧「{}」中定義的變數(但不包括static宣告的變數,static意味這在資料段中存放變數)。除此以外在函式被呼叫時,其引數也會被壓入發起呼叫的程序棧中,並且待到呼叫結束後,函式的返回值也回被存放回棧中。由於棧的先進先出特點,所以棧特別方便用來儲存/恢復呼叫現場。從這個意義上將我們可以把堆疊看成乙個臨時資料寄存、交換的記憶體區。

我們要知道,棧中存放的是乙個個被調函式所對應的堆疊幀,當函式fun1被呼叫,則fun1的堆疊幀入棧,fun1返回時,fun1的堆疊幀出棧。什麼是堆疊幀呢,堆疊幀其實就是儲存被調函式返回時下一條執行指令的指標、主調函式的堆疊幀的指標、主調函式傳遞給被調函式的實參(如果有的話)、被調函式的區域性變數等資訊的乙個結構。

首先,我們要說明的是如何區分每個堆疊幀,或者說,如何知道我現在在使用哪個堆疊幀。和棧密切相關的有2個暫存器,乙個是ebp,乙個是esp,前者可以叫作棧基址指標,後者可以叫棧頂指標。對於乙個堆疊幀來說,ebp也叫堆疊幀指標,它永遠指向這個堆疊幀的某個固定位置(見上圖),所以可以根據ebp來表示乙個堆疊幀,可以通過對ebp的偏移加減,來在堆疊幀中來來回回的訪問。esp則是隨著push和pop而不斷移動。因此根據esp來對堆疊幀進行操作。

再來講一下上圖,乙個堆疊幀的最頂部,是實參,然後是return address,這個值是由主調函式中的call命令在call呼叫時自動壓入的,不需要我們關心,previousframe pointer,就是主調函式的堆疊幀指標,也就是主調函式的ebp值。ebp偏移為正的都是被調函式的區域性變數。

Linux程序的記憶體布局

這張圖很好,注意其中最上面是高位位址,雖然很多個0,但是c開頭的,不要看反了 更具體的可以看這裡 a.正文段。這是由cpu執行的機器指令部分。通常,正文段是可共享的,所以即使是經常執行的程式 如文字編輯程式 c編譯程式 shell等 在儲存器中也只需要有乙個副本,另外,正文段常常是唯讀的,以防止程式...

Linux程序映象的記憶體布局

作業系統的程序記憶體分為5個區,區 資料區 bss區 堆區 棧區,更高層就是命令列引數和環境變數表,程序的核心空間與使用者空間分別對應不同的記憶體區。全域性常量 區 static const int static cons global 3 全域性靜態常量 區 int init global 2 全...

Linux系統的記憶體布局

在linux系統中每個程序的記憶體由很多部分組成 文字段 段,區 包含了程序執行的程式機器語言指令,也就是 文字段具有唯讀屬性,以防止程序通過錯誤的指標意外修改自身的指令,因為很多個程序可以同時執行同一 所以文字段具有共享屬性,這樣,乙份程式的 可以拷貝對映所有這些程序的虛擬記憶體中。初始化資料段包...