一、虛擬記憶體
分段機制:即分成程式碼片段,資料區段,堆棧段。每個記憶體段都與一個特權級相關聯,即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);均在這裡分配。
分段機制:即分成程式碼片段,資料區段,堆棧段。每個記憶體段都與一個特權級相關聯,即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);均在這裡分配。