1. 程式人生 > >Linux虛擬地址空間分佈

Linux虛擬地址空間分佈

在多工作業系統中,每個程序都執行在屬於自己的記憶體沙盤中。這個沙盤就是虛擬地址空間(Virtual Address Space),在32位模式下它是一個4GB的記憶體地址塊。在Linux系統中, 核心程序和使用者程序所佔的虛擬記憶體比例是1:3,而Windows系統為2:2(通過設定Large-Address-Aware Executables標誌也可為1:3)。這並不意味著核心使用那麼多實體記憶體,僅表示它可支配這部分地址空間,根據需要將其對映到實體記憶體。

虛擬地址通過頁表(Page Table)對映到實體記憶體,頁表由作業系統維護並被處理器引用。核心空間在頁表中擁有較高特權級,因此使用者態程式試圖訪問這些頁時會導致一個頁錯誤(page fault)。在Linux中,核心空間是持續存在的,並且在所有程序中都對映到同樣的實體記憶體。核心程式碼和資料總是可定址,隨時準備處理中斷和系統呼叫。與此相反,使用者模式地址空間的對映隨程序切換的發生而不斷變化。

 Linux程序在虛擬記憶體中的標準記憶體段佈局如下圖所示:

這裡寫圖片描述

使用者程序部分分段儲存內容如下表所示(按地址遞減順序):
這裡寫圖片描述

1 核心空間

核心總是駐留在記憶體中,是作業系統的一部分。核心空間為核心保留,不允許應用程式讀寫該區域的內容或直接呼叫核心程式碼定義的函式。

2 棧(stack)

棧又稱堆疊,由編譯器自動分配釋放,行為類似資料結構中的棧(先進後出)。堆疊主要有三個用途:

為函式內部宣告的非靜態區域性變數(C語言中稱“自動變數”)提供儲存空間。
記錄函式呼叫過程相關的維護性資訊,稱為棧幀(Stack Frame)或過程活動記錄(Procedure Activation Record)。它包括函式返回地址,不適合裝入暫存器的函式引數及一些暫存器值的儲存。除遞迴呼叫外,堆疊並非必需。因為編譯時可獲知區域性變數,引數和返回地址所需空間,並將其分配於BSS段。
臨時儲存區,用於暫存長算術表示式部分計算結果或alloca()函式分配的棧內記憶體。
持續地重用棧空間有助於使活躍的棧記憶體保持在CPU快取中,從而加速訪問。程序中的每個執行緒都有屬於自己的棧。向棧中不斷壓入資料時,若超出其容量就會耗盡棧對應的記憶體區域,從而觸發一個頁錯誤。此時若棧的大小低於堆疊最大值RLIMIT_STACK(通常是8M),則棧會動態增長,程式繼續執行。對映的棧區擴充套件到所需大小後,不再收縮。

Linux中ulimit -s命令可檢視和設定堆疊最大值,當程式使用的堆疊超過該值時, 發生棧溢位(Stack Overflow),程式收到一個段錯誤(Segmentation Fault)。注意,調高堆疊容量可能會增加記憶體開銷和啟動時間。

堆疊既可向下增長(向記憶體低地址)也可向上增長, 這依賴於具體的實現。本文所述堆疊向下增長。

 堆疊的大小在執行時由核心動態調整。

3 記憶體對映段(mmap)

此處,核心將硬碟檔案的內容直接對映到記憶體, 任何應用程式都可通過Linux的mmap()系統呼叫或Windows的CreateFileMapping()/MapViewOfFile()請求這種對映。記憶體對映是一種方便高效的檔案I/O方式, 因而被用於裝載動態共享庫。使用者也可建立匿名記憶體對映,該對映沒有對應的檔案, 可用於存放程式資料。在 Linux中,若通過malloc()請求一大塊記憶體,C執行庫將建立一個匿名記憶體對映,而不使用堆記憶體。”大塊” 意味著比閾值 MMAP_THRESHOLD還大,預設為128KB,可通過mallopt()調整。

該區域用於對映可執行檔案用到的動態連結庫。在Linux 2.4版本中,若可執行檔案依賴共享庫,則系統會為這些動態庫在從0x40000000開始的地址分配相應空間,並在程式裝載時將其載入到該空間。在Linux 2.6核心中,共享庫的起始地址被往上移動至更靠近棧區的位置。

