1. 程式人生 > >Linux程序地址空間 && 程序記憶體佈局

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

一 程序空間分佈概述     對於一個程序,其空間分佈如下圖所示:

                                     

程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。

初始化過的資料(Data):在程式執行初已經對變數進行初始化的資料。

未初始化過的資料(BSS):在程式執行初未對變數進行初始化的資料。

棧 (Stack):儲存區域性、臨時變數,函式呼叫時,儲存函式的返回指標,用於控制函式的呼叫和返回。在程式塊開始時自動分配記憶體,結束時自動釋放記憶體,其操作方式類似於資料結構中的棧。

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

注意它與資料結構中的堆是兩回事,分配方式類似於連結串列。

注:1.Text, BSS, Data段在編譯時已經決定了程序將佔用多少VM         可以通過size,知道這些資訊:           2. 正常情況下,Linux程序不能對用來存放程式程式碼的記憶體區域執行寫操作,即程式程式碼是以只讀的方式載入到記憶體中,但它可以被多個程序安全的共享。 二  核心空間和使用者空間   Linux的虛擬地址空間範圍為0~4G,Linux核心將這4G位元組的空間分為兩部分,將最高的1G位元組(從虛擬地址0xC0000000到0xFFFFFFFF)供核心使用,稱為“核心空間”。而將較低的3G位元組(從虛擬地址0x00000000到0xBFFFFFFF)供各個程序使用,稱為“使用者空間。因為每個程序可以通過系統呼叫進入核心,因此,Linux核心由系統內的所有程序共享
。於是,從具體程序的角度來看,每個程序可以擁有4G位元組的虛擬空間。
    Linux使用兩級保護機制:0級供核心使用,3級供使用者程式使用,每個程序有各自的私有使用者空間(0~3G),這個空間對系統中的其他程序是不可見的,最高的1GB位元組虛擬核心空間則為所有程序以及核心所共享。
    核心空間中存放的是核心程式碼和資料,而程序的使用者空間中存放的是使用者程式的程式碼和資料。不管是核心空間還是使用者空間,它們都處於虛擬空間中。 雖然核心空間佔據了每個虛擬空間中的最高1GB位元組,但對映到實體記憶體卻總是從最低地址(0x00000000),另外,使用虛擬地址可以很好的保護核心空間被使用者空間破壞,虛擬地址到實體地址轉換過程有作業系統和CPU共同完成(作業系統為
CPU設定好頁表,CPU通過MMU單元進行地址轉換)。

        多工作業系統中的每一個程序都執行在一個屬於它自己的記憶體沙盒中,這個沙盒就是虛擬地址空間(virtual address space),在32位模式下,它總是一個4GB的記憶體地址塊。這些虛擬地址通過頁表(page table)對映到實體記憶體,頁表由作業系統維護並被處理器引用。每個程序都擁有一套屬於它自己的頁表。   程序記憶體空間分佈如下圖所示:                               通常32位Linux核心地址空間劃分0~3G為使用者空間,3~4G為核心空間      : 1.這裡是32位核心地址空間劃分,64位核心地址空間劃分是不同的           2.現代的作業系統都處於32位保護模式下。每個程序一般都能定址4G的物理空間。但是我們的實體記憶體一般都是幾百M,程序怎麼能獲得4G的物理空間呢?這就是使用了虛擬地址的好處,通常我們使用一種叫做虛擬記憶體的技術來實現,因為可以使用硬碟中的一部分來當作記憶體使用                                                          Linux系統對自身進行了劃分,一部分核心軟體獨立於普通應用程式,執行在較高的特權級別上,它們駐留在被保護的記憶體空間上,擁有訪問硬體裝置的所有許可權,Linux將此稱為核心空間。
        相對地,應用程式則是在“使用者空間”中執行。執行在使用者空間的應用程式只能看到允許它們使用的部分系統資源,並且不能使用某些特定的系統功能,也不能直接訪問核心空間和硬體裝置,以及其他一些具體的使用限制。
        將使用者空間和核心空間置於這種非對稱訪問機制下有很好的安全性,能有效抵禦惡意使用者的窺探,也能防止質量低劣的使用者程式的侵害,從而使系統執行得更穩定可靠。
