深刻理解計算機系統之虛擬存儲器

http://blog.csdn.net/al_xin/article/details/38590931html

進程提供給應用程序的關鍵抽象:linux

 

  • 一個獨立的邏輯控制流,它提供一個假象,好像咱們的程序獨佔地使用處理器
  • 一個私有的地址空間,它提供一個假象,好像咱們的程序獨佔地使用存儲器系統.
 
 

虛擬存儲器算法

虛擬存儲器是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的完美交互,它爲每一個進程提供了一個大的、一致的和私有的地址空間。經過一個很清晰的機制,虛擬存儲器提供了三個重要的能力:數組

(1)它將主存當作是一個存儲在磁盤上的地址空間的高速緩存,在主存中只保存活動區域,並根據須要在磁盤和主存之間來回傳送數據,經過這種方式,它高效地使用了主存。緩存

(2)它爲每一個進程提供了一致的地址空間,從而簡化了存儲器管理。數據結構

(3)它保護了每一個進程的地址空間不被其餘進程破壞app

 

物理和虛擬尋址dom

物理尋址函數

計算機系統的主存被組織成一個由M個連續的字節大小的單元組成的數組。每字節都有一個惟一的物理地址(Physical Address,PA)。第一個字節的地址爲0,接下來的字節的地址爲1,再下一個爲2,依此類推。給定這種簡單的結構,CPU訪問存儲器的最天然的方式就是使用物理地址,咱們把這種方式稱爲物理尋址。工具

虛擬尋址

使用虛擬尋址時,CPU經過生成一個虛擬地址(Virtual Address,VA)來訪問主存,這個虛擬地址在被送到存儲器以前先轉換成適當的物理地址。將一個虛擬地址轉換爲物理地址的任務叫作地址翻譯(address translation)。就像異常處理同樣,地址翻譯須要CPU硬件和操做系統之間的緊密合做。CPU芯片上叫作存儲器管理單元(Memory Management Unit,MMU)的專用硬件,利用存放在主存中的查詢表來動態翻譯虛擬地址,該表的內容是由操做系統管理。

 

地址空間

地址空間(adress space)是一個非整數地址的有序集合:{0,1,2,...}

若是地址空間中的整數是連續的,那麼咱們說它是一個線性地址空間(linear address space)。在一個帶虛擬存儲器的系統中,CPU從一個有N = 2 ^ n個地址空間中生成虛擬地址,這個地址空間稱爲虛擬地址空間(virtual address space):{0,1,2,3,...,N-1}

一個地址空間的大小是由表示最大地址所須要的倍數來描述的。例如,一個包含N=2^n個地址的虛擬地址空間叫作一個n位地址空間。如今系統典型地支持32位或者64位虛擬地址空間是。

一個系統還有一個物理地址空間(physical addresss space),它與系統中物理存儲器的M字節相對應:{0,1,2,...M-1}

M不要求是2的冪,可是爲了簡化討論,咱們假設M = 2 ^ m。

地址空間的概念是很重要的,由於它清楚地區分了數據對象(字節)和它們的屬性(地址)。一旦認識到了這種區別,那麼咱們就能夠將其推廣,容許每一個數據對象有多個獨立的地址,其中每一個地址都選自一個不一樣的地址空間(不連續的意思嗎?)。這就是虛擬存儲器的基本思想。主存中每一個字節都有一個選自虛擬地址空間的虛擬地址和一個選自物理地址空間的物理地址。(這段沒怎麼看懂~~)

 

虛擬存儲器做爲緩存的工具

概念上而言,虛擬存儲器(VM)被組織爲一個由存放在磁盤上N個連續的字節大小的單元組成的數組。每一個字節都有一個惟一的虛擬地址,這個惟一的虛擬地址是做爲到數組的索引的。磁盤上的數組的內容被緩存在主存中。和存儲器層次結構中其餘緩存同樣,磁盤(較低層)上的數據被分割成塊,這些塊做爲磁盤和主存(較高層)之間的傳輸單元。VM系統經過將虛擬存儲器分割稱爲虛擬頁(Vitual Page,VP)的大小固定的塊來處理這個問題。每一個虛擬頁的大小爲P = 2 ^ n字節。相似地,物理存儲器被分割爲物理頁(Physical Page,PP),大小也爲P字節(物理頁也稱爲頁幀(page frame))。