從程序地址空間的佈局可以看到,在有共享庫的情況下,留給堆的可用空間還有兩處:一處是從.bss段到0x40000000,約不到1GB的空間;另一處是從共享庫到棧之間的空間,約不到2GB。這兩塊空間大小取決於棧、共享庫的大小和數量。這樣來看,是否應用程式可申請的最大堆空間只有2GB?事實上,這與Linux核心版本有關。在上面給出的程序地址空間經典佈局圖中,共享庫的裝載地址為0x40000000,這實際上是Linux kernel 2.6版本之前的情況了,在2.6版本里,共享庫的裝載地址已經被挪到靠近棧的位置,即位於0xBFxxxxxx附近,因此,此時的堆範圍就不會被共享庫分割成2個“碎片”,故kernel 2.6的32位Linux系統中,malloc申請的最大記憶體理論值在2.9GB左右。

4 堆(heap)

堆用於存放程序執行時動態分配的記憶體段,可動態擴張或縮減。堆中內容是匿名的,不能按名字直接訪問,只能通過指標間接訪問。當程序呼叫malloc(C)/new(C++)等函式分配記憶體時,新分配的記憶體動態新增到堆上(擴張);當呼叫free(C)/delete(C++)等函式釋放記憶體時,被釋放的記憶體從堆中剔除(縮減) 。

分配的堆記憶體是經過位元組對齊的空間,以適合原子操作。堆管理器通過連結串列管理每個申請的記憶體,由於堆申請和釋放是無序的,最終會產生記憶體碎片。堆記憶體一般由應用程式分配釋放,回收的記憶體可供重新使用。若程式設計師不釋放,程式結束時作業系統可能會自動回收。

堆的末端由break指標標識,當堆管理器需要更多記憶體時,可通過系統呼叫brk()和sbrk()來移動break指標以擴張堆,一般由系統自動呼叫。

使用堆時經常出現兩種問題:1) 釋放或改寫仍在使用的記憶體(“記憶體破壞”);2)未釋放不再使用的記憶體(“記憶體洩漏”)。當釋放次數少於申請次數時,可能已造成記憶體洩漏。洩漏的記憶體往往比忘記釋放的資料結構更大,因為所分配的記憶體通常會圓整為下個大於申請數量的2的冪次(如申請212B,會圓整為256B)。

注意,堆不同於資料結構中的”堆”,其行為類似連結串列。

【擴充套件閱讀】棧和堆的區別

①管理方式:棧由編譯器自動管理;堆由程式設計師控制,使用方便,但易產生記憶體洩露。

②生長方向:棧向低地址擴充套件(即”向下生長”),是連續的記憶體區域;堆向高地址擴充套件(即”向上生長”),是不連續的記憶體區域。這是由於系統用連結串列來儲存空閒記憶體地址,自然不連續,而連結串列從低地址向高地址遍歷。

③空間大小:棧頂地址和棧的最大容量由系統預先規定(通常預設2M或10M);堆的大小則受限於計算機系統中有效的虛擬記憶體,32位Linux系統中堆記憶體可達2.9G空間。

④儲存內容:棧在函式呼叫時,首先壓入主調函式中下條指令(函式呼叫語句的下條可執行語句)的地址,然後是函式實參,然後是被調函式的區域性變數。本次呼叫結束後,區域性變數先出棧,然後是引數,最後棧頂指標指向最開始存的指令地址,程式由該點繼續執行下條可執行語句。堆通常在頭部用一個位元組存放其大小,堆用於儲存生存期與函式呼叫無關的資料,具體內容由程式設計師安排。

⑤分配方式:棧可靜態分配或動態分配。靜態分配由編譯器完成,如區域性變數的分配。動態分配由alloca函式在棧上申請空間,用完後自動釋放。堆只能動態分配且手工釋放。

⑥分配效率:棧由計算機底層提供支援:分配專門的暫存器存放棧地址,壓棧出棧由專門的指令執行,因此效率較高。堆由函式庫提供,機制複雜,效率比棧低得多。Windows系統中VirtualAlloc可直接在程序地址空間中分配一塊記憶體,快速且靈活。

