第九章 虛擬存儲器

                《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程序中常見的與存儲器有關的錯誤:

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






發佈了28 篇原創文章 · 獲贊 5 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章