第9章 虛擬存儲器

  • 虛擬存儲器是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的完美交互,它爲每個進程提供一個大的、一致的私有的地址空間;
    • 主存是磁盤的高速緩存、主存中只保留活動區域、並且根據需要,在磁盤和主存之間來回傳送數據;
    • 每個進程提供一致的地址空間,簡化了存儲器的管理;
    • 保護每個進程的地址空間,保護不被破壞;

9.1 物理和虛擬尋址

  • 內存中每個字節都有一個物理地址
  • CPU利用物理地址進行訪問,是物理尋址;
  • CPU利用虛擬地址,在地址被傳送到存儲器之前,先進行地址翻譯(存儲器管理單元(MMU)和系統的合作),在訪問物理主存;

9.2 地址空間

  • 地址空間:非負整數地址的有序結合;整數是連續的,我們稱爲 線型地址空間
  • 虛擬地址空間:這是CPU生成的地址空間;
  • 物理地址空間:對應於物理存儲器的M個字節;
  • 物理地址可以對應多個虛擬地址(如共享內存);

9.3 虛擬存儲器作爲緩存工具

  • 虛擬存儲器:被組織爲一個由存放在磁盤上N個連續字節大小的數組;
  • 虛擬頁:將虛擬存儲器分割爲虛擬頁,以此爲單位用來與物理內存交換;
    • 未分配的:系統還未分配的頁,在頁表中體現爲,既不指向物理存儲器,也不指向虛擬頁
    • 緩存的:當前虛擬頁已被加載到物理內存中,頁表體現爲指向物理內存;
    • 未緩存的:當前虛擬頁未被加載到物理內存中,頁表體現爲指向虛擬頁(磁盤);
  • 物理頁:物理內存被分割爲物理頁;
  • DRAM總是使用寫回,而不是直寫;

9.3.2 頁表

  • 頁表將虛擬頁映射到物理頁,頁表常駐在內存中;
  • 每次地址翻譯時,都將讀取頁表,這需要系統、內存管理單元中的地址翻譯硬件和頁表的共同作用;
  • 系統負責維護頁表,以數據頁的相互傳送;
  • 頁表是一個條目的數組,虛擬頁頁表中都有一個條目,具有固定的偏移;
    這裏寫圖片描述

9.3.3 頁命中

當CPU通過虛擬地址翻譯後,對應的虛擬頁已緩存到內存中時,稱爲頁命中;

9.3.4 缺頁

  • DMA緩存不命中稱爲缺頁;
  • 當CPU的虛擬地址訪問頁表發現虛擬頁並未緩存時(有效爲未標記),觸發缺頁異常;
  • 缺頁異常調用缺頁異常處理程序;程序會選擇一個犧牲頁,如果犧牲頁發生過修改,就寫回;並修改犧牲頁的條目,表明此虛擬頁不在存儲器中;
  • 將需要緩存的虛擬頁加載到存儲器中,跟新對應條目,表明此虛擬頁已緩存;之後重新啓動導致缺頁的指令;
    這裏寫圖片描述

9.3.5 分配頁面

在磁盤上創建空間,並更新對應頁表目,指向新創建的虛擬頁(未被緩存);

9.3.6 頁的局部性

  • 局部性保證了代碼往往在較小的活動頁面集合上工作,這個集合叫做工作集;
  • 當工作集的大小超過物理存儲器時,會產生顛簸,頻繁的發生頁面交換;

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

  • 操作系統爲每個進程提供一個獨立的頁表,因此每個進程擁有獨立的地址空間
  • 按需調度頁面和獨立的虛擬地址空間
    • 簡化鏈接:獨立的地址空間允許每個進程的存儲器映像使用相同的基本格式 ;這樣簡化了鏈接器的設計;可執行文件獨立於最終的物理地址;
    • 簡化加載:加載時只需要將條目指向目標文件的對應位置;在實際中,加載器從不拷貝任何數據到存儲器,而是按需交換數據頁;
    • 簡化共享:只需要將頁表指向相同的物理內存頁;
    • 簡化存儲器分配:當用戶程序需要堆內存時,系統分配K個連續虛擬存儲器頁面,但由於頁表的存在,實際的物理存儲器中並不存在;

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

一個用戶進程不能:

  • 修改其只讀文本段
  • 內核代碼和數據結構
  • 其他進程的存儲器
  • 共享的物理頁面,除非顯式允許(共享內存)

    解決辦法,在頁表中添加標誌位:

9.6 地址翻譯

地址翻譯是N元素的虛擬地址空間和M元素的物理地址空間之間的映射:
這裏寫圖片描述

虛擬地址-》物理地址的大致翻譯原理:
這裏寫圖片描述

  • 虛擬頁偏移量(VPO)和物理頁偏移量(PPO)是相同的,因爲虛擬頁和物理頁大小相同,可以直接映射;

物理地址翻譯過程:
這裏寫圖片描述

