Linux虛擬位址空間

2021-08-30 17:50:48 字數 4050 閱讀 8584

在多工作業系統中,每個程序都執行在屬於自己的記憶體沙盤中。這個沙盤就是虛擬位址空間(virtual address space),在32位模式下它是乙個4gb的記憶體位址塊,這篇部落格均是x86架構的

1. 位址空間分布

2. 核心位址空間

從pkmap_base 到 fixaddr_start用於對映高階記憶體

vmalloc area loremap area 動態對映空間

3. 使用者位址空間

envioment variables 為環境變數

command line arguments 為命令列引數

random stack offset和random mmap offset等隨機值意在防止惡意程式。linux通過對棧、記憶體對映段、堆的起始位址加上隨機偏移量來打亂布局,以免惡意程式通過計算訪問棧、庫函式等

(1). 棧

棧可以被稱為堆疊,由編譯器進行管理(自動釋放和分配)並且是先入後出,這裡說的堆疊是程序棧(棧分為程序棧,核心棧,執行緒棧, 中斷棧)

程序棧的初始化大小是由編譯器和鏈結器計算出來的,但是棧的實時大小並不是固定的,linux 核心會根據入棧情況對棧區進行動態增長(其實也就是新增新的頁表)。但是並不是說棧區可以無限增長,它也有最大限制 rlimit_stack (一般為 8m)。

棧的特點和用途:

1. 程序呼叫函式產生的棧幀建立在棧區(首先壓入主調函式中下條指令(函式呼叫語句的下條可執行語句)的位址,然後是函式實參,然後是被調函式的區域性變數。本次呼叫結束後,區域性變數先出棧,然後是引數,最後棧頂指標指向最開始存的指令位址,程式由該點繼續執行下條可執行語句)

2. 臨時儲存區,用於暫存長算術表示式部分計算結果或alloca()函式分配的棧內記憶體,或者c++的臨時物件。

3. 棧由計算機底層提供支援:分配專門的暫存器存放棧位址,壓棧出棧由專門的指令執行, 其相比堆效率較高

程序棧的動態增長實現

程序在執行的過程中,通過不斷向棧區壓入資料,當超出棧區容量時,就會耗盡棧所對應的記憶體區域,這將觸發乙個 缺頁異常 (page fault)。通過異常陷入核心態後,異常會被核心的 expand_stack() 函式處理,進而呼叫 acct_stack_growth() 來檢查是否還有合適的地方用於棧的增長。

如果棧的大小低於 rlimit_stack(通常為8mb),那麼一般情況下棧會被加長,程式繼續執行,感覺不到發生了什麼事情,這是一種將棧擴充套件到所需大小的常規機制。然而,如果達到了最大棧空間的大小,就會發生 棧溢位(stack overflow),程序將會收到核心發出的 段錯誤(segmentation fault) 訊號。

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

(2). 記憶體對映段(mmap)

(3). 堆

堆的特點:

1> 堆管理器通過鍊錶管理堆,由於申請和釋放堆記憶體是無序的因此會產生記憶體碎片,堆的釋放由程式設計師完成,**的記憶體可以重新使用, 如果不釋放只有在程式結束時才會統一釋放。

2> 堆的末端由break指標標識,當堆管理器需要更多記憶體時,可通過系統呼叫brk()和sbrk()來移動break指標以擴張堆(向上生長並且32位linux系統中堆記憶體理論上可達2.9g空間),一般由系統自動呼叫。

3> 作業系統為堆維護乙個記錄空閒記憶體位址的鍊錶。當系統收到程式的記憶體分配申請時,會遍歷該鍊錶尋找第乙個空間大於所申請空間的堆結點,然後將該結點從空閒結點鍊錶中刪除,並將該結點空間分配給程式。若無足夠大小的空間(可能由於記憶體碎片太多),有可能呼叫系統功能去增加程式資料段的記憶體空間,以便有機會分到足夠大小的記憶體,然後進行返回。,大多數系統會在該記憶體空間首位址處記錄本次分配的記憶體大小,供後續的釋放函式(如free/delete)正確釋放本記憶體空間。此外,由於找到的堆結點大小不一定正好等於申請的大小,系統會自動將多餘的部分重新放入空閒鍊錶中。

使用堆時經常出現兩種問題

1> 釋放或改寫仍在使用的記憶體(「記憶體破壞」);

2> 未釋放不再使用的記憶體(「記憶體洩漏」)。當釋放次數少於申請次數時,可能已造成記憶體洩漏。****存往往比忘記釋放的資料結構更大,因為所分配的記憶體通常會圓整為下個大於申請數量的2的冪次(如申請212b,會圓整為256b)。

(4). bss段

bss段用於存放程式的以下符號:

由於程式載入時,bss會被作業系統清零,所以未賦初值或初值為0的全域性變數都在bss中。bss段僅為未初始化的靜態分配變數預留位置,在目標檔案中並不佔據空間,這樣可減少目標檔案體積。但程式執行時需為變數分配記憶體空間,故目標檔案必須記錄所有未初始化的靜態分配變數大小總和(通過start_bss和end_bss位址寫入機器**)。當載入器(loader)引導程式時,將為bss段分配的記憶體初始化為0