⑦分配後系統響應:只要棧剩餘空間大於所申請空間,系統將為程式提供記憶體,否則報告異常提示棧溢位。

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

此外,由於找到的堆結點大小不一定正好等於申請的大小,系統會自動將多餘的部分重新放入空閒連結串列中。

⑧碎片問題:棧不會存在碎片問題,因為棧是先進後出的佇列,記憶體塊彈出棧之前,在其上面的後進的棧內容已彈出。而頻繁申請釋放操作會造成堆記憶體空間的不連續,從而造成大量碎片,使程式效率降低。

可見,堆容易造成記憶體碎片;由於沒有專門的系統支援,效率很低;由於可能引發使用者態和核心態切換,記憶體申請的代價更為昂貴。所以棧在程式中應用最廣泛,函式呼叫也利用棧來完成,呼叫過程中的引數、返回地址、棧基指標和區域性變數等都採用棧的方式存放。所以,建議儘量使用棧,僅在分配大量或大塊記憶體空間時使用堆。

使用棧和堆時應避免越界發生,否則可能程式崩潰或破壞程式堆、棧結構,產生意想不到的後果。

5 BSS段

 BSS(Block Started by Symbol)段中通常存放程式中以下符號:

未初始化的全域性變數和靜態區域性變數
初始值為0的全域性變數和靜態區域性變數(依賴於編譯器實現)
未定義且初值不為0的符號(該初值即common block的大小)
C語言中,未顯式初始化的靜態分配變數被初始化為0(算術型別)或空指標(指標型別)。由於程式載入時,BSS會被作業系統清零,所以未賦初值或初值為0的全域性變數都在BSS中。BSS段僅為未初始化的靜態分配變數預留位置,在目標檔案中並不佔據空間,這樣可減少目標檔案體積。但程式執行時需為變數分配記憶體空間,故目標檔案必須記錄所有未初始化的靜態分配變數大小總和(通過start_bss和end_bss地址寫入機器程式碼)。當載入器(loader)載入程式時,將為BSS段分配的記憶體初始化為0。在嵌入式軟體中,進入main()函式之前BSS段被C執行時系統對映到初始化為全零的記憶體(效率較高)。

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

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

此外,由於目標檔案不含BSS段,故程式燒入儲存器(Flash)後BSS段地址空間內容未知。U-Boot啟動過程中,將U-Boot的Stage2程式碼(通常位於lib_xxxx/board.c檔案)搬遷(拷貝)到SDRAM空間後必須人為新增清零BSS段的程式碼,而不可依賴於Stage2程式碼中變數定義時賦0值。

6 資料段(Data)

資料段通常用於存放程式中已初始化且初值不為0的全域性變數和靜態區域性變數。資料段屬於靜態記憶體分配(靜態儲存區),可讀可寫。

資料段儲存在目標檔案中(在嵌入式系統裡一般固化在映象檔案中),其內容由程式初始化。例如,對於全域性變數int gVar = 10,必須在目標檔案資料段中儲存10這個資料,然後在程式載入時複製到相應的記憶體。

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

1) BSS段不佔用物理檔案尺寸,但佔用記憶體空間;資料段佔用物理檔案,也佔用記憶體空間。
對於大型陣列如int ar0[10000] = {1, 2, 3, …}和int ar1[10000],ar1放在BSS段,只記錄共有10000*4個位元組需要初始化為0,而不是像ar0那樣記錄每個資料1、2、3…,此時BSS為目標檔案所節省的磁碟空間相當可觀。

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

執行時資料段和BSS段的整個區段通常稱為資料區。某些資料中“資料段”指代資料段 + BSS段 + 堆。

7 程式碼段(text)

程式碼段也稱正文段或文字段,通常用於存放程式執行程式碼(即CPU執行的機器指令)。一般C語言執行語句都編譯成機器程式碼儲存在程式碼段。通常程式碼段是可共享的,因此頻繁執行的程式只需要在記憶體中擁有一份拷貝即可。程式碼段通常屬於只讀,以防止其他程式意外地修改其指令(對該段的寫操作將導致段錯誤)。某些架構也允許程式碼段為可寫,即允許修改程式。