在任意時刻,虛擬頁面的集合都分爲三個不相交的子集:

 

  • 未分配的:VM系統還未分配(或者建立)的頁。未分配的塊沒有任何數據和它們相關聯,所以也就不佔用任何磁盤空間。(沒有調用malloc或者mmap的)
  • 緩存的:當前緩存在物理存儲中的已分配頁。(已經調用malloc和mmap的,在程序中正在引用的)
  • 未緩存的:沒有緩存在物理存儲器中的已分配頁。(已經調用malloc和mmap的,在程序中尚未被引用的)

 

 

頁表

同任何緩存同樣,虛擬存儲器系統必須有某種方法來斷定一個虛擬頁是否存放在DRAM中的某個地方。若是是,系統還必須肯定這個虛擬頁存放在哪一個物理頁中。若是不命中,系統必須判斷這個虛擬頁存放在磁盤的哪一個位置,在物理存儲器中選擇一個犧牲頁,並將虛擬頁從磁盤拷貝到DRAM中,替換這個犧牲頁。

這些功能是由許多軟硬件聯合提供的,包括操做系統軟件,MMU(存儲器管理單元)中地址翻譯硬件和一個存放在物理存儲器中叫作頁表(page table)的數據結構,頁表將虛擬頁映射到物理頁。頁表就是一個頁表條目(Page Table Entry,PTE)的數組。

 

 

 

Linux虛擬存儲器系統

linux爲每一個進程維持了一個單獨的虛擬地址空間


 

內核虛擬存儲器包含內核中的代碼和數據結構。內核虛擬存儲器的某些區域被映射到全部進程共享的物理頁面。例如,每一個進程共享內核的代碼和全局數據結構

 

一、Linux虛擬存儲器區域(Windows下也有區域的概念)

Linux將虛擬存儲器組織成一些區域(也叫作段)的集合。一個區域(area)就是已經存在着的(已分配的)虛擬存儲器的連續片(chunk),這些頁是以某種方式相關聯的。例如,代碼段、數據段、堆、共享庫段,以及用戶棧都不一樣的區域。每一個存在的虛擬頁面保存在某個區域中,而不屬於某個區域的虛擬頁是不存在的,而且不能被進程引用。區域的概念很重要,由於它容許虛擬地址空間有間隙。內核不用記錄那些不存在的虛擬頁,而這樣的頁也不佔用存儲器。磁盤或者內核自己的任何額外資源

內核爲系統中的每一個進程維護一個單獨的任務結構(源代碼中的task_struct)。任務結構中的元素包含或者指向內核運行該進程所須要的全部信息(例如,PID,指向用戶棧的指針、可執行的目標文件的名字以及程序計數器)。

 

task_struct中的一個條目指向mm_struct,它描述了虛擬存儲器中的當前狀態。其中pgd指向第一級頁表(頁全局目錄)的基址,而mmap指向一個vm_area_struct(區域結構)的鏈表,其中每一個vm_area_structs都描述了當前虛擬地址空間的一個區域(area)。當內核運行這個進程時,它就將pgd存放在CR3控制寄存器中。

一個具體區域結構包含下面的字段:

 

  • vm_start:指向這個區域的起始處。
  • vm_end:指向這個區域的結束處。
  • vm_prot:描述這個區域的內包含的全部頁的讀寫許可權限。
  • vm_flags:描述這個區域內頁面是與其餘進程共享的,仍是這個進程私有的(還描述了其餘一些信息)。
  • vm_next:指向鏈表中下一個區域結構。
 
 
 
存儲器映射(Windows下也有相似的機制,名叫內存映射) 
Linux(以及其餘一些形式的Unix)經過將一個虛擬存儲器區域與一個磁盤上的對象(object)關聯起來,以初始化這個虛擬存儲器區域的內容,這個過程稱爲存儲器映射(memory mapping)。虛擬存儲器區域能夠映射到兩種類型的對象的一種:
(1) Unix文件上的普通文件:一個區域能夠映射到一個普通磁盤文件的連續部分,例如一個可執行目標文件。文件區(section)被分紅頁大小的片,每一片包含一個虛擬頁面的初始化內容。由於按需進行頁面高度,因此這些虛擬頁面沒有實際進行物理存儲器,直到CPU第一次引用到頁面(即發射一個虛擬地址,落在地址空間這個頁面的範圍以內)。若是區域文件區要大,那麼就用零來填充這個區域的餘下部分。
 
