操作系統物理內存管理:連續和非連續

物理內存管理:連續內存分配

地址空間定義

  • 物理地址空間:硬件支持的地址空間
    • 起始地址0,直到 MAXsys
  • 邏輯地址空間:在 CPU 運行的進程看到的地址
    • 起始地址0,直到 MAXprog

地址生成時機和限制

  • 編譯時
    • 假設起始地址已知
    • 如果起始地址改變,必須重新編譯
  • 加載時
    • 如編譯時起始位置未知,編譯器需生成可重定位的代碼(relocatable code)
    • 加載時,生成絕對地址
  • 執行時
    • 執行時代碼可移動
    • 需地址轉換(映射)硬件支持

地址生成過程

  • CPU
    • ALU:需要邏輯地址的內存內容
    • MMU:進行邏輯地址和物理地址的轉換
    • CPU 控制邏輯:給總線發送物理地址請求
  • 內存
    • 發送物理地址的內容給 CPU
    • 或接受 CPU 數據到物理地址
  • 操作系統
    • 建立邏輯地址 LA 和物理地址 PA 的映射

連續內存分配和內存碎片

  • 連續內存分配
    • 給進程分配一塊不小於指定大小的連續的物理內存區域
  • 內存碎片
    • 空閒內存不能被利用
  • 外部碎片
    • 分配單元之間的未被使用內存
  • 內部碎片
    • 分配單元內部的未被使用內存
    • 取決於分配單元大小是否要取整

連續內存分配:動態分區分配

  • 動態分區分配
    • 當程序被加載執行時,分配一個進程指定大小可變的分區(塊、內存塊)
    • 分區的地址是連續的
  • 操作系統需要維護的數據結構
    • 所有進程的已分配分區
    • 空閒分區(Empty-blocks)
  • 動態分區分配策略
    • 最先匹配(First-fit)
    • 最佳匹配(Best-fit)
    • 最差匹配(Worst-fit)

最先匹配(First Fit Allocation)策略

  • 思路:分配 n 個字節,使用功能第一個可用的空間比 n 大的空閒塊
  • 原理 & 實現
    • 空閒分區列表按地址順序排序
    • 分配過程中,搜索一個合適的分區
    • 釋放分區時,檢查是都可與臨近的空閒分區合併
  • 優點
    • 簡單
    • 在高地址空間有大塊的空閒分區
  • 缺點
    • 外部碎片
    • 分配大塊時較慢

最佳匹配(Best Fit Allocation)策略

  • 思路:分配 n 字節分區時,查找並使用不小於 n 的最小空閒分區
  • 原理 & 實現
    • 空閒分區列表按照大小排序
    • 分配時,查找一個合適的分區
    • 釋放時,查找並且合併臨近的空閒分區(如果找到)
  • 優點
    • 大部分分配的尺寸較小時,效果很好
      • 可避免大的空閒分區被拆分
      • 可減少外部碎片的大小
      • 相對簡單
  • 缺點
    • 外部碎片
    • 釋放分區比較慢
    • 容易產生很多無用的小碎片

最差匹配(Worst Fit Allocation)策略

  • 思路:分配 n 字節,使用尺寸不小於 n 的最大空閒分區
  • 原理 & 實現
    • 空閒分區列表按由大到小排序
    • 分配時,選最大的分區
    • 釋放時,檢查是否可與臨近的空閒分區合併,進行可能的合併,並調整空閒分區列表順序
  • 優點
    • 中等大小的分配比較多時,效果最好
    • 避免出現太多的小碎片
  • 缺點
    • 釋放分區較慢
    • 外部碎片
    • 容易破壞大的空閒分區,因此後續難以分配大的分區

碎片整理:緊湊( compaction )

  • 碎片整理
    • 通過調整進程佔用的分區位置來減少或避免分區碎片
  • 碎片緊湊
    • 通過移動分配給進程的內存分區,以合併外部碎片
    • 碎片緊湊的條件:所有的應用程序可動態重定位
    • 需要解決的問題:
      • 什麼時候移動?
      • 開銷

碎片整理:分區兌換( Swapping in/out )

  • 通過搶佔並回收處於等待狀態進程的分區,以增大可用內存空間
  • 需要解決的問題
    • 交換哪個(些)程序?

