深刻理解計算機系統 第九章 虛擬存儲器

虛擬存儲器

定義:html

對主存的抽象機制,是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的完美交互。

功能: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個不相交的子集。數據結構

一、[未分配的] VM系統還未分配(或者建立)的頁。未分配的塊沒有任何數據與之相關聯。不佔用磁盤空間

二、[緩存的] 當前緩存在物理存儲器的已分配頁。    

三、[未緩存的] 沒有緩存在物理頁面存儲器中的已分配頁。

圖片描述

9.3.2 頁表

clipboard.png

9.4 虛擬存儲器做爲存儲器管理的工具

操做系統爲每一個進程提供一個獨立的頁表,VM簡化了連接加載,代碼和數據共享,以及應用程序的存儲器分配app

clipboard.png

1.簡化連接函數

獨立的空間地址意味着每一個進程的存儲器映像使用相同的格式。文本節老是從0x08048000(32位)處
 或0x400000(64位)處開始。而後是數據,bss節,棧。一致性極大簡化了連接器的設計和實現。

2.簡化加載工具

在ELF可執行文件中.text和.data節是連續的。要把這些節加載到一個新建立的進程中,linux加載器. 
 分配虛擬頁的一個連續的片,從地址0x08048000處(32bit)開始, 或者從0x400000(64bit), 
 [把這些虛擬頁標記爲無效,將頁表條目指向目標文件中適當的位置,加載器從不實際拷貝任何數據從磁盤到存儲器.]

3.簡化共享spa

4.簡化存儲器分配操作系統

malloc在堆空間分配一個適當數字(例如k)個連續的虛擬存儲器頁面,而且將他們映射到物理存儲器中任意
  位置的k個任意(不必定連續)的物理頁面。

9.5 虛擬存儲器做爲存儲器保護的工具

9.7.2 Linux虛擬存儲器系統

Linux爲每一個進程維持一個單獨的虛擬地址空間:內核虛擬存儲器和進程虛擬存儲器。
clipboard.png

內核虛擬存儲器包含內核中的代碼和數據。

一、內核虛擬存儲器的某些區域被映射到全部進程共享的物理頁面.如:內核代碼,全局數據結構。

二、Linux將一組連續的虛擬頁面(大小等同於系統DRAM總量)映射到相應的一組物理頁面。[直接映射,不使用頁表]

三、內核虛擬存儲器包含每一個進程不相同的數據。頁表,內核在進程上下文中時使用的棧等。

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時,觸發缺頁。這個異常致使控制轉移到缺頁處理程序,執行以下步驟:

一、虛擬地址A是合法的嗎? A在某個區域結構定義的區域內嗎?
  解決方法: 缺頁處理程序搜索區域結構鏈表。把A和每一個區域的vm_start和vm_end作比較。
若是不合法,觸發段錯誤。 
    
二、試圖訪問的存儲器是否合法? 即:是否有讀,寫,執行這個頁面的權限?
   若是不合法,觸發保護異常,終止進程。一切正常的話
   
三、若不存在以上狀況,則選擇犧牲頁,替換,從新執行指令

9.8 存儲器映射

定義
Linux 經過將一個虛擬存儲器區域與一個磁盤上的對象關聯,以初始化這個虛擬存儲器區域的內容。
虛擬存儲器區域能夠映射到如下兩種類型文件:

一、Unix文件系統中的普通文件:一個區域能夠映射到一個普通磁盤文件的連續部分。
例如,一個可執行文件。文件區(section)被分紅頁大小的片,每一片包含一個虛擬頁面的初始化內容。
僅僅是初始化,虛擬頁面此時還並未進入物理存儲器,直到CPU第一次引用這個頁面。

二、匿名文件
匿名文件由內核建立,包含的全是二進制零。CPU第一次引用這樣區域(匿名文件)的虛擬頁面時,
將存儲器中犧牲頁面所有用二進制零覆蓋。並將虛擬頁面標記爲駐留在存儲器中。
注意在磁盤和存儲器之間並無實際的數據傳送。又叫請求二進制零的頁(demand-zero page)。

注意: 一個虛擬頁被初始化了,它就在一個有內核維護的專門的交換文件(交換空間)之間切換。
在任什麼時候刻,交換空間都限制着當前運行着的進程可以分配的虛擬頁面的總數

9.8.1 再看共享對象

一個對象能夠被映像到虛擬存儲器的一個區域,要麼做爲共享對象,要麼做爲私有對象.

clipboard.png

私有對象的寫時拷貝

clipboard.png

9.8.2 再看fork函數

當fork函數被當前進程調用時:

一、內核爲新進程建立內核數據結構,並分配給它惟一一個PID。
二、爲了給新進程建立虛擬存儲器[建立頁目錄]。 
三、建立了當前進程的mm_struct,區域結構和頁表的原樣拷貝。
四、將兩個進程的每一個頁面都標記爲[只讀]。並給兩個區域進程的每一個區域結構都標記爲[私有的寫時拷貝]。
注意:[沒有對物理存儲器進行拷貝,利用的是私有對象的寫時拷貝技術。]

9.8.3 再看execve函數

假設運行在當前的進程中的程序執行了以下的調用:

execve("a.out",NULL,NULL);

execve函數在當前進程加載並執行目標文件a.out中的程序,用a.out代替當前程序。

加載並運行須要如下幾個步驟。

一、刪除已存在的用戶區域:刪除當前進程虛擬地址的用戶部分中已存在的區域結構。

二、映射私有區域:爲新程序的文本,數據,bss和棧區域建立新的區域結構。全部新的區域結構都是私有的,寫時拷貝的。
   文本和數據區域被映射到a.out文件中的文件和數據區。bss區域是請求二進制零,映射到匿名文件。

三、映射共享區域

四、設置程序計數器

五、execve最後一件事設置PC指向文本區域的入口點。

clipboard.png

9.8.4 使用mmap函數的用戶級存儲器映射

Unix進程可使用mmap函數來建立新的虛擬存儲器區域,並將對象映射到這些區域中

#include <unistd.h>
#include <sys/mman.h>

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);

返回:若成功時則爲指向映射區域的指正,若出錯則爲MAP_FAILED(-1).

munmap函數刪除虛擬存儲器的區域

#include <unistd.h>
#include <sys/mman.h>

void *munmap(void *start,size_t length);

返回:若成功則爲0,若出錯則爲-1

練習題 9.5代碼

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include "csapp.h" // 本身寫的
#include <errno.h>
#include <fcntl.h>
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)。

系統之間細節不一樣,可是不失通用型。

一、堆是一個請求二進制零的區域。
二、緊接着未初始化的bss區域,並向上生長(向更高的地址)。
三、對於每一個進程,內核維護一個變量[brk],指向堆頂,當堆空間不足時,利用sbrk函數修改該變量。
四、分配器將堆視爲一組不一樣大小的塊block的集合來維護。

每一個塊就是一個連續的虛擬存儲器片,要麼是已分配,要麼是空閒。

9.11 C程序中常見的與存儲器有關的錯誤

相關文章
相關標籤/搜索