(2) 匿名文件:一個區域也能夠映射到一個匿名文件,匿名文件是由內核建立的,包含的全是二進制零。CPU第一次引用這樣一個區域內的虛擬頁面時,內核就在物理存儲器中找到一個合適的犧牲頁面,若是該頁面被修改過,就將這個頁面換出來,用二進制零覆蓋犧牲頁面並更新頁表,將這個頁面標記爲是駐留在存儲器中的。注意在磁盤和存儲器之間沒有實際的數據傳送。由於這個緣由,映射到匿名文件的區域中的頁面有時也叫作請求二進制零的頁(demand-zero page)。
不管在哪一種狀況下,一旦一個虛擬頁面被初始化了, 它就在一個由內核維護的專門的交換文件(swap file)之間換來換去。交換文件也叫作交換空間(swap space)或者交換區域(swap area)。須要意識到的很重要的一點, 在任什麼時候刻,交換空間都限制着當前運行着的進程可以分配的虛擬頁面的總數
 
再看共享對象
一個對象能夠被映射到虛擬存儲的一個區域,要麼做爲共享對象,要麼做爲私有對象。若是一個進程將一個共享對象映射到它的虛擬地址空間的一個區域內,那麼這個進程對這個區域的任何寫操做,對於那些也把這個共享對象映射到它們虛擬存儲器的其餘進程而言也是可見的。並且,這此變化也會反映在磁盤上的原始對象中。(IPC的一種方式)
另外一方面,對一個映射到私有對象的區域作的改變,對於其餘進程來講是不可見的,而且進程對這個區域所作的任何寫操做都不會反映在磁盤上的對象中。一個映射到共享對象的虛擬存儲器區域叫作共享區域。相似地,也有私有區域。
 

共享對象的關鍵點在於即便對象被映射到了多個共享區域,物理存儲器也只須要存放共享對象的一個拷貝。

一個共享對象(注意,物理頁面不必定是連續的。)


 

私有對象是使用一種叫作寫時拷貝(copy-on-write)的巧妙技術被映射到虛擬存儲器中的。對於每一個映射私有對象的進程,相應私有區域的頁表條目都被標記爲只讀,而且區域結構被標記爲私有的寫時拷貝。


 

再看fork函數

當fork函數被當前進程調用時,內核爲新進程建立各類數據結構,並分配給它一個惟一的PID。爲了給這個新進程建立虛擬存儲器,它建立了當前進程的mm_struct、區域結構和頁表的原樣拷貝。它將兩個進程中的每一個頁面都爲標記只讀,並將兩個進程中的每一個區域結構都標記爲私有的寫時拷貝。

當fork在新進程中返回時,新進程如今的虛擬存儲器恰好和調用fork時存在的虛擬存儲器相同。當這兩個進程中的任一個後來進行寫操做時,寫時拷貝機制就會建立新頁面,所以,也就爲每一個進程保持了私有地址空間的抽象概念。

 

再看execve函數

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

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

execve函數在當前進程中加載並運行包含在可執行目標文件a.out中的程序,用a.out程序有效地替代了當前程序。加載並運行a.out須要如下幾個步驟:

 

  • 刪除已存在的用戶區域。刪除當前進程虛擬地址用戶部分中的已存在的區域結構。
  • 映射私有區域。爲新程序的文本、數據、bss和棧區域建立新的區域結構。全部這些新的區域都是私有的、寫時拷貝的。文本和數據區域被映射爲a.out文件中的文本和數據區。bss區域是請求二進制零的,映射到匿名文件,其大小包含在a.out中。棧和堆區域也是請求二進制零的。
  • 映射共享區域。若是a.out程序與共享對象(或目標)連接,好比標準C庫libc.so,那麼這些對象都是動態連接到這個程序的,而後再映射到用戶虛擬地址空間中的共享區域內。
  • 設置程序計數器(PC)。execve作的最後一件事情就是設置當前進程上下文中的程序計數器,使之指向文本區域的入口點。