儘管均放置於bss段,但初值為0的全域性變數是強符號,而未初始化的全域性變數是弱符號。若其他地方已定義同名的強符號(初值可能非0),則弱符號與之鏈結時不會引起重定義錯誤,但執行時的初值可能並非期望值(會被強符號覆蓋)。因此,定義全域性變數時,若只有本檔案使用,則盡量使用static關鍵字修飾;否則需要為全域性變數定義賦初值(哪怕0值),保證該變數為強符號,以便鏈結時發現變數名衝突,而不是被未知值覆蓋。

gcc將未初始化的全域性變數儲存在common段,鏈結時再將其放入bss段。在編譯階段可通過-fno-common選項來禁止將未初始化的全域性變數放入common段。

(5). 資料段

資料段通常用於存放程式中已初始化且初值不為0的全域性變數和靜態區域性變數。資料段屬於靜態記憶體分配(靜態儲存區),可讀可寫。資料段儲存在目標檔案中,其內容由程式初始化。例如,對於全域性變數int gvar = 10,必須在目標檔案資料段中儲存10這個資料,然後在程式載入時複製到相應的記憶體。

資料段與bss段的區別如下:

1> bss段不占用物理檔案尺寸,但占用記憶體空間;資料段占用物理檔案,也占用記憶體空間。

對於大型陣列如int ar0[1000] = 和int ar1[1000],ar1放在bss段,只記錄共有1000*4個位元組需要初始化為0,而不是像ar0那樣記錄每個資料1、2、3…,此時bss為目標檔案所節省的磁碟空間相當可觀。

2) 當程式讀取資料段的資料時,系統會出發缺頁故障,從而分配相應的物理記憶體;當程式讀取bss段的資料時,核心會將其轉到乙個全零頁面,不會發生缺頁故障,也不會為其分配相應的物理記憶體。

(6). **段

**段也稱正文段或文字段,通常用於存放程式執行**(即cpu執行的機器指令)。**段通常屬於唯讀,以防止其他程式意外地修改其指令(對該段的寫操作將導致段錯誤)。

**段還存放一些唯讀資料如字串常量。

**段指令中包括操作碼和操作物件(或物件位址引用)。若操作物件是立即數(具體數值),將直接包含在**中;若是區域性資料,將在棧區分配空間,然後引用該資料位址;若位於bss段和資料段,同樣引用該資料位址

(7) 保留區

位於虛擬位址空間的最低部分,未賦予實體地址。任何對它的引用都是非法的,用於捕捉使用空指標和小整型值指標引用記憶體的異常情況。

它並不是乙個單一的記憶體區域,而是對位址空間中受到作業系統保護而禁止使用者程序訪問的位址區域的總稱。大多數作業系統中,極小的位址通常都是不允許訪問的,如null。c語言將無效指標賦值為0也是出於這種考慮,因為0位址上正常情況下不會存放有效的可訪問資料。

在32位x86架構的linux系統中,使用者程序可執行程式一般從虛擬位址空間0x08048000開始載入。該載入位址由elf檔案頭決定,可通過自定義鏈結器指令碼覆蓋鏈結器預設配置,進而修改載入位址。0x08048000以下的位址空間通常由c動態鏈結庫、動態載入器ld.so和核心vdso(核心提供的虛擬共享庫)等占用。通過使用mmap系統呼叫,可訪問0x08048000以下的位址空間。

linux使用者空間與核心位址空間

linux記憶體對映mmap原理分析

Linux虛擬位址空間

為了防止不同程序同一時刻在物理記憶體中執行而對物理記憶體的爭奪和踐踏,採用了虛擬記憶體。虛擬記憶體技術使得不同程序在執行過程中,它所看得到的是自己獨自占有了當前系統的4g記憶體。所有程序共享同一物理記憶體,每個程序只把自己目前需要的虛擬記憶體空間對映並儲存到物理記憶體上。事實上,在每個程序建立載入時...

虛擬位址空間

當處理器讀或寫入記憶體位置時,它會使用虛擬位址。作為讀或寫操作的一部分,處理器將虛擬位址轉換為實體地址。通過虛擬位址訪問記憶體有以下優勢 程序可用的虛擬位址範圍稱為該程序的 虛擬位址空間 每個使用者模式程序都有其各自的專用虛擬位址空間。對於 32 位程序,虛擬位址空間通常為 2 gb,範圍從 0x0...

虛擬位址空間

14 共 14 對本文的評價是有幫助 評價此主題 程序可用的虛擬位址範圍稱為該程序的 虛擬位址空間 每個使用者模式程序都有其各自的專用虛擬位址空間。對於 32 位程序,虛擬位址空間通常為 2 gb,範圍從 0x00000000 至 0x7fffffff。對於 64 位程序,虛擬位址空間為 8 tb,...