程式碼段指令根據程式設計流程依次執行,對於順序指令,只會執行一次(每個程序);若有反覆,則需使用跳轉指令;若進行遞迴,則需要藉助棧來實現。

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

程式碼段最容易受優化措施影響。
8 保留區
位於虛擬地址空間的最低部分,未賦予實體地址。任何對它的引用都是非法的,用於捕捉使用空指標和小整型值指標引用記憶體的異常情況。

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

【擴充套件閱讀】分段的好處

程序執行過程中,程式碼指令根據流程依次執行,只需訪問一次(當然跳轉和遞迴可能使程式碼執行多次);而資料(資料段和BSS段)通常需要訪問多次,因此單獨開闢空間以方便訪問和節約空間。具體解釋如下:

當程式被裝載後,資料和指令分別對映到兩個虛存區域。資料區對於程序而言可讀寫,而指令區對於程序只讀。兩區的許可權可分別設定為可讀寫和只讀。以防止程式指令被有意或無意地改寫。

現代CPU具有極為強大的快取(Cache)體系,程式必須儘量提高快取命中率。指令區和資料區的分離有利於提高程式的區域性性。現代CPU一般資料快取和指令快取分離,故程式的指令和資料分開存放有利於提高CPU快取命中率。

當系統中執行多個該程式的副本時,其指令相同,故記憶體中只須儲存一份該程式的指令部分。若系統中執行數百程序,通過共享指令將節省大量空間(尤其對於有動態連結的系統)。其他只讀資料如程式裡的圖示、圖片、文字等資源也可共享。而每個副本程序的資料區域不同,它們是程序私有的。

此外,臨時資料及需要再次使用的程式碼在執行時放入棧區中,生命週期短。全域性資料和靜態資料可能在整個程式執行過程中都需要訪問,因此單獨儲存管理。堆區由使用者自由分配,以便管理。

相關推薦

Linux虛擬地址空間分佈

在多工作業系統中,每個程序都執行在屬於自己的記憶體沙盤中。這個沙盤就是虛擬地址空間(Virtual Address Space),在32位模式下它是一個4GB的記憶體地址塊。在Linux系統中, 核心程序和使用者程序所佔的虛擬記憶體比例是1:3,而Windo

一次外場宕機引發對linux記憶體管理的進一步思考--Linux虛擬地址空間如何分佈

0x01 緣由         外場一次伺服器宕機,一群人baba的上去圍觀,分析問題,大部分是猜測,通過回退版本後只解決了問題表象,內在的真實原因沒確定。伺服器上執行著JAVA程式和C程式,到底是什麼導致這次宕機事故。通過分析日誌發現有類似如下錯誤: test_me

Linux虛擬地址空間布局

border 指令 AS 庫函數 app 創建 cell 由於 機會 在多任務操作系統中,每個進程都運行在屬於自己的內存沙盤中。這個沙盤就是虛擬地址空間(Virtual Address Space),在32位模式下它是一個4GB的內存地址塊。在Linu

Linux虛擬地址空間布局以及進程棧和線程棧總結(轉)