9.6.1 結合高速緩存和虛擬存儲器

  • 當存在SRAM和虛擬存儲器時,大多數系統使用物理地址訪問SRAM(SRAM相當於主存,因此也可以採用虛擬地址的方式,但大多數系統沒有采用);
  • 高速緩存無需處理保護的問題,因爲這是在地址翻譯的過程中解決的;
    這裏寫圖片描述

9.6.2 利用TLB加速地址翻譯

  • 在MMU中包括了一個關於PTE的小緩存,翻譯後備緩衝器(Translation Lookaside Buffer,TLB);
  • TLB每一行都保存着一個PTE條目;
  • 訪問TLB的標記和索引都是從虛擬地址中的VPN提取的;
    這裏寫圖片描述
  • 應爲所有的操作都是在MMU中進行的,速度非常快;
    這裏寫圖片描述

9.6.3 多級頁表

-當使用一級頁表時,對於32位的虛擬地址,頁表就佔就有4MB的空間,64位佔有跟多;
- 使用多級頁表可以解決這樣的問題;
這裏寫圖片描述
- 一級頁表條目指向二級頁表,二級頁表條目指向虛擬存儲器;
- 當一級頁表有些條目爲空時,二級頁表不需要存在,大大節省了空間
- 只有一個頁表總是存儲在存儲器中,系統可以在需要時創建,調入,調出二級頁表;只有經常使用的二級頁表存在在主存中;

#### 9.7.2 Linux虛擬存儲器系統

  • Linux爲每個進程維護了一個單獨的虛擬地址空間
  • Linux也將一組連續的虛擬頁面映射到一組連續的物理頁面,大小爲DRAM,方便系統訪問任何位置;(因爲用戶空間中沒有映射所有的物理頁,所以不能通過用戶空間訪問所有物理頁)

    完整的虛擬地址空間:
    這裏寫圖片描述

    1. Linux將虛擬存儲器區域
      • Linux將虛擬存儲器組織成一些區域,也叫
      • 一個區域或者段,就是已經分配的虛擬存儲器的連續片;
      • 每個存在的虛擬頁都是在某個區域中的,不屬於某個區域的虛擬頁是不存在的;
      • 區域的存在,使得虛擬地址空間存在間隙,內核也不用記錄不存在的虛擬頁;(頁表記錄)
        這裏寫圖片描述
    2. Linux缺頁處理
      當MMU試圖翻譯一個虛擬地址時,觸發缺頁異常,處理程序會執行下面的步驟:
      • 虛擬地址是否合法,地址是否在某個區域中;
      • 訪問是否合法,即是否有權限讀、寫或者執行的權限;
      • 到此處就是合法的地址和訪問權限,異常是由於缺頁造成的;交換頁之後,再次執行這個虛擬地址的翻譯
        這裏寫圖片描述

9.8 存儲器映射

  • 將虛擬存儲器區域與磁盤上的一個對象關聯起來,以初始化這個虛擬存儲器區域的內容,此稱爲存儲器映射
  • 虛擬存儲器可以映射兩種文件:
    • 普通文件:將文件分成頁大小的片,將頁表指向這些片,但不緩存這些片,採用按需調度的策略,直到CPU第一次引用這個頁面;如果區域要比文件區大,就用零填充;
    • 匿名文件:匿名文件由內核創建,包含的全是二進制零;過程是:CPU在物理存儲器找一個合適的犧牲頁面(修改過則寫回),用零覆蓋,更新頁表,將此頁標記爲緩存在存儲器中;磁盤和存儲器沒有數據交換;

9.8.1 在看共享對象

  • 一個對象可以被映射到虛擬存儲器的一個區域,要麼作爲一個共享對象,要麼私有對象;
  • 當作爲共享對象時,任何修改對其他進程是可見的,也會反映到磁盤的文件上;
  • 映射到私有區域的對象,修改對其他進程是不可見的,也不會影響磁盤上的文件;
    這裏寫圖片描述
  • 私有對象使用了一種寫時拷貝技術
    • 寫時拷貝只會拷貝要修改的頁,使頁表條目指向新拷貝的物理頁
      這裏寫圖片描述

9.8.2 再看fork函數

  • 當調用fork時,內核爲新進程創建各種數據結構,並分配一個唯一的PID;
  • 給新進程創建虛擬存儲器,複製當前進程的頁表,mm_struct和區域結構;並將兩個進程的頁面都標記爲只讀,區域結構標記爲寫時拷貝;

9.8.3 再看execve函數

利用execve函數加載程序有以下幾步:

  • 刪除已經存在的用戶區域結構:刪除當前進程中用戶空間中的區域結構;
  • 映射私有區域:爲新程序的文本、數據、bss和棧區創建新的區域結構;所有的這些區域都是私有的,寫時拷貝的;
  • 映射共享區域:將共享庫動態鏈接到此程序,然後映射到用戶虛擬地址空間的共享區域;
  • 設置程序計數器: 設置當前進程的上下文程序計數器指向文本區域的入口點;
    這裏寫圖片描述

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