核心空間在頁表中擁有較高的特權級(ring2或以下),因此只要使用者態的程式試圖訪問這些頁,就會導致一個頁錯誤(page fault)。在Linux中,核心空間是持續存在的,並且在所有程序中都對映到同樣的實體記憶體,核心程式碼和資料總是可定址的,隨時準備處理中斷和系統呼叫。與之相反,使用者模式地址空間的對映隨著程序切換的發生而不斷的變化,如下圖所示:

                                           

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

 三  程序記憶體佈局        Linux程序標準的記憶體段佈局,如下圖所示,地址空間中的各個條帶對應於不同的記憶體段(memory segment),如:堆、棧之類的。                                 
                                                  q               這些段只是簡單的虛擬記憶體地址空間範圍,與Intel處理器的段沒有任何關係。 幾乎每個程序的虛擬地址空間中各段的分佈都與上圖完全一致,這就給遠端發掘程式漏洞的人打開了方便之門。一個發掘過程往往需要引用絕對記憶體地址:棧地址,庫函式地址等。遠端攻擊者必須依賴地址空間分佈的一致性,來探索出這些地址。如果讓他們猜個正著,那麼有人就會被整了。因此,地址空間的隨機排布方式便逐漸流行起來,Linux通過對棧、記憶體對映段、堆的起始地址加上隨機的偏移量來打亂佈局。但不幸的是,32位地址空間相當緊湊,這給隨機化所留下的空間不大,削弱了這種技巧的效果。

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

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

     :動態棧增長是唯一一種訪問未對映記憶體區域而被允許的情形,其他任何對未對映記憶體區域的訪問都會觸發頁錯誤,從而導致段錯誤。一些被對映的區域是隻讀的,因此企圖寫這些區域也會導致段錯誤 記憶體對映段      在棧的下方是記憶體對映段核心將檔案的內容直接對映到記憶體。任何應用程式都可以通過Linux的mmap()系統呼叫或者Windows的CreateFileMapping()/MapViewOfFile()請求這種對映。記憶體對映是一種方便高效的檔案I/O方式,所以它被用來載入動態庫。建立一個不對應於任何檔案的匿名記憶體對映也是可能的,此方法用於存放程式的資料。在Linux中,如果你通過malloc()請求一大塊記憶體,C執行庫將會建立這樣一個匿名對映而不是使用堆記憶體。“大塊”意味著比MMAP_THRESHOLD還大,預設128KB,可以通過mallocp()調整。       與棧一樣,堆用於執行時記憶體分配;但不同的是,堆用於儲存那些生存期與函式呼叫無關的資料。大部分語言都提供了堆管理功能。在C語言中,堆分配的介面是malloc()函式。如果堆中有足夠的空間來滿足記憶體請求,它就可以被語言執行時庫處理而不需要核心參與,否則,堆會被擴大,通過brk()系統呼叫來分配請求所需的記憶體塊。堆管理是很複雜的,需要精細的演算法來應付我們程式中雜亂的分配模式,優化速度和記憶體使用效率。處理一個堆請求所需的時間會大幅度的變動。實時系統通過特殊目的分配器來解決這個問題。堆在分配過程中可能會變得零零碎碎,如下圖所示:                           
  一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收 。注意它與資料結構中的堆是兩回事,分配方式類似於連結串列。 BBS和資料段       在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語言程式例項分析如下所示:

	 #include<stdio.h>  
	 #include <malloc.h>  
	   
	 void print(char *,int);  
	 int main()  
	{  
	      char *s1 = "abcde";  //"abcde"作為字串常量儲存在常量區 s1、s2、s5擁有相同的地址
	      char *s2 = "abcde";  
	      char s3[] = "abcd";  
	      long int *s4[100];  
	      char *s5 = "abcde";  
	      int a = 5;  
	      int b =6;//a,b在棧上,&a>&b地址反向增長  
	   
	     printf("variables address in main function: s1=%p  s2=%p s3=%p s4=%p s5=%p a=%p b=%p \n",   
	             s1,s2,s3,s4,s5,&a,&b); 
	     printf("variables address in processcall:n");  
         print("ddddddddd",5);//引數入棧從右至左進行,p先進棧,str後進 &p>&str  
	     printf("main=%p print=%p \n",main,print);  
	     //列印程式碼段中主函式和子函式的地址,編譯時先編譯的地址低,後編譯的地址高main<print  
	 }  

	 void print(char *str,int p)  
	{  
	     char *s1 = "abcde";  //abcde在常量區,s1在棧上  
	     char *s2 = "abcde";  //abcde在常量區,s2在棧上 s2-s1=6可能等於0,編譯器優化了相同的常量,只在記憶體儲存一份  
	     //而&s1>&s2  
	     char s3[] = "abcdeee";//abcdeee在常量區,s3在棧上,陣列儲存的內容為abcdeee的一份拷貝  
	    long int *s4[100];  
	     char *s5 = "abcde";  
	     int a = 5;  
	     int b =6;  
	     int c;  
	     int d;           //a,b,c,d均在棧上,&a>&b>&c>&d地址反向增長  
	    char *q=str; 
	    int m=p;         
	    char *r=(char *)malloc(1);  
	    char *w=(char *)malloc(1) ;  // r<w 堆正向增長  
	  
	    printf("s1=%p s2=%p s3=%p s4=%p s5=%p a=%p b=%p c=%p d=%p str=%p q=%p p=%p m=%p r=%p w=%p \n",  
	            s1,s2,s3,s4,s5,&a,&b,&c,&d,&str,q,&p,&m,r,w); 
		/* 棧和堆是在程式執行時候動態分配的,區域性變數均在棧上分配。
		    棧是反向增長的,地址遞減;malloc等分配的記憶體空間在堆空間。堆是正向增長的,地址遞增。  
			r,w變數在棧上(則&r>&w),r,w所指內容在堆中(即r<w)。*/ 
	 }  
	 
