linux進程空間地址划分
以Linux 64位系統為例。理論上,64bit內存地址可用空間為0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF(16位十六進制數),這是個相當龐大的空間,Linux實際上只用了其中一小部分(256T)。
Linux64位操作系統僅使用低47位,高17位做擴展(只能是全0或全1)。所以,實際用到的地址為空間為0x0000000000000000 ~ 0x00007FFFFFFFFFFF(user space)和0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF(kernel space),其余的都是unused space
user space 也就是用戶區由以下幾部分組成:代碼段,數據段,BSS段,heap,stack
在精細一些:一個進程在內存中的分配如下
代碼段(text):
通常用於存放程序執行代碼(即CPU執行的機器指令)
數據段(Data):
存放程序中已初始化且初值不為0的全局變量和靜態局部變量。數據段屬於靜態內存分配(靜態存儲區),可讀可寫。
BSS段:
包括:
- 未初始化的全局變量和靜態局部變量
- 初始值為0的全局變量和靜態局部變量(依賴於編譯器實現)
- 未定義且初值不為0的符號(該初值即common block的大小)
堆(heap):
- 堆用於存放進程運行時動態分配的內存段,可動態擴張或縮減。
- 堆中內容是匿名的,不能按名字直接訪問,只能通過指針間接訪問。當進程調用malloc(C)/new(C++)等函數分配內存時,新分配的內存動態添加到堆上(擴張);當調用free(C)/delete(C++)等函數釋放內存時,被釋放的內存從堆中剔除(縮減) 。
- 堆的末端由break指針標識,當堆管理器需要更多內存時,可通過系統調用brk()和sbrk()來移動break指針以擴張堆,一般由系統自動調用。
- 可見,堆容易造成內存碎片;由於沒有專門的系統支持,效率很低;由於可能引發用戶態和內核態切換,內存申請的代價更為昂貴
- 操作系統為堆維護一個記錄空閑內存地址的鏈表。當系統收到程序的內存分配申請時,會遍歷該鏈表尋找第一個空間大於所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,並將該結點空間分配給程序。若無足夠大小的空間(可能由於內存碎片太多),有可能調用系統功能去增加程序數據段的內存空間,以便有機會分到足夠大小的內存,然后進行返回
內存映射段(mmap):
-
此處,內核將硬盤文件的內容直接映射到內存, 任何應用程序都可通過Linux的mmap()系統調用請求這種映射。內存映射是一種方便高效的文件I/O方式,。普通文件被映射到進程地址空間后,進程可以像訪問普通內存一樣對文件進行訪問,不必再調用read()/write()等操作。 因而被用於裝載動態共享庫。用戶也可創建匿名內存映射,該映射沒有對應的文件, 可用於存放程序數據
-
從進程地址空間的布局可以看到,在有共享庫的情況下,留給堆的可用空間還有兩處:一處是從.bss段到0x40000000,約不到1GB的空間;另一處是從共享庫到棧之間的空間,約不到2GB。這兩塊空間大小取決於棧、共享庫的大小和數量。這樣來看,是否應用程序可申請的最大堆空間只有2GB?事實上,這與Linux內核版本有關。在上面給出的進程地址空間經典布局圖中,共享庫的裝載地址為0x40000000,這實際上是Linux kernel 2.6版本之前的情況了,在2.6版本里,共享庫的裝載地址已經被挪到靠近棧的位置,即位於0xBFxxxxxx附近,因此,此時的堆范圍就不會被共享庫分割成2個“碎片”,故kernel 2.6的32位Linux系統中,malloc申請的最大內存理論值在2.9GB左右。
-
mmap/munmap是常用的一個系統調用,使用場景是:分配內存、讀寫大文件、連接動態庫文件、多進程間共享內存。
-
malloc申請內存的大小超過128K就會使用mmap分配內存,在堆和棧之間找一塊空閑內存分配(對應獨立內存,而且初始化為0)
- mmap通過將磁盤文件映射到用戶空間
當進程讀文件時,發生缺頁中斷,因為很明顯 當前文件還不在內存當中,要去磁盤進行訪問,給虛擬內存分配對應的物理內存,在通過磁盤調頁操作將磁盤數據讀到物理內存上,實現了用戶空間數據的讀取,整個過程只有一次內存拷貝。普通文件被映射到進程地址空間后,進程可以像訪問普通內存一樣對文件進行訪問,不必再調用read()/write()等操作
- 用於進程間大數據量通信:(進程之間通過共享內存進行通信的實例):
兩個進程映射同一個文件,在兩個進程中,同一個文件區域映射的虛擬地址空間不同。當一個進程先操作文件時,先通過缺頁獲取物理內存,進而通過磁盤文件調頁操作將文件數據讀入內存。
另一個進程訪問文件的時候,發現沒有物理頁面映射到虛擬內存,通過fs的缺頁處理查找cache區是否有讀入磁盤文件,有的話建立映射關系(都指向同一塊內存),這樣兩個進程通過共享內存就可以進行通信。
-
私有/共享、文件/匿名映射組合
(1)私有文件映射:多個進程使用同樣的物理頁面進行初始化,但是各個進程對內存文件的修改不會共享,也不會反映到物理文件中。
比如對linux .so動態庫文件就采用這種方式映射到各個進程虛擬地址空間中。
(2)共享文件映射:多個進程通過虛擬內存技術共享同樣物理內存,對內存文件的修改會反應到實際物理內存中,也是進程間通信的一種。
(3)私有匿名映射:mmap會創建一個新的映射,各個進程不共享,主要用於分配內存(malloc方式分配的內存)(malloc分配大內存會調用mmap)。
(4)共享匿名映射:這種機制在進行fork時不會采用寫時復制,父子進程完全共享同樣的物理內存頁,也就是父子進程通信,父進程或者子進程malloc了一大塊空間,對於父子進程都是可以訪問的,共享的。
-
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);
mmap系統調用接口函數
棧(stack)
- 由編譯器自動分配釋放
- 為函數內部聲明的非靜態局部變量(C語言中稱“自動變量”)提供存儲空間
- 記錄函數調用過程相關的維護性信息,稱為棧幀(Stack Frame)或過程活動記錄(Procedure Activation Record)
- 棧的大小在運行時由內核動態調整。
- Linux中ulimit -s命令可查看和設置堆棧最大值,當程序使用的堆棧超過該值時, 發生棧溢出(Stack Overflow),程序收到一個段錯誤(Segmentation Fault)。
說了這么多 舉個實際的例子看看吧:
//main.cpp
int a = 0; 全局初始化區
char *p1; 全局未初始化區
main()
{
int a = 4; 棧,4也是存在棧上
char s[] = "abc"; 棧 "abc"也是存在棧上
char *p2; 棧
char *p3 = "123456"; 123456\0在常量區(是在Data段上),p3在棧上。
static int c =0; 全局(靜態)初始化為0,就是放在BSS段
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
malloc分配得來得10和20字節的區域就在堆區。因為屬於動態申請分配內存空間
strcpy(p1, "123456"); 123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。
}
一定注意:數組s儲存的內容是在運行的時候賦值的,但是指針p3指向的常量區中的字符串內容是編譯時就賦值的。
malloc函數使用時虛擬內存的分配情況
下圖描述的是虛擬內存的分配情況
情況一、malloc小於128k的內存,使用brk分配內存,將_edata往高地址推(只分配虛擬空間,不對應物理內存(因此沒有初始化),第一次讀/寫數據時,引起內核缺頁中斷,內核才分配對應的物理內存,然后虛擬地址空間建立映射關系(動態裝入的例子)),如下圖:(虛擬空間)
在分配A空間的時候。_edata+30K只是完成虛擬地址的分配,A這塊內存現在還是沒有物理頁與之對應的,等到進程第一次讀寫A這塊內存的時候,發生缺頁中斷,內核才分配A這塊內存對應的物理頁。 如果用malloc分配了A這塊內容,然后從來不訪問它,那么,A對應的物理頁是不會被分配的。
brk分配的內存需要等到高地址內存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的,這就是內存碎片產生的原因,A對應的虛擬內存和物理內存都不會釋放(有B的情況下),但是要是malloc在申請一個一樣大小的內存區域,malloc可能就把B這塊內存返回回去了)
情況二:malloc大於128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閑內存分配(對應獨立內存,而且初始化為0),比如C就是,mmap分配的內存可以單獨釋放,直接free就釋放了。
當最高地址空間的空閑內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操作(trim):比如釋放了B D那么兩塊內存緊縮
malloc與mmap分配虛擬內存對應的物理內存的方式
當進程訪問這些沒有建立映射關系的虛擬內存時,邏輯地址轉化為物理地址,發現當前頁並不在內存當中。處理器自動觸發一個缺頁異常。
虛擬(邏輯)地址轉化為物理地址是一個比較復雜的過程,這個以后寫一篇關於虛擬存儲器的博客介紹~
參考:
https://www.cnblogs.com/arnoldlu/p/9367253.html
https://www.cnblogs.com/arnoldlu/p/8329283.html