下一次調度這個進程時,它將從這個入口點開始執行。Linux將根據須要換入代碼和數據頁面。
 
 
使用mmap函數的用戶級存儲器映射
[cpp]  view plain copy
 
  1. #include<unistd.h>  
  2. #include<sys/mman.h>  
  3.   
  4.   
  5. void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset) ;  
  6.                 //返回:若成功時則爲指向映射區域的指針,若出錯則爲MAP_FAILED(-1)  

mmap函數要求內核建立一個新的虛擬存儲器區域是,最好是從地址start開始的一個區域,並將文件描述符fd指定的對象的一個連續的片(chunk)映射到這個新區域。連續的對象片大小爲length字節,從距文件開始處偏移量爲offset字節的地方開始。start地址僅僅是一個暗示,一般被定義爲NULL。










[cpp]  view plain copy
 
  1. munmap函數刪除虛擬存儲器的區域:  
  2. #include<unistd.h>  
  3. #include<sys/mman.h>  
  4.   
  5.   
  6. int munmap(void *start,size_t length);  
  7.     //返回:若成功則爲0,若出錯則爲-1  

一、須要額外的虛擬存儲器時,使用一種動態存儲器分配器(dynamic memory allocator)。一個動態存儲器分配器維護着一個進程的虛擬存儲器區域,稱爲堆(heap)。在大多數的unix系統中,堆是一個請求二進制0的區域;對於每一個進程,內核維護着一個變量brk,它指向堆的頂部。

wps_clip_image-14482

二、分配器將堆視爲一組不一樣大小的塊(block)的集合來維護。每一個塊就是一個連續的虛擬存儲器組塊(chunk),要麼是已分配的,要麼是未分配的。

1)顯式分配器(explicit allocator):如經過malloc,free或C++中經過new,delete來分配和釋放一個塊。

2)隱式分配器(implicit allocator):也叫作垃圾收集器(garbage collector)。自動釋放未使用的已分配的塊的過程叫作垃圾回收(garbage collection)。

三、malloc不初始化它返回的存儲器,calloc是一個基於malloc的包裝(wrapper)函數,它將分配的存儲器初始化爲0。想要改變一個之前已分配的塊的大小,可使用realloc函數。

四、分配器必須對齊塊,使得它們能夠保存任何類型的數據對象。在大多數系統中,以8字節邊界對齊。

不修改已分配的塊:分配器只能操做或者改變空閒塊。一旦被分配,就不容許修改或者移動它。

五、碎片(fragmentation)

有內部碎片(internal)和外部碎片(external)。

外部碎片:在一個已分配塊比有效載荷在時發生的。(如對齊要求,分配最小值限制等)

外部碎片:當空閒存儲器合計起來足夠知足一個分配請求,可是沒有一個單獨的空閒塊足夠大能夠來處理這個請求時發生的。

六、隱式空間鏈表

wps_clip_image-14485

wps_clip_image-15582

    放置分配的塊的策略有:首次適配(first fit),下一次適配(next fit),和最佳適配(best fit)。

若是空閒塊已經最大程度的合併,而仍然不能生成一個足夠大的塊,來知足要求的話,分配器就會向內核請求額外的堆存儲器,要麼是經過調用nmap,要麼是經過調用sbrk函數;分配器都會將額外的(增長的)存儲器轉化成一個大的空閒塊,將這個塊插入到空閒鏈表中,而後將被請求的塊放置在這個新的空閒塊中。

七、書中對分配器的設計舉了一個小例子,10.9.12節。

八、一種流行的減小分配時間的方法,稱爲分離存儲(segregated storage),維護多個空閒鏈表,其中每一個鏈表中的塊有大體相等的大小。

    關於「簡單分離存儲」、「分離適配」、「夥伴系統」等概念,10.9.14節進行了敘述。

 

垃圾回收

一、垃圾收集器將存儲器視爲一張有向可達圖(reachability graph)。

wps_clip_image-11717

二、Mark%Sweep垃圾收集器由標記(mark)階段和清除(sweep)階段組成。標記階段標記出根節點的全部可達的和已分配的後繼,然後面的清除階段釋放每一個被標記的已分配塊。典型地,塊頭部中空閒的低位中的一位來表示這個塊是否被標記了。

wps_clip_image-1930

    Note that the arrows in this example denote memory references, and not free list pointers.

三、在10.11中,講述了與存儲器相關的錯誤,值得一讀。

如:指針的算術運算是以它們指向的對象的大小爲單位來進行的。

相關文章
相關標籤/搜索