mmap函數創建新的虛擬存儲器區域,並將對象映射到這些區域中;
這裏寫圖片描述

9.9 動態存儲器分配

  • 動態存儲器分配器維護虛擬存儲器中的堆區域;
  • 對於每一個進程,內核維護着一個變量brk,指向堆的頂部
  • 分配器將堆視爲一組不同大小的塊(就是malloc分配的對齊的空間),而每個塊就是連續的虛擬存儲器片,要麼已分配,要麼空閒;
  • 分配時,都要求顯式執行;
  • 已分配的塊的釋放方式:
    • 顯式分配器
      • 要求引用顯式的釋放已分配的塊
      • 例如malloc&free new&delete
    • 隱式分配器
      • 當分配器檢測到不再使用的已分配的塊時,就釋放這個塊;

9.9.1 malloc和free函數

這裏寫圖片描述

  • 返回塊大小至少爲申請的大小,可能爲了能夠保存各種數據類型,保證內存對齊
  • malloc從初始化分配的塊,可以使用calloc函數,此函數將塊初始化爲0;
  • 改變已分配塊的小小,realloc函數;
  • 可以使用mmap和mumap函數顯式分配和釋放堆存儲器
  • 也可以使用sbrk函數:

這裏寫圖片描述

  • 通過將內核的brk指針增加incr來擴展或者收縮;

  • 程序通過調用free來釋放以分配的塊:
    這裏寫圖片描述

    • ptr必須指向一個從malloc、calloc、realloc獲得的已分配塊的起始位置;

9.9.2 爲何要使用動態存儲器分配

  • 程序經常在運行時才知道某些數據結構的實際地址;

9.9.3 分配器的要求和目標

  • 處理任意的請求序列
    • 一個應用可以有任意的分配請求和分配請求序列
    • 每個釋放請求必須對應一個通過分配請求的塊;
  • 立即響應請求
    • 分配器必須立即響應請求,不可爲提高性能而重新排列或者緩衝請求
  • 只使用堆
    • 任何非標量數據結構都必須都必須保存在堆中;
  • 對齊塊
    • 對齊塊,保證可以存儲任何類型的數據對象;
  • 不修改已經分配的塊

    • 分配器只能操作已分配的塊,一旦塊被分配,就不可以移動或者修改他;

    9.9.4 碎片

    雖然有未使用的存儲器,但不能用來滿足分配請求,這些存儲空間被稱爲碎片
    碎片的兩種形式:

  • 內部碎片
    • 發生在分配的塊比有效載荷大時發生的;比如爲了對齊;
  • 外部碎片

    • 當空閒塊合併時滿足請求,但沒有一個單獨的塊滿足請求;

    9.9.6隱式空閒鏈表

    一個簡單塊的結構:
    這裏寫圖片描述

    • 由於雙字對齊,所以地址前3位總是0,用來存放標記;

    隱式空閒鏈表結構:
    這裏寫圖片描述

    • 空閒塊是通過頭部中的大小字段隱含的鏈接着,所以稱爲隱式空閒鏈表;
    • 最後爲一個結束塊,大小0,已分配;

    9.9.7 放置已分配的塊

    當請求一個k字節的塊時,結果是由放置策略決定的,常用的放置策略:

    • 首次適配
    • 下一次適配:每一次搜索都是從上一次結束的地方開始
    • 最佳適配:大小最合適

    9.9.8 分割空閒塊

    • 匹配較好時,只會產生內部碎片;
    • 匹配較差時,一部分變成分配塊,一部分變成空閒塊;

    9.9.9 獲取額外的堆存儲器

    當存儲器不能找到合適的塊時:

    • 合併相鄰的空閒塊
    • 當還不滿足時,分配器調用sbrk函數,申請額外的堆存儲器,插入鏈表中;

    9.9.10 合併空閒塊

    • 假碎片:有兩個相鄰的空閒塊,單獨時不能滿足分配請求,合併則可以,則這兩個空閒塊形成假空閒塊
      這裏寫圖片描述
  • 合併策略:

    • 立即合併:釋放塊時,合併相鄰的塊;
    • 推遲合併:推遲到某個時刻,例如沒有合適的空閒塊時;

    9.9.11 帶邊界標記的合併

    這裏寫圖片描述

    • 在塊的尾部添加一個標記,那麼釋放的當前塊就可以用常數時間去查詢前面一個塊是否空閒,而鏈表又需要重新遍歷到當前塊的前一個塊;

    9.9.13 顯式空閒鏈表

    顯式空閒鏈表顯式的將指針記錄在空閒塊中:
    這裏寫圖片描述

  • 使用雙向鏈表而不是隱式空閒鏈表,使得空閒塊的尋找時間從總塊數的線型時間,下降到空閒塊的線性時間;
  • 顯式鏈表的缺點是空閒塊的空間必須足夠大,用以存放
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章