虛擬存儲系統是指什么 虛擬存儲( 四 )


內存映射提供了一種共享對象的機制,以避免浪費內存資源 。一個對象被映射到虛擬內存的一個區域,或者作為一個共享對象,或者作為一個私有對象 。
如果一個進程把一個共享對象映射到它的虛擬地址空之間的一個區域,那么這個進程對這個區域的任何寫操作對同樣把這個共享對象映射到它們的虛擬內存的其他進程也是可見的 。相反,對映射到私有對象的區域的任何寫操作對其他進程是不可見的 。映射到共享對象的虛擬內存區域稱為共享區域 。同樣,也有私人區域 。
為了節省內存,私有對象的生命周期與共享對象的生命周期基本相同(物理內存中只保存私有對象的一個副本),并采用寫時復制的技術來處理多個進程的寫沖突 。

只要沒有進程試圖寫入自己的私有區域,多個進程就可以繼續共享物理內存中私有對象的單個副本 。然而,每當進程試圖寫入私有區域中的頁面時,它將觸發保護異常 。在上圖中,進程B試圖寫入私有區域的頁面,這觸發了一個保護異常 。異常處理程序將在物理內存中創建這個頁面的新副本,更新PTE以指向這個新副本,然后恢復這個頁面的可寫權限 。
另一個典型的例子是fork()函數,它用于創建子進程 。當前進程調用fork()函數時,內核會為新進程創建各種必要的數據結構,并為其分配一個唯一的PID 。為了給新進程創建虛擬內存,它復制了當前進程的mm_struct、vm_area_struct和頁表的原始副本 。并將兩個進程的每個頁面標記為只讀,并將兩個進程的每個區域標記為私有區域(寫入時復制) 。
這樣,父進程和子進程的虛擬內存空是完全一致的,只有當這兩個進程中的任何一個在寫的時候,才能使用寫時復制來保證每個進程的虛擬地址空之間的私有抽象概念 。
存儲器分配雖然內存映射(mmap()函數)可以用來創建和刪除虛擬內存區域,以滿足運行時動態內存分配的問題 。但是,為了更好的可移植性和方便性,需要更高層次的抽象,即動態內存分配器 。
動態內存分配器維護進程的虛擬內存區域,也稱為“堆” 。內核還維護一個指向堆頂部的指針brk(break) 。動態內存分配器將堆視為連續虛擬內存塊的集合,每個塊有兩種狀態,已分配和空空閑 。分配的塊是為應用程序顯式保留的,而空空閑塊可用于分配,其空空閑狀態為,直到被應用程序顯式分配 。分配的塊要么由應用程序顯式釋放,要么由垃圾收集器釋放 。

本文只解釋動態內存分配的一些概念,動態內存分配的實現不在本文討論范圍之內 。感興趣的話可以參考dlmalloc的源代碼,這是Doug Lea(寫Java發契約的那個)實現的一個設計巧妙的內存分配器,源代碼中有很多注釋 。
內存碎片空堆之間利用率低的主要原因是一種叫做碎片化的現象 。當有未使用的內存,但該內存無法滿足分配請求時,就會出現碎片 。有兩種類型的碎片:
內部碎片:當分配的塊大于有效負載時發生 。例如,程序請求一個5字塊(這里我們不關心字的大小,假設一個字是4個字節,堆的大小是16個字,保證邊界雙字對齊),內存分配器為了保證空 free block是雙字邊界對齊,不得不分配一個6字塊(具體實現中對齊的規定可能略有不同,但對齊肯定會存在) 。在這個例子中,分配的塊是6個字,有效載荷是5個字,內部片段是分配的塊減去有效載荷,即1個字 。
外部碎片:當空空閑內存足以滿足分配請求,但沒有任何單個空空閑塊大到足以處理該請求時,就會發生這種情況 。外部碎片難以量化且不可預測,所以分發商通常會嘗試通過啟發式策略維護少量的大空空閑塊,而不是維護大量的小空空閑塊 。分配器還會根據策略和分配請求的匹配劃分空空閑塊和合并空空閑塊(它們必須是相鄰的) 。
空自由鏈表分配器被組織成一個連續的已分配塊和空空閑塊序列,稱為空空閑鏈表 ??兆杂涉湵矸譃殡[式空自由鏈表和顯式空自由鏈表 。
隱式空空閑鏈表是一個單向鏈表,每個空空閑塊只通過頭中的size字段隱式連接 。
顯式空空閑鏈表是指空空閑塊被組織成某種形式的顯式數據結構(為了更高效地合并和劃分空空閑塊) 。比如把堆組織成一個雙向空空閑鏈表,每個空空閑塊包含一個前任節點的指針和一個后繼節點的指針 。
有幾種策略可以找到空空閑塊:
第一次適配:從頭搜索空空閑鏈表,選擇最先遇到的合適的空空閑塊 。它的優點是傾向于在鏈表后面保留大的空空閑塊,缺點是傾向于在鏈表前面附近留下碎片 。

推薦閱讀