linux進程地址空間划分


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM