1. 程式人生 > >第九章 虛擬儲存器

第九章 虛擬儲存器

                《1》虛擬儲存器提供了三個重要的能力:

                          1.它將主存看成是一個儲存在磁碟上的地址空間的快取記憶體,在主存中只儲存活動區域,並根據需要在磁碟和主存之間來回傳送資料,通過這種方式,它高效的使用主存。

                         2. 它為每個程序提供了一致的地址空間,從而簡化了儲存器管理。

                         3.它保護了每個程序的地址空間不被其他程序破壞。它成功的一個主要原因就是因為它是沉默地,自動地工作的,不需要應用程式設計師的任何干涉。

                《2》使用虛擬地址定址,CPU通過生成一個虛擬地址來訪問主存,這個虛擬地址在被送到儲存器之前先轉換成適當的實體地址。將一個虛擬地址轉換為實體地址的任務叫做地址翻譯。CPU晶片上叫做儲存器管理單元(MMU)的專用硬體,利用存放在主存中的查詢表來動態翻譯虛擬地址,該表的內容是由作業系統管理的。

                《3》虛擬儲存器被組織為一個由存放在磁碟上的N個連續的位元組大小的單元組成的陣列。每位元組都有一個唯一的虛擬地址,這個唯一的虛擬地址是作為到陣列的索引的。磁碟上陣列的內容被快取在主存中。和儲存器層次結構中其他快取一樣,磁碟(較低層)上的資料被分割成塊,這些塊作為磁碟和主存(較高層)之間的傳輸單元。VM系統通過將虛擬儲存器分割成虛擬頁(Virtual Page , VP)的大小固定的塊來處理這個問題。每個虛擬頁的大小為P=2^p 位元組。類似地, 物理儲存器被分割成物理頁(Physical Page , PP), 大小也為P位元組(物理頁也稱頁幀(page frame))。在任意時刻,虛擬頁面的集合都分為三個不相交的子集:未分配的

:VM系統還未分配(或者建立)的頁。未分配的塊沒有任何資料和它們相關聯,因此也就不佔用任何磁碟空間。快取的:當前快取在物理儲存器中的已分配頁。未快取的:沒有快取在物理儲存器中已分配頁。

                《4》頁表就是一個頁表條目(Page Table Entry , PTE)的陣列。虛擬地址空間中的每個頁但在頁表中一個固定偏移量處都有一個PTE。為了我們的目的,我們假設每個PTE是由一個有效位和一個n位地址欄位組成的。有效位表明了該虛擬頁當前是否被快取在DRAM中。如果設定了有效位,那麼地址欄位就表示DRAM中相應的物理頁的起始位置,這個物理頁中快取了該虛擬頁。如果沒有設定有效位,那麼一個空地址表示這個虛擬頁還未被分配。否則,這個地址就指向該虛擬頁在磁碟上的起始位置。

                                                                                                    

到目前為止,我們都假設有一個單獨的頁表,將一個虛擬地址空間對映到實體地址空間。實際上,作業系統為每個程序提供了一個獨立的頁表,因而也就是一個獨立的虛擬地址空間。

虛擬地址偏移量VPO與物理頁面偏移量PPO是相等的。

                 《5》Linux將虛擬儲存器組織成一些區域(也叫做段)的集合。一個區域(area)就是已經存在著的(已分配的)虛擬儲存器的連續片(chunk),這些頁是以某種方式相關聯的。

  一個Linux程序的虛擬儲存器:

                                                                                

Linux是這樣組織虛擬儲存器的:


task_struct 中的一個條目指向mm_struct,它描述了虛擬儲存器的當前狀態。我們感興趣的兩個欄位是pgd和mmap,其中pgd指向第一級頁表(頁全域性目錄)的基址,而mmap指向一個vm_area_structs(區域結構)的連結串列,其中每個vm_area_structs都描述了當前虛擬地址空間的一個區域。具體區域的結構包含下面的欄位:

vm_start :指向這個區域的起始處。

vm_end: 指向這個區域的結束處。

vm_port : 描述這個區域內包含的所有頁的讀寫許可許可權。

vm_flags:描述這個區域內的頁面時與其他程序共享的,還是這個程序私有的(還描述了其他的一些資訊)。

vm_next:指向連結串列中的下一個區域結構。

                   《6》Linux(以及其他一些形式的Unix)通過將一個虛擬儲存器區域與一個磁碟上的物件(object)關聯起來。以初始化這個虛擬儲存器的內容,這個過程稱為儲存器對映(memory mapping)。一個物件可以被對映到它的儲存器的一個區域,要麼作為共享物件,要麼作為私有物件。如果一個程序將一個共享物件對映到它的虛擬地址空間的一個區域內,那麼這個程序對這個區域的任何寫操作,對於那些也把這個共享物件對映到它們虛擬儲存器的其他程序而言也是可見的。而且,這些變化也會反映在硬碟上的原始物件中。另一方面,對一個對映到私有物件的區域做的改變,對於其他程序來說是不可見的,並且程序對這個區域所做的任何寫操作都不會反映在磁碟上的物件中。

                    《7》私有物件是使用一種叫做寫時拷貝(copy-on-write)的巧妙技術被對映到虛擬儲存器中的。一個私有物件開始生命週期的方式基本上與共享物件的一樣,在物理儲存器中只儲存有私有物件的一份拷貝。如果兩個程序將一個私有物件對映到它們虛擬儲存器的不同區域,但是共享這個物件同一個物理拷貝,對每個對映私有物件的程序相應私有區域的頁表條目都被標記為只讀,並且區域結構被標記為私有寫時拷貝。只要沒有程序試圖寫自己的私有區域,它們都可以繼續共享物理儲存器中物件的一個單獨拷貝。

一個私有的寫時拷貝物件示例如下:


當只要有一個程序試圖寫私有區域的某個頁面,那麼這個寫操作就會觸發一個保護故障,當故障處理程式注意到保護異常是由於程序試圖寫私有的寫時拷貝區域中的一個頁面而引起的,它就會在物理儲存器中建立這個頁面的一個新拷貝,更新頁表條目指向這個新的拷貝,然後恢復這個頁面的可寫許可權。

                     《8》當fork函式被呼叫的時候,核心為新程序建立各種資料結構,並分配給它一個唯一的PID。為了給這個新程序建立虛擬儲存器,它建立了當前程序的mm_struct,區域結構和頁表的原樣拷貝。它將兩個程序中的每個頁面都標記為只讀,並將兩個程序中的每個區域結構都標記為私有寫時拷貝。

                     《9》動態儲存器分配器維護著一個程序的虛擬儲存器區域,稱為堆(heap)

                                                                                                                   

分配器有兩種基本風格:顯示分配器,要求應用顯示地釋放任何已分配的塊。 C程式通過呼叫malloc函式來分配一個塊,並通過呼叫free函式來釋放一個快。C++的new和delete操作符與C中的malloc和free相當。隱式分配器:要求分配器檢測一個已分配塊何時不再被程式所使用,那麼就釋放這個塊。隱式分配器也叫做垃圾收集器。

                    《10》malloc不初始化它返回的儲存器。想要已初始化的動態儲存器的應用程式可以使用calloc, calloc是一個基於malloc的瘦包裝函式,它將分配的儲存器初始化為零。想要改變一個以前已經分配塊的大小,可使用realloc函式。

                    《11》可以通過sbrk函式來擴充套件和收縮堆:

            #include<unistd.h>

     void *sbrk(intptr_t incr);   sbrk函式通過將核心的brk指標增加incr來擴容和收縮堆。如果成功,它就返回brk的舊值,否則返回-1,並將errno設定為ENOMEM。如果incr為零,那麼sbrk就返回brk的當前值。用一個負的incr來呼叫sbrk是合法的,而且很巧妙,因為返回值(brk的舊值)指向距離新堆頂向上abs(incr)位元組處。

                    《12》造成堆利用率很低的主要原因是一種稱為碎片的現象,當雖然有未使用的儲存器但不能用來滿足分配請求時,就會發生這種現象。有兩種形式的碎片:內部碎片和外部碎片。內部碎片是在一個已分配塊比有效載荷大時發生的。外部碎片是當空閒儲存器合計起來足夠滿足一個分配請求,但是沒有一個單獨的空閒塊足夠大可以用來處理這個請求發生時發生的。一個簡單的碓塊格式:


                  《13》C程式中常見的與儲存器有關的錯誤:

             間接引用壞指標, 讀未初始化的儲存器, 允許棧緩衝區溢位, 假設指標和它們指向的物件是相同大小的, 造成錯位錯誤, 引用指標,而不是它所指向的物件, 引用不存在的變數, 引用空閒塊中的資料, 引起儲存器洩漏。