開始 系統初始 後來 文本 lov fault 和數 ps命令 變量大小 一:Linux虛擬地址空間布局 (轉自:Linux虛擬地址空間布局) 在多任務操作系統中,每個進程都運行在屬於自己的內存沙盤中。這個沙盤就是虛擬地址空間(Virtual Address Spac

Linux虛擬地址空間佈局

     在多工作業系統中,每個程序都執行在屬於自己的記憶體沙盤中。這個沙盤就是虛擬地址空間(Virtual Address Space),在32位模式下它是一個4GB的記憶體地址塊。在Linux系統中, 核心程序和使用者程序所佔的虛擬記憶體比例是1:3,而Windows系統為2:2(通過設定Large-

Linux下4G虛擬地址空間分佈

我們現在所寫的原始碼並不是我們所說的程式,從C程式碼(.c/.cpp)---->連結程式(.exe)是要經過以下幾個過程才能真正的執行連結的; C源程式--->預編譯處理(.c/.cpp)-->編譯,優化程式(.s)--->彙編程式(.o)---&g

Linux下的4G虛擬地址空間

在windows下4G 地址空間中低2G,0x00000000-0x7FFFFFFF 是使用者地址空間,4G地址空間中高2G,0x80000000-0xFFFFFFFF 是 系統地址空間。訪問 系統地址空間需要程式有ring0的許可權。而Linux對4G空間的劃分不同與windows。linu

Linux下的程序1——程序概念,程序切換,上下文切換,虛擬地址空間

程序概述   當一個可執行程式在現代系統上執行時,作業系統會提供一種假象——好像系統上只有這個程式在執行,看上去只有這個程式在使用處理器,主存和IO裝置。   處理器看上去就像在不間斷的一條接一條的執行程式中的指令,即改程式的程式碼和資料是系統儲存器中唯一的

Linux程序的虛擬地址空間

1.以32位系統為例,Linux系統中每個程序共有3GB的使用者地址空間,當用戶呼叫系統呼叫時,核心執行緒會代表程序執行,此時是在核心空間內執行的,故所有程序共享1GB的核心空間. 所以,總的來說,每個程序可用的地址空間共有4GB 2.程序的3GB使用者地址空

Linux 應用程式的地址空間分佈

Linux 應用程式在被核心調入記憶體中執行後就成為一個程序,因此分析應用程式的地址空間實際上就是分析程序的地址空間分佈。 應用程式的地址空間實際上由以下幾個部分組成:程式碼段、初始化資料段、未初始化資料段(bss段)、堆、棧。其在記憶體中的分佈如下:       A

編譯後的程式是如何在作業系統(linux)中執行的,虛擬地址空間到實際實體記憶體的訪問

Linux中,每個程序通過一個task_struct結構體描述,每個程序地址虛擬空間通過一個mm_struct描述,c語言中每個段空間通過vm_area_struct描述,關係如下, 當執行

Linux程序地址空間 程序記憶體佈局

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

4G虛擬地址空間佈局

4G虛擬地址空間佈局     4G的虛擬記憶體空間: 其中1G是屬於核心空間,另外的3G屬於使用者空間 所有的程序都擁有屬於自己的使用者空間,但卻共享一個核心空間   現在我們從上向下開始分析 首先是使用者空間: ①:128M大

Linux程式地址空間

概述 程式地址空間,如果更加好理解的說,應該叫程序地址空間。因為程式是一些死程式碼,他們並非在記憶體上,而是安安靜靜的躺在硬碟上,只有執行程式,變成程序時才有記憶體的程序地址空間。 先上一個記憶體的空間佈局圖 這感覺也沒

c++ 編譯連結執行原理及虛擬地址空間佈局

當我們寫好.c/.cpp檔案時 此時檔案還不能執行 因為他要經過以下的四步才可以執行   .c/.cpp(生成.i)     編譯(生成.s)      彙編(生成.o)  &nbs

虛擬地址空間的深度剖析

1.為什麼引用虛擬記憶體:         當我們執行一個程式時,會將程式全部裝入記憶體,然後執行。但是在執行時經常會出現一些問題: (1)繼承地址空間沒有隔離,沒有許可權保護。          

結合_虛擬地址空間_簡明扼要地談談蠕蟲病毒的原理

--------參考文獻   湯, 樑, 哲, 湯.計算機作業系統(第四版)[M].西安:西安電子科技大學出版社,2014.5:123-124, 395-396. 一、 ALM的缺陷 Absolute Loading Mode 絕對裝入方式。當計算機系統

Linux程序地址空間 && 程序記憶體佈局

轉載自:https://blog.csdn.net/yusiguyuan/article/details/45155035   一 程序空間分佈概述       對於一個程序,其空間分佈如下圖所示:      

虛擬地址空間佈局和編譯連結執行原理

一、 利用中間層訪問實體記憶體,一是提高了記憶體使用效率和最優分配,二是提高了安全性。中間層又被稱作虛擬地址空間,由此一來,程式訪問的不再是實際的實體記憶體地址,而是訪問虛擬地址,作業系統再將虛擬地址對映到實體記憶體上;linux系統下虛擬地址空間是這樣劃分的: .d

linux4G虛擬地址空間

在linux下編寫程式,不知你是否遇到過 “Segmentation fault” ,出現這種情況大多是因為訪問到了未經允許的地址。 例如:                  linux作業系統