附錄: 棧與堆的區別            
轉載:http://blog.csdn.net/zhangzhebjut/article/details/39060253

相關推薦

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

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

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

一 程序空間分佈概述     對於一個程序,其空間分佈如下圖所示:                                       程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。 初始化過的資料(Data):在程式執

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

                一 程序空間分佈概述    對於一個程序,其空間分佈如下圖所示:                                     程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。初始化過的資料(Data):在程式執行初已經對變數進行初始化的資

程序地址空間記憶體檔案對映

程序地址空間 每個程序都有自己的地址空間。對32位程序來說,由於32位指標可以表示從0x00000000到0xFFFFFFFF之間的任一值,地址空間的大小為4GB。對64位程序來說,由於64位指標可以表示從0x00000000'00000000到0xFFFFFFFF'FFF

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

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

程序地址空間佈局

建立一個程序時,作業系統會為該程序分配一個 4GB 大小的程序地址空間,本文具體講的是程序地址空間的各個部分: 棧: 存放程式臨時建立的區域性變數,也就是程式碼塊之內或者函式之內的變數,但不包括sta

Linux系統程式設計——淺談程序地址空間與虛擬儲存空間

早期的記憶體分配機制 在早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的實體記憶體地址。當計算機同時執行多個程式時,必須保證這些程式用到的記憶體總量要小於計算機實際實體記憶體的大小。 那當程式同時執行

深入理解 Linux 核心---程序地址空間

講述: 程序是怎樣看待動態記憶體的。 程序空間的基本組成。 缺頁異常處理程式在推遲給程序分配頁框中所起的作用。 核心怎樣建立和刪除程序的整個地址空間。 與程序的地址空間管理有關的 API 和系統呼叫。 程序的地址空間 程序的地址空間由允許程序使用的全部線性地址組成。 每個程序看到

Linux核心設計與實現》讀書筆記(十五)- 程序地址空間(kernel 2.6.32.60)

程序地址空間也就是每個程序所使用的記憶體,核心對程序地址空間的管理,也就是對使用者態程式的記憶體管理。 主要內容: 地址空間(mm_struct) 虛擬記憶體區域(VMA) 地址空間和頁表 1. 地址空間(mm_struct) 地址空間就是每個程序所能訪問的記憶體地址範圍。 這個地址

Linux程序地址空間的理解

對於Linux的虛擬記憶體的理解,這個例子算是一個很好的引導了,原文連結:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26683523&id=3201345 《Linux核心設計與實現》15

15---程序地址空間

什麼是程序地址空間? 系統中每個使用者空間程序所看到的記憶體 程序地址空間有什麼特點? 核心允許程序使用虛擬記憶體 系統中所有程序之間以虛擬方式共享記憶體 對一個程序而言,它好像可以訪問系統中所有的實體記憶體,它擁有的地址空間可以遠大於系統實體記憶體 每個程序擁有

淺談程序地址空間與虛擬儲存空間

早期的記憶體分配機制 在早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的實體記憶體地址。當計算機同時執行多個程式時,必

作業系統原理:程序地址空間

Linux程序虛擬儲存 先回憶一下ELF檔案的組織結構,可以看這篇文章:Linux 連結與ELF檔案。程式執行後進程地址空間佈局則和作業系統密切相關。在將應用程式載入到記憶體空間執行時,作業系統負責程式碼段與資料段的載入,並在記憶體中為這些段分配空間。Linu

Linux虛擬地址空間佈局

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

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

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

程序地址空間與虛擬儲存空間區別

在進入正題前先來談談作業系統記憶體管理機制的發展歷程,瞭解這些有利於我們更好的理解目前作業系統的記憶體管理機制。 一 早期的記憶體分配機制 在 早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的

程序地址空間 get_unmmapped_area()

程序地址空間 get_unmapped_area() 在向資料結構插入新的記憶體區域之前,核心必須確認虛擬地址空間中有足夠的空閒空間,可用於給定長度的區域。該工作由get_unmmaped_area()完成。 在分析get_unmmaped_area()之前

如何獲得當前執行模組在程序地址空間的位置

(w)WinMain的hInstanceExe引數實際值是一個記憶體基地址;系統將可執行檔案的映像載入到程序地址空間中的這個位置。例如,系統開啟可執行檔案,並將它載入到地址0x00400000,則(w)WinMain的hInstanceExe引數值為0x00400000.

程序地址空間分佈

轉載請註明出處:http://blog.csdn.net/wangxiaolong_china 對於一個程序,其空間分佈如下圖所示: C程式一般分為: 1.程式段:程式段為程式程式碼在記憶體中的對映.一個程式可以在記憶體中多有個副本. 2.初始化過的資料:

Windows運用AVL樹對程序地址空間的管理

32位Windows系統中,程序在使用者態可用的地址空間範圍是低2G(x64下是低8192G)。隨著程序不斷的申請和釋放記憶體,這個2G的地址空間,有的地址範圍是保留狀態(reserved),有的地址範圍是提交狀態(對映到了物理頁面,committed),有的地址範圍是空閒