linux动态分配全局置换,深入理解计算机系统 第九章 虚拟存储器
虛擬存儲器
定義:
對主存的抽象機制,是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的完美交互。
功能:
1. 將主存看成是一個存儲在磁盤上的地址空間的高速緩存,在內存中只保存活動區域,并根據需要在磁盤和內存之間來回傳送數據。
2. 為進程提供了一致的地址空間,從而簡化了存儲器管理。
3. 保護了每個進程的地址空間不被其他進程所破壞。
9.1 物理和虛擬地址
CPU通過生成一個虛擬地址(Virtual address,VA)來訪問主存。將虛擬地址轉換為物理地址叫做地址翻譯(address translation)。地址翻譯也需要CPU硬件和操作系統之間的緊密結合。
CPU芯片上有叫做存儲器管理單元(Memory Management Unit,MMU)的專用硬件。利用存儲在主存中的查詢表來動態翻譯虛擬地址。 查詢表由操作系統管理。
9.2 地址空間
9.3 虛擬存儲器作為緩存的工具
概念上而言,虛擬存儲器(VM)被組織為一個存放在磁盤上的N個連續字節大小的單元組成的數組。每個字節都有一個唯一的虛擬地址,這個唯一的虛擬地址作為到數組的索引。磁盤上數組的內容被緩存到主存中。VM系統通過將虛擬存儲器分割為稱為虛擬頁的大小固定的塊來處理磁盤和主存信息交互問題。
任何時候,虛擬頁的集合都被分為3個不相交的子集。
1、[未分配的] VM系統還未分配(或者創建)的頁。未分配的塊沒有任何數據與之相關聯。不占用磁盤空間
2、[緩存的] 當前緩存在物理存儲器的已分配頁。
3、[未緩存的] 沒有緩存在物理頁面存儲器中的已分配頁。
9.3.2 頁表
9.4 虛擬存儲器作為存儲器管理的工具
操作系統為每個進程提供一個獨立的頁表,VM簡化了鏈接和加載,代碼和數據共享,以及應用程序的存儲器分配。
1.簡化鏈接
獨立的空間地址意味著每個進程的存儲器映像使用相同的格式。文本節總是從0x08048000(32位)處
或0x400000(64位)處開始。然后是數據,bss節,棧。一致性極大簡化了鏈接器的設計和實現。
2.簡化加載
在ELF可執行文件中.text和.data節是連續的。要把這些節加載到一個新創建的進程中,linux加載器.
分配虛擬頁的一個連續的片,從地址0x08048000處(32bit)開始, 或者從0x400000(64bit),
[把這些虛擬頁標記為無效,將頁表條目指向目標文件中適當的位置,加載器從不實際拷貝任何數據從磁盤到存儲器.]
3.簡化共享
4.簡化存儲器分配
malloc在堆空間分配一個適當數字(例如k)個連續的虛擬存儲器頁面,并且將他們映射到物理存儲器中任意
位置的k個任意(不一定連續)的物理頁面。
9.5 虛擬存儲器作為存儲器保護的工具
9.7.2 Linux虛擬存儲器系統
Linux為每個進程維持一個單獨的虛擬地址空間:內核虛擬存儲器和進程虛擬存儲器。
內核虛擬存儲器包含內核中的代碼和數據。
1、內核虛擬存儲器的某些區域被映射到所有進程共享的物理頁面.如:內核代碼,全局數據結構。
2、Linux將一組連續的虛擬頁面(大小等同于系統DRAM總量)映射到相應的一組物理頁面。[直接映射,不使用頁表]
3、內核虛擬存儲器包含每個進程不相同的數據。頁表,內核在進程上下文中時使用的棧等。
1.Linux 虛擬存儲器區域
Linux將虛擬存儲器組織成一些區域(也叫做段)的集合。一個區域
就是已經存在著的(已分配的) 虛擬存儲器的連續片,這些片/頁已某種形式相關聯。
如:代碼段,數據段,堆,共享庫段,用戶棧。
所有存在的虛擬頁都保存在某個區域,允許虛擬地址空間有間隙。
虛擬存儲器區域的內核數據結構
task_struct
mm_struct: 描述了虛擬存儲器的當前狀態。
pgd: 指向第一級頁表的基址。當進程運行時,內核將pgd存放在CR3控制寄存器
mmap: 指向vm_area_structs的鏈表
vm_area_structs描述了當前虛擬地址空間的一個區域(area).
vm_start:指向這個區域的起始處。
vm_end:指向這個區域的結束處。
vm_port:描述這個區域內包含的所有頁的讀寫許可權限。
vm_flags:描述這個區域頁面是否與其他進程共享,還是私有等
vm_next: 指向鏈表的下一個區域。
2.Linux缺頁異常處理
MMU在試圖翻譯虛擬地址A時,觸發缺頁。這個異常導致控制轉移到缺頁處理程序,執行如下步驟:
1、虛擬地址A是合法的嗎? A在某個區域結構定義的區域內嗎?
解決方法: 缺頁處理程序搜索區域結構鏈表。把A和每個區域的vm_start和vm_end做比較。
如果不合法,觸發段錯誤。
2、試圖訪問的存儲器是否合法? 即:是否有讀,寫,執行這個頁面的權限?
如果不合法,觸發保護異常,終止進程。一切正常的話
3、若不存在以上情況,則選擇犧牲頁,替換,重新執行指令
9.8 存儲器映射
定義:
Linux 通過將一個虛擬存儲器區域與一個磁盤上的對象關聯,以初始化這個虛擬存儲器區域的內容。
虛擬存儲器區域可以映射到以下兩種類型文件:
1、Unix文件系統中的普通文件:一個區域可以映射到一個普通磁盤文件的連續部分。
例如,一個可執行文件。文件區(section)被分成頁大小的片,每一片包含一個虛擬頁面的初始化內容。
僅僅是初始化,虛擬頁面此時還并未進入物理存儲器,直到CPU第一次引用這個頁面。
2、匿名文件
匿名文件由內核創建,包含的全是二進制零。CPU第一次引用這樣區域(匿名文件)的虛擬頁面時,
將存儲器中犧牲頁面全部用二進制零覆蓋。并將虛擬頁面標記為駐留在存儲器中。
注意在磁盤和存儲器之間并沒有實際的數據傳送。又叫請求二進制零的頁(demand-zero page)。
注意: 一個虛擬頁被初始化了,它就在一個有內核維護的專門的交換文件(交換空間)之間切換。
在任何時刻,交換空間都限制著當前運行著的進程能夠分配的虛擬頁面的總數。
9.8.1 再看共享對象
一個對象可以被映像到虛擬存儲器的一個區域,要么作為共享對象,要么作為私有對象.
私有對象的寫時拷貝
9.8.2 再看fork函數
當fork函數被當前進程調用時:
1、內核為新進程創建內核數據結構,并分配給它唯一一個PID。
2、為了給新進程創建虛擬存儲器[創建頁目錄]。
3、創建了當前進程的mm_struct,區域結構和頁表的原樣拷貝。
4、將兩個進程的每個頁面都標記為[只讀]。并給兩個區域進程的每個區域結構都標記為[私有的寫時拷貝]。
注意:[沒有對物理存儲器進行拷貝,利用的是私有對象的寫時拷貝技術。]
9.8.3 再看execve函數
假設運行在當前的進程中的程序執行了如下的調用:
execve("a.out",NULL,NULL);
execve函數在當前進程加載并執行目標文件a.out中的程序,用a.out代替當前程序。
加載并運行需要以下幾個步驟。
1、刪除已存在的用戶區域:刪除當前進程虛擬地址的用戶部分中已存在的區域結構。
2、映射私有區域:為新程序的文本,數據,bss和棧區域創建新的區域結構。所有新的區域結構都是私有的,寫時拷貝的。
文本和數據區域被映射到a.out文件中的文件和數據區。bss區域是請求二進制零,映射到匿名文件。
3、映射共享區域
4、設置程序計數器
5、execve最后一件事設置PC指向文本區域的入口點。
9.8.4 使用mmap函數的用戶級存儲器映射
Unix進程可以使用mmap函數來創建新的虛擬存儲器區域,并將對象映射到這些區域中
#include
#include
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
返回:若成功時則為指向映射區域的指正,若出錯則為MAP_FAILED(-1).
munmap函數刪除虛擬存儲器的區域
#include
#include
void *munmap(void *start,size_t length);
返回:若成功則為0,若出錯則為-1
練習題 9.5代碼
#include
#include
#include
#include
#include "csapp.h" // 自己寫的
#include
#include
int main(int argc, char* argv[]){
if (argc < 2)
csapp_error("lack of filename.");
int fd;
if ((fd = open(argv[1], O_RDWR)) < 0)
csapp_error2("open error",errno);
off_t length;
if ((length = lseek(fd, 0, SEEK_END)) < 0)
csapp_error2("lseek error.",errno);
char* bufptr = (char*)mmap(NULL,length,PROT_READ,MAP_PRIVATE,fd,0);
if (!bufptr)
csapp_error2("mmap error.", errno);
fprintf(stdout,"%s",bufptr);
exit(0);
}
mmap為什么比傳統的讀寫速度要快
mmap : 將文件內容直接映射到進程的地址空間,通過對這段內存的讀寫,來達到對文件的讀寫目的;
read,write : 每次調用都需要從用戶態到內核態的切換,且數據需要從用戶態拷貝到內核態,然后再寫入磁盤,增加了中間步驟
mmap的缺點 : 不能改變文件長度,無法寫入多余的字符。
9.9 動態存儲器分配
malloc通過調用sbrk函數來實現內存的分配,且在在sbrk之上加了一層對所分配的內存的管理,
而sbrk以及brk是實現從虛擬內存到內存的映射的
Linux內存分配小結--malloc、brk、mmap
動態存儲器分配器維護著一個進程的虛擬存儲區域,稱為堆(heap)。
系統之間細節不同,但是不失通用型。
1、堆是一個請求二進制零的區域。
2、緊接著未初始化的bss區域,并向上生長(向更高的地址)。
3、對于每個進程,內核維護一個變量[brk],指向堆頂,當堆空間不足時,利用sbrk函數修改該變量。
4、分配器將堆視為一組不同大小的塊block的集合來維護。
每個塊就是一個連續的虛擬存儲器片,要么是已分配,要么是空閑。
9.11 C程序中常見的與存儲器有關的錯誤
總結
以上是生活随笔為你收集整理的linux动态分配全局置换,深入理解计算机系统 第九章 虚拟存储器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cgo linux arm,Golang
- 下一篇: linux sock结构体,struct