夥伴系統( Buddy System )

  • 整個可分配的分區大小 2U
  • 需要的分區大小爲 2U-1 < s <= 2U 時,把整個塊分配給該進程
    • 如 s <= 2i-1 ,將大小爲 2i 的當前空閒分區劃分成兩個大小爲 2i-1 的空閒分區
    • 重複劃分過程,直到 2i-1 < s <= 2i ,並把一個空閒分區分配給該進程

夥伴系統的實現

  • 數據結構
    • 空閒塊按大小和起始地址組織成二維數組
    • 初始狀態:只有一個大小爲 2U 的空閒塊
  • 分配過程
    • 由小到大在空閒塊數組中找最小的可用空閒塊
    • 如空閒塊過大,對可用空閒塊進行二等分,知道得到合適的可用空閒塊
  • 釋放過程
    • 把釋放的塊放入空閒塊數組
    • 合併滿足合併條件的空閒塊
  • 合併條件
    • 大小相同 2i
    • 地址相鄰
    • 起始地址較小的塊的起始地址必須是 2^(i+1) 的倍數

物理內存管理:非連續內存分配

非連續分配的設計目標

  • 連續分配的缺點
    • 分配給程序的物理內存必須連續
    • 存在外碎片和內碎片
    • 內存分配的動態修改困難
    • 內存利用率較低
  • 非連續分配的設計目標:提高內存利用效率和管理靈活性
    • 允許一個程序的使用非連續的物理地址空間
    • 允許共享代碼與數據
    • 支持動態加載和動態鏈接
  • 非連續分配需要解決的問題
    • 如何實現虛擬地址和物理地址的轉換?
      • 軟件實現 (靈活,開銷大)
      • 硬件實現 (夠用,開銷小)
  • 非連續分配的硬件輔助機制
    • 如何選擇非連續分配中的內存分塊大小?
      • 段式存儲管理( segmentation )
      • 頁式存儲管理( paging )

段地址空間

  • 進程的段地址空間由多個段組成
    • 主代碼段
    • 子模塊代碼段
    • 公用庫代碼段
    • 堆棧段( stack )
    • 堆數據( heap )
    • 初始化數據段
    • 符號表等
  • 段式存儲管理的目的
    • 更細粒度和靈活的分離與共享

段訪問機制

  • 段的概念
    • 段表示訪問方式和存儲數據等屬性相同的一段地址空間
    • 對應一個連續的內存"塊"
    • 若干個段組成進程邏輯地址空間
  • 段訪問:邏輯地址由二元組 (s,addr) 表示
    • s: 段號
    • addr: 段內偏移

頁式存儲管理

  • 頁幀(幀,物理頁面,Frame,Page Frame)
  • 頁面(頁,邏輯頁面,Page)
  • 頁面到頁幀
    • 邏輯地址到物理地址的轉換
    • 頁表
    • MMU/TLB

幀( Frame )

  • 物理內存被劃分成大小相等的幀,內存物理地址的表示:二元組 (f,o)
    f: 幀號( F 位,共有 2F 個幀)
    o: 幀內偏移 ( S 位,每幀有 2S 字節)
    物理地址 = f * 2S + o
  • 基於頁幀的物理地址計算實例
    • 假定
      • 16-bit 的地址空間
      • 9-bit (512 byte) 大小的頁幀
    • 物理地址計算
      • 物理地址表示 = (3, 6)
      • 物理地址 = f * 2S + o

      • F = 7,S = 9,f = 3,o = 6
    • 實際物理地址 = 2^9 * 3 + 6 = 1536 + 6 = 1542

頁( Page )

  • 進程邏輯地址空間被劃分爲大小相等的頁
    • 頁內偏移 = 幀內偏移
    • 通常:頁號大小 ≠ 幀號大小
    • 進程邏輯地址的表示:二元組 (p, o)
      p: 頁號 ( P 位,2P 個頁)
      o: 頁內偏移 ( S 位,每頁有 2S 字節)
      虛擬地址 = p * 2S + o
  • 頁式存儲中的地址映射
    • 頁到幀的映射
    • 邏輯地址中的頁號是連續的
    • 物理地址中的幀號是不連續的
    • 不是所有的頁都有對應的幀

頁表

  • 頁表概述
    • 頁表保存了邏輯地址與物理地址之間的映射關係
  • 頁表結構
    • 每個進程都有一個頁表
      • 每個頁面對應一個頁表項
      • 隨進程運行狀態而動態變化
      • 頁表基址寄存器( PTBR: Page Table Base Register )
    • 頁表項的組成
      • 幀號:f
      • 頁表項狀態
        • 存在位( register bit )
        • 修改位( dirty bit )
        • 引用位( clock/reference bit )
  • 頁式存儲管理機制的性能問題
    • 內存訪問性能問題
      • 訪問一個內存單元需要2次內存訪問
      • 第一次訪問:獲取頁表項
      • 第二次訪問:訪問數據
    • 頁表大小問題
      • 頁表可能非常大
      • 64位機器如果每頁1024字節,那麼一個頁表的大小會是多少
    • 如何處理?
      • 緩存 ( Caching )
      • 間接 ( Indirection ) 訪問

解決頁表問題

  • 快表 ( Translation Look-aside Buffer, TLB )
    • 緩存近期訪問的頁表項
      • TLB 使用關聯存儲 ( association memory ) 實現,具備快速訪問性能
      • 如果 TLB 命中,物理頁號可以很快被獲取
      • 如果 TLB 未命中,對應的表項被更新到 TLB 中
  • 多級頁表
    • 通過間接引用將頁號分成 k 級
      • 建立頁表"樹"
      • 減少每級頁表的長度
  • 反置頁表
    • 大地址空間問題
      • 對於大地址空間 ( 64-bits ) 系統,多級頁表變得繁瑣
        • 比如:5級頁表
        • 邏輯(虛擬)地址空間增長速度快於物理地址空間
      • 頁寄存器和反置頁面的思路
        • 不讓頁表與邏輯地址空間的大小相對應
        • 讓頁表與物理地址空間的大小相對應
    • 頁寄存器( Page Registers )
      • 每個幀與一個頁寄存器( Page Register )關聯,寄存器內容包括:
        • 使用位( Register bit ):此幀是否被進程佔用
        • 佔用頁號( Occupier ):對應的頁號 p
        • 保護位 ( Protection bits )
      • 頁寄存器示例
        • 物理內存大小:4096 * 4096 = 4 K * 4 KB = 16 MB
        • 頁面大小:4096 bytes = 4 KB
        • 頁幀數:4096 = 4 K
        • 頁寄存器使用的空間(假設每個頁寄存器佔8字節):8 * 4096 = 32 Kbytes
        • 頁寄存器帶來的額外開銷:32K / 16M = 0.2%(大約)
        • 虛擬內存的大小:任意
      • 頁寄存器方案特徵
        • 優點
          • 頁表大小相對於物理內存而言很小
          • 頁表大小與邏輯地址空間大小無關
        • 缺點
          • 頁表信息對調後,需要依據幀號可找頁號
          • 在頁寄存器中搜索邏輯地址中的頁號
      • 頁寄存器中的地址轉換
        • CPU 生成的邏輯地址如何找對應的物理地址?
          • 對邏輯地址進行 Hash 映射,以減少搜索範圍
          • 需要解決可能的衝突
        • 用快表緩存頁表項後的頁寄存器搜索步驟
          • 對邏輯地址進行 Hash 變換
          • 在快表中查找對應頁表項
          • 有衝突時遍歷衝突項鍊表
          • 查找失敗時,產生異常
        • 快表的限制
          • 快表的容量限制
          • 快表的功耗限制( StrongARM 上快表功耗佔 27% )
    • 反置頁表
      • 基於 Hash 映射值查找對應頁表項中的幀號
        • 進程標識與頁號的 Hash 值可能有衝突
        • 頁表項中包括保護位、修改位、訪問位和存在位等標識
      • 反置頁表的 Hash 衝突

段頁式存儲管理

  • 需求
    • 段式存儲在內存保護方面有優勢,頁式存儲在內存利用和優化轉移到後備存儲方面有優勢。
    • 段式存儲,頁式存儲能否結合?
  • 做法
    • 在段式存儲管理基礎上,給每個段加一級頁表
  • 段頁式存儲管理中的內存共享
    • 通過指向相同的頁表基址,實現進程間的段共享
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章