一步一圖帶你深入理解 Linux 虛擬內存管理( 四 )


除此之外 , 我們的程序在運行過程中還需要依賴動態鏈接庫 , 這些動態鏈接庫以 .so 文件的形式存放在磁盤中 , 比如 C 程序中的 glibc,里邊對系統調用進行了封裝 。glibc 庫里提供的用于動態申請堆內存的 malloc 函數就是對系統調用 sbrk 和 mmap 的封裝 。這些動態鏈接庫也有自己的對應的代碼段,數據段,BSS 段,也需要一起被加載進內存中 。
還有用于內存文件映射的系統調用 mmap,會將文件與內存進行映射,那么映射的這塊內存(虛擬內存)也需要在虛擬地址空間中有一塊區域存儲 。
這些動態鏈接庫中的代碼段,數據段,BSS 段,以及通過 mmap 系統調用映射的共享內存區,在虛擬內存空間的存儲區域叫做文件映射與匿名映射區 。

一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
最后我們在程序運行的時候總該要調用各種函數吧,那么調用函數過程中使用到的局部變量和函數參數也需要一塊內存區域來保存 。這一塊區域在虛擬內存空間中叫做棧 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
現在進程的虛擬內存空間所包含的主要區域 , 筆者就為大家介紹完了,我們看到內核根據進程運行的過程中所需要不同種類的數據而為其開辟了對應的地址空間 。分別為:
  • 用于存放進程程序二進制文件中的機器指令的代碼段
  • 用于存放程序二進制文件中定義的全局變量和靜態變量的數據段和 BSS 段 。
  • 用于在程序運行過程中動態申請內存的堆 。
  • 用于存放動態鏈接庫以及內存映射區域的文件映射與匿名映射區 。
  • 用于存放函數調用過程中的局部變量和函數參數的棧 。
以上就是我們通過一個程序在運行過程中所需要的數據所規劃出的虛擬內存空間的分布 , 這些只是一個大概的規劃,那么在真實的 Linux 系統中,進程的虛擬內存空間的具體規劃又是如何的呢?我們接著往下看~~
4. Linux 進程虛擬內存空間在上小節中我們介紹了進程虛擬內存空間中各個內存區域的一個大概分布,在此基礎之上,本小節筆者就帶大家分別從 32 位 和 64 位機器上看下在 Linux 系統中進程虛擬內存空間的真實分布情況 。
4.1 32 位機器上進程虛擬內存空間分布在 32 位機器上,指針的尋址范圍為 2^32,所能表達的虛擬內存空間為 4 GB 。所以在 32 位機器上進程的虛擬內存地址范圍為:0x0000 0000 - 0xFFFF FFFF 。
其中用戶態虛擬內存空間為 3 GB,虛擬內存地址范圍為:0x0000 0000 - 0xC000 000。
內核態虛擬內存空間為 1 GB,虛擬內存地址范圍為:0xC000 000 - 0xFFFF FFFF 。
一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
但是用戶態虛擬內存空間中的代碼段并不是從 0x0000 0000 地址開始的,而是從 0x0804 8000 地址開始 。
0x0000 0000 到 0x0804 8000 這段虛擬內存地址是一段不可訪問的保留區,因為在大多數操作系統中,數值比較小的地址通常被認為不是一個合法的地址 , 這塊小地址是不允許訪問的 。比如在 C 語言中我們通常會將一些無效的指針設置為 NULL,指向這塊不允許訪問的地址 。
保留區的上邊就是代碼段和數據段,它們是從程序的二進制文件中直接加載進內存中的,BSS 段中的數據也存在于二進制文件中,因為內核知道這些數據是沒有初值的,所以在二進制文件中只會記錄 BSS 段的大??,栽傆载进脑熸时粠V梢歡?0 填充的內存空間 。
緊挨著 BSS 段的上邊就是我們經常使用到的堆空間,從圖中的紅色箭頭我們可以知道在堆空間中地址的增長方向是從低地址到高地址增長 。
內核中使用 start_brk 標識堆的起始位置,brk 標識堆當前的結束位置 。當堆申請新的內存空間時 , 只需要將 brk 指針增加對應的大?。?回收地址時減少對應的大小即可 。比如當我們通過 malloc 向內核申請很小的一塊內存時(128K 之內),就是通過改變 brk 位置實現的 。
堆空間的上邊是一段待分配區域,用于擴展堆空間的使用 。接下來就來到了文件映射與匿名映射區域 。進程運行時所依賴的動態鏈接庫中的代碼段 , 數據段,BSS 段就加載在這里 。還有我們調用 mmap 映射出來的一段虛擬內存空間也保存在這個區域 。注意:在文件映射與匿名映射區的地址增長方向是從高地址向低地址增長 。
接下來用戶態虛擬內存空間的最后一塊區域就是棧空間了,在這里會保存函數運行過程所需要的局部變量以及函數參數等函數調用信息 。??臻g中的地址增長方向是從高地址向低地址增長 。每次進程申請新的棧地址時,其地址值是在減少的 。

推薦閱讀