一、虛擬內存
先來看一張圖(來自《Linux內核完全剖析》),如下:
分段機制:即分成代碼段,數據段,堆棧段。每個內存段都與一個特權級相關聯,即0~3,0具有最高特權級(內核),3則是最低特權級(用戶),每當程序試圖訪問(權限又分為可讀、可寫和可執行)一個段時,當前特權級CPL就會與段的特權級進行比較,以確定是否有權限訪問。每個特權級都有自己的程序棧,當程序從一個特權級切換到另一個特權級上執行時,堆棧段也隨之改換到新級別的堆棧中。
段選擇符:每個段都有一個段選擇符。段選擇符指明段的大小、訪問權限和段的特權級、段類型以及段的第一個字節在線性地址空間中的位置(稱為段的基地址)。
虛擬地址:虛擬地址的偏移量部分加上段的基地址上就可以定位段中某個字節的位置,即形成線性地址空間中的地址。
分頁機制:當使用分頁機制時,每個段被劃分成頁面(通常每頁在4KB大小),頁面會被存儲於物理內存或硬盤上。如果禁用分頁機制,那麼線性地址空間就是物理地址空間。
當程序試圖訪問線性地址空間上的一個地址位置時,發生以下操作:
if(數據在物理內存中) { 虛擬地址轉換成物理地址 讀數據 } else { if(數據在磁盤中) { if(物理內存還有空閒) { 把數據從磁盤中讀到物理內存 虛擬地址轉換成物理地址 讀數據 } else { 把物理內存中某頁的數據存入磁盤 把要讀的數據從磁盤讀到該頁的物理內存中 虛擬地址轉換成物理地址 讀數據 } } else { 報錯 } }
其中MMU負責虛擬地址到物理地址的轉換工作,分段和分頁操作都使用駐留在內存中的段表和頁表來指定他們各自的交換信息。如果用戶程序想要訪問一個虛擬地址,經MMU檢查無權訪問(特權級),MMU產生一個異常,CPU從用戶模式切換到特權模式,跳轉到內核代碼中執行異常服務程序,內核把這個異常解釋為段錯誤,把引發異常的進程終止掉。
二、linux進程地址空間
由前面可得知,進程有4G的尋址空間,其中第一部分為“用戶空間”,用來映射其整個進程空間(0x0000 0000-0xBFFF FFFF)即3G字節的虛擬地址;第二部分為“系統空間”,用來映射(0xC000 0000-0xFFFF FFFF)1G字節的虛擬地址。如下圖
將其更加詳細地展示如下:
環境變量:類似linux下的PATH,HOME等的環境變量,子進程會繼承父進程的環境變量。
命令行參數:類似ls -l 中-l 就是命令行參數,而ls 就是可執行程序。
棧:就是堆棧,程序運行時需要在這裡做數據運算,存儲臨時數據,開辟函數棧等。在Linux下,棧是高地址往低地址增長的。
對於函數棧來說,函數運行完畢就釋放內存,舉例遞歸來說,一直開辟向下函數棧,然後由下往上收復,所以遞歸太多層的話很可能造成棧溢出。
局部變量(不包含靜態變量);局部可讀變量(const)都分配在棧上。
共享庫和mmap內存映射區:比如很多程序都會用到的printf,函數共享庫 printf.o 固定在某個物理內存位置上,讓許多進程映射共享。mmap是個系統函數,可以把磁盤文件的一部分直接映射到內存,這樣文件中的位置直接就有對應的內存地址,對文件的讀寫可以直接用指針來做而不需要read/write函數。
堆:即malloc申請的內存,使用free釋放,如果沒有主動釋放,在進程運行結束時也會被釋放。
Text Segment: 可執行程序(二進制)(.text);全局初始化只讀變量(const)(.rodata);字符串常量(.rodata);均在這裡分配。
Data Segment: 全局變量(初始化的在.data,未初始化的在.bss);靜態變量(全局和局部)(初始化的在.data,未初始化的在.bss);全局未初始化只讀變量(.bss);均在這裡分配。