2017-2018-1 20155227 《信息安全系統設計基礎》第十三周學習總結

2017-2018-1 20155227 《信息安全系統設計基礎》第十三周學習總結

找出全書你認為最重要的一章,深入重新學習一下,要求(期末占10分):
完成這一章所有習題詳細總結本章要點給你的結對學習搭檔講解你的總結并獲取反饋

我選擇教材第九章的內容來深入學習,一方面,虛擬內存這一部分內容本身就十分重要,另一方面,我在操作系統課上對這一部分的知識理解的很淺,在十一周自學教材這一部分內容時也因為知識點太多而沒有學得很深入,正好借此機會來重新學習這一章的內容,加深理解。

教材學習內容總結

第九章 虛擬存儲器

為了更加有效地管理存儲器且少出錯,現代系統提供了對主存的抽象概念,叫做虛擬存儲器(VM)

  • 虛擬存儲器是硬件異常,硬件地址翻譯,主存,磁盤文件和內核軟件的完美交互。

  • 為每個進程提供一個大的,一致的和 私有的地址空間。

  • 提供了3個重要能力
    • 將主存看成磁盤地址空間的高速緩存

      • 只保留了活動區域,并根據需要在磁盤和主存間來回傳送數據,高效使用主存。
    • 為每個進程提供一致的地址空間
      • 簡化存儲器管理
    • 保護了每個進程的地址空間不被其他進程破壞。
  • 虛擬內存在幕后工作,程序員為什么要理解它呢?

    • 虛擬存儲器是中心的。
    遍布在計算機系統所有層次,硬件異常,匯編器,連接器,加載器,共享對象,文件和進程中扮演重要角色。
    • 虛擬存儲器是強大的。
      • 可以創建和銷毀存儲器片(chunk)
      • 將存儲器片映射到磁盤文件的某個部分。
      • 其他進程共享存儲器。
      • 例子

        能讀寫存儲器位置來修改磁盤文件內容。
        加載文件到存儲器不需要顯式的拷貝。
    • 虛擬存儲器是危險的

      • 引用變量,間接引用指正,調用malloc動態分配程序,就會和虛擬存儲器交互。
      • 如果使用不當,將遇到復雜危險的與存儲器有關的錯誤。
      • 例子

         一個帶有錯誤指針的程序可以立即崩潰于段錯誤或者保護錯誤。運行完成,卻不產生正確結果。

9.1 物理與虛擬尋址


  • 物理地址(Physical Address,PA):計算機系統的主存被組織為M個連續的字節大小的單元組成的數組。每個字節的地址叫物理地址.
  • CPU訪問存儲器的最自然的方式使用物理地址,這種方式稱為物理尋址
    早期的PC,數字信號處理器,嵌入式微控制器以及Cray超級計算機使用物理尋址
  • 現代處理器使用的是虛擬尋址(virtual addressing)的尋址形式。

1065400-20171214124153842-890064266.png

1065400-20171214124159857-814053071.png

  • CPU通過生成一個虛擬地址(Virtual address,VA)來訪問主存。
    • 虛擬地址轉換為物理地址叫做地址翻譯(address translation)
  • 地址翻譯也需要CPU硬件和操作系統之間的緊密結合。
    • CPU芯片上有叫做存儲器管理單元(Memory Management Unit,MMU)的專用硬件。

      利用存儲在主存中的查詢表來動態翻譯虛擬地址。查詢表由操作系統管理。

9.2 地址空間


地址空間(address space)是一個非負整數地址的有序集合。

  • 如果地址空間中整數是連續的,我們說它是線性地址空間(linear address space)

    • 我們總是假設使用線性地址空間
  • 在一個帶虛擬存儲器的系統中,CPU從一個有N=2^n個地址的地址空間中生成虛擬地址,這個地址空間稱為虛擬地址空間(virtual address space)
  • 一個地址空間大小是由表示最大地址所需要的位數來描述的。
    • N=2^n個地址的虛擬地址空間叫做n位地址空間
    • 現在操作系統支持32位或64位
  • 一個系統還有物理地址空間,它與系統中物理存儲器的M=2^m(假設為2的冪)個字節相對應。

地址空間的概念很重要,因為它區分了數據對象(字節)和 它們的屬性(地址)

  • 每個字節(數據對象)一般有多個 獨立的地址(屬性)。每個地址都選自不同的地址空間

    • 字節有一個在虛擬地址空間的虛擬地址
    • 還有一個在物理地址空間的物理地址
    • 兩個地址都能訪問到這個字節

9.3 虛擬存儲器作為緩存的工具


虛擬存儲器(VM) 被組織為一個存放在磁盤上的N個連續字節大小的單元組成的數組

  • 每個字節都有一個唯一的虛擬地址,這個虛擬地址作為到數組的索引。

  • 磁盤上數組的內容被緩存到主存中。

    • 同存儲器層次結構其他緩存一樣,磁盤上的數據被分割成塊

       這些塊作為磁盤和主存之間的傳輸單元。虛擬頁(Virtual Page,VP)就是這個塊 
    • 物理存儲器被分割為物理頁,大小也為P字節`` 也被稱為``頁幀(page frame)
  • 任何時候,虛擬頁的集合都被分為3個不相交的子集
    • 未分配的: VM系統還未分配(或者創建)的頁。未分配的塊沒有任何數據與之相關聯。
      • 不占用磁盤空間
      • 通過malloc來分配
    • 緩存的: 當前緩存在物理存儲器的已分配頁。
    • 未緩存的: 沒有緩存在物理頁面存儲器中的已分配頁。
      1065400-20171214124211060-1954012872.png
9.3.1 DRAM緩存的組織結構

DRAM表示虛擬存儲器系統的緩存,在主存中緩存虛擬頁,有兩個特點。

  • DRAM緩存不命中處罰十分嚴重。

    • 因為磁盤比DRAM慢100000多倍。
  • 訪問一字節開銷
    • 從一個磁盤的一個扇區讀取第一個字節的時間開銷要比從該扇區中讀連續的字節慢大約100000倍

DRAM緩存的組織結構由這種巨大的不命中開銷驅動。因此有以下特點。

  • 虛擬頁往往很大。

    • 4KB~2MB
  • DRAM緩存是全相聯
    • 任何虛擬頁都能放在任何物理頁中。
    • 原因在于大的不命中懲罰
  • 精密替換算法
    • 替換錯了虛擬頁的懲罰很高。
  • DRAM緩存總是寫回
    • 因為對磁盤的訪問時間很長
    • 而不用直寫
9.3.2 頁表

判斷命中和替換由多種軟硬件聯合提供。

  • 操作系統軟件,MMU中的地址翻譯硬件和頁表(page table)
    • 頁表是存放在物理存儲器的數據結構。
      • 頁表將虛擬頁映射到物理頁
      • 地址翻譯硬件將虛擬地址轉換為物理地址都會讀取頁表
    • 操作系統負責維護頁表的內容,以及磁盤及DRAM之間來回傳送頁

1065400-20171214124222904-494129943.png

  • 頁表就是一個頁表條目(Page Table Entry,PTE)的數組.
    • 虛擬地址空間中每個頁在頁表的固定偏移量處都有一個PTE.
    • 每個PTE有一個有效位和n位地址字段

      有效位表明虛擬頁是否被緩存。 
      如果有效位存在,那么地址字段指向對應的物理存儲器。
      如果有效位不存在。 地址字段要么為NULL,要么指向虛擬頁在磁盤所在的位置。
9.3.3 頁命中

1065400-20171214124239045-558612853.png

  • 一個頁命中的過程。
  • 一個虛擬地址轉換為物理地址的過程。
9.3.4 缺頁

DRAM緩存不命中稱為缺頁
處理過程如下:

  • 讀取虛擬地址所指向的PT
  • 讀取PTE有效位,發現未被緩存,觸發缺頁異常
  • 調用缺頁異常處理程序
    • 選擇犧牲頁
    • 如果犧牲頁發生了改變,將其拷貝回磁盤(因為是寫回)
    • 需要讀取的頁代替了犧牲頁的位置。
    • 結果:犧牲也不被緩存,需要讀取的頁被緩存。
  • 中斷結束,重新執行最開始的指令。

  • DRAM中讀取成功。

1065400-20171214124250888-177995175.png

塊被稱為頁。
磁盤和DRAM之間傳送頁的活動叫做交換(swapping)或者頁面調度(paging)。
有不命中發生時,才換入頁面,這種策略叫做按需頁面調度(demand paging)。 
9.3.5 分配頁面

比如某個頁面所指向地址為NULL,將這個地址指向磁盤某處,那么這就叫分配頁面

此時虛擬頁從未分配狀態 變為 未緩存

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


操作系統為每個進程提供一個獨立的頁表

1065400-20171214124304545-291314981.png

因此,VM簡化了鏈接和加載,代碼和數據共享,以及應用程序的存儲器分配

  • 簡化鏈接
    • 獨立的空間地址意味著每個進程的存儲器映像使用相同的格式
      • 文本節總是從0x08048000(32位)處或0x400000(64位)處開始。
      • 然后是數據,bss節,棧
    • 一致性極大簡化了鏈接器的設計和實現
  • 簡化加載
    • 加載器可以從不實際拷貝任何數據從磁盤到存儲器
    • 基本都是虛擬存儲系統完成。
簡化共享
  • 獨立地址空間為操作系統提供了一個管理用戶進程和操作系統自身之間的一致共享機制

    • 操作相同的操作系統內核代碼
    • C標準庫的printf.
  • 因此操作系統需要將不同進程的適當的虛擬頁映射到相同的物理頁面。
    • 多個進程共享這部分代碼的一個拷貝。
    • 而不是每個進程都要加載單獨的內核和C標準庫的拷貝。
簡化存儲器分配

虛擬頁連續(虛擬頁還是單獨的),物理頁可以不連續。使得分配更加容易。

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


任何現代操作系統必須為操作系統提供手段來控制存儲器系統的訪問。

  • 不應該允許用戶進程修改它的只讀文本段。
  • 不允許它讀或修改任何內核的代碼和數據結構
  • 不允許讀寫其他進程的私有存儲器。
  • 不允許修改共享的虛擬頁,除非所有共享者顯示允許這么做(通過調用明確的進程間通信)

1065400-20171214124314717-45687079.png

  • SUP: 是否只有在內核模式下才能訪問?
  • READ: 讀權限。
  • WRITE: 寫權限。

如果指令違反了許可條件,觸發一般保護性異常,然后交給異常處理程序Shell一般會報告為段錯誤(segmentaion fault)

9.6 地址翻譯


1065400-20171214124324326-289812842.png

  • 形式上來說,地址翻譯是一個N元素的虛擬地址空間(VAS)中的元素和一個M元素的物理地址空間(PAS)元素之間的映射,

1065400-20171214124346670-197608503.png

  • 以下展示了MMU(Memory Management Unit,存儲器管理單元)如何利用頁表實現這樣的功能

1065400-20171214124334982-1750218459.png

  • 頁表基址寄存器(Page Table Base Register,PTBR)指向當前頁表
  • n位的虛擬地址包含兩個部分
    • 一個p位的虛擬頁面偏移(Virtual Page Offset,VPO)
    • 一個n-p位的虛擬頁號(Virtual Page Number,VPN)
  • 頁面條目 (PTE)物理頁號(PPN)虛擬地址中的VPO串聯起啦,即是物理地址
    • PPO和VPO是相同的
    • VPN,PPN都是塊,都是首地址而已,所以需要偏移地址PPO,VPO

1065400-20171214124400701-474971787.png

圖(a)展示頁面命中,CPU硬件執行過程:

  • 第一步:處理器生成虛擬地址,把它傳送給MMU。
  • 第二步: MMU生成PTE地址(PTEA),并從高速緩存/主存請求中得到它。
  • 第三步: 高速緩存/主存向MMU返回PTE
  • 第四步: MMU構造物理地址(PA),并把它傳送給高速緩存/主存。
  • 第五步: 高速緩存/主存返回所請求的數據字給處理器

頁面命中完全由硬件處理,與之不同的是,處理缺頁需要 硬件和操作系統內核協作完成。

  • 第一到三步: 與命中時的一樣
  • 第四步:PTE有效位是零,所以MMU觸發異常,傳遞CPU中的控制到操作系統內核中的 缺頁異常處理程序。
  • 第五步:缺頁異常處理程序確定出物理存儲頁中的犧牲頁,如果這個頁面已經被修改,則把它換出到磁盤。
  • 第六步:缺頁異常處理程序調入新的頁面,并更新存儲器中的PTE。
  • 第七部:缺頁異常處理程序返回到原來的進程,再次執行導致缺頁的指令,之后就是頁面命中一樣的步驟。
9.6.1 結合高速緩存和虛擬存儲器

在任何使用虛擬存儲器又使用SRAM高速緩存的系統中,都存在應該使用虛擬地址 還是 使用 物理地址 來訪問SRAM高速緩存的問題。

大多數系統是選擇物理尋址

  • 使用物理尋址,多個進程同時在高速緩存中有存儲塊共享來自相同虛擬頁面的塊成為簡單的事。
    • 而且還無需處理保護問題,因為 訪問權限的檢查在地址翻譯中(PTE)的一部分。
  • 以下是一個例子(將PTE進行高速緩存)。

1065400-20171214124411935-1546295859.png

9.6.2 利用TLB加速地址翻譯

每次CPU產生一個虛擬地址MMU就必須查閱一個PTE,以便將虛擬地址翻譯為 物理地址

  • 在最糟糕的情況下,會從內存中取數據,代價是幾十 到幾百個周期
  • 如果PTE碰巧緩存在L1中,那么開銷就下降到一到兩個周期

許多系統都試圖消除這樣的開銷,他們在MMU中包含了一個關于PTE的小緩存,稱為翻譯后備緩沖器(Translation Lookaside Buffer,TLB)

  • TLB是一個小的,虛擬尋址的緩存。
    • 每一行都保存著一個由單個PTE組成的塊。
    • TLB通常用于高度的相連性
      - 如圖所示
      • 用于組選擇和行匹配的索引和標記字段是從虛擬地址中的虛擬頁號中提取出來的。
    • 如果TLBT=2^t個組
      • 那么TLB索引(TLBI)是由VPNt個最低位組成。(對應于VPO)
      • TLB標記(TLBT)是由VPN剩余位組成(對應于VPN)
  • 下圖展示了TLB命中步驟
    • 關鍵點:所有的地址翻譯步驟都是在芯片上的MMU中執行的,因此非常快

1065400-20171214124431170-1587435121.png

  • TLB命中
    • 第一步:CPU產生虛擬地址
    • 第二步和第三部:MMUTLB取出對應的PTE
    • 第四步:MMU將這個虛擬地址翻譯成一個物理地址,發送到高速緩存/主存
    • 第五步:高速緩存/主存所請求的數據字返回給CPU
  • TLB不命中的時候,MMU必須從L1緩存或內存中取出相應的PTE,并進行類似缺頁處理過程
9.6.3 多級頁表

如果我們有一個32位地址空間,4KB大小的頁面(p=2^12)和一個4B的PTE,即使應用所引用的只是虛擬地址空間中很小的一部分,也總是需要一個4MB的頁表駐留在存儲器中。

所以多級頁表的誕生用于解決在很少使用時有一個很大的頁表常駐于內存。

用來壓縮頁表的常用方式是使用層次結構的頁表。

1065400-20171214124441498-1336766826.png

以下用上圖的兩層作為例子。

  • 總共有9KB個頁面,PTE為4個字節。
    • 2KB個頁面分配給代碼和數據。
    • 接下來6KB個頁面未分配
    • 再接下來1023個頁面也未分配
    • 接下一個頁面分配給用戶棧
  • 一級頁表中的每個PTE負責映射虛擬地址空間中一個4MB大小的片(chunk).
    • 每一個片都是由1024個連續的頁面組成。
    • 4MB=1024個頁面*PTE大小4字節。
  • 如果片i中每個頁面都沒有分配,那么一級PTE i就為空。
    • 例如圖中的PTE 2~PTE 7
    • 但是如果片i中有一個被分配了,那么PTE i就不能為空。
  • 這種方法從兩個方面減少了存儲器要求。
    • 如果一級頁表PTE為空,那么相應的二級頁表就根本不會存在。
      • 一種巨大的潛在節約,大部分時候內存都是未分配的。
  • 只有一級頁表才需要總是在主存中。
    • 虛擬存儲器系統可以在需要時創建,頁面調入,調出二級頁面,減少主存壓力。

1065400-20171214124451545-1281894336.png

k級頁表層次結構的地址翻譯。

  • 虛擬地址被分為k個VPN一個VPO。每個VPN i都是i-1級頁表到i級頁表的索引。
  • PPN存于k級頁表
  • PPO依舊與VPO相同。

此時TLB能發揮作用,因為層次更細,更利于緩存。使得多級頁表的地址翻譯不比單級頁表慢很多。

9.6.4 綜合:端到端的地址翻譯

一個在有一個TLBL1 d-cache的小系統上。作出如下假設:

  • 存儲器都是按字節尋址的。
  • 存儲器訪問是針對一字節的字的。
  • 虛擬地址是14位長(n=14)
  • 物理地址是12位長(m=12)
  • 頁面大小是64字節(P=2^6)
  • TLB是四路組相連的,總共有16個條目
  • L1 d-cache是物理尋址,高速緩存,直接映射(E=1)的,行大小為4字節,而總共有16個組

存儲結構快照

1065400-20171214124503873-635225665.png
1065400-20171214124515795-53964710.png
1065400-20171214124525607-1052218806.png

  • TLB: TLB利用VPN的位進行緩存。
  • 頁表: 這個頁表是一個單級設計。一個有256個,但是這里只列出16個。
  • 高速緩存:直接映射的緩存通過物理地址的字段來尋址。
    • 因為是直接映射,通過索引就能直接找到。且E=1
    • 直接能判定是否命中
9.7 案例研究: Intel Core i7/Linux 存儲器系統

1065400-20171214124539154-2093552874.png

處理器包(processor package)
  • 四個核
    • 層次結構的TLB
      • 虛擬尋址
      • 四路組相連
      • Linux 一頁4kb
    • 層次結構的數據和指令高速緩存。
      • 物理尋址
      • L1,L2 八路組相連
      • L3 十六路組相連
      • 大小64字節
    • 快速的點到點鏈接。
      • 基于Intel QuickPath技術
      • 為了讓核與其他核和外部I/O橋直接通信
  • L3高速緩存
  • DDR3存儲器控制器
9.7.1 Core i7地址翻譯

1065400-20171214124550810-634097909.png

上圖完整總結了Core i7地址翻譯過程,從虛擬地址到找到數據傳入CPU。

  • Core i7采用四級頁表層次結構。
    • CR3 控制寄存器指向第一級頁表(L1)的起始位置
      • CR3也是每個進程上下文的一部分。
      • 上下文切換的時候,CR3也要被重置。

一級,二級,三級頁表PTE的格式:

1065400-20171214124600357-1441369817.png

  • P=1時 地址字段包含了一個40位物理頁號(PPN),指向適當的頁表開始處。

  • 強加了一個要求,要求物理頁4kb對齊。
    • 因為PPO12位 = 4kb
    • PPO的大小就跟物理頁的大小有關。

四級頁表的PTE格式:

1065400-20171214124611607-1519223483.png

  • PTE有三個權限位,控制對頁的訪問
    • R/W位確定頁的內容是可以 讀寫還是 只讀。
    • U/S位確定用戶模式是否能夠訪問,從而保護操作系統內核代碼不被用戶程序訪問。
    • XD (禁止執行) 位是在64位系統引入,禁止某些存儲器頁取指令。
      • 這是一個重要的新特性,限制只能執行只讀文本段,降低緩沖區溢出的風險。
  • MMU翻譯虛擬地址時,還會更新兩個內核缺頁處理程序會用到的位。
    • A位
      • 每次訪問一個頁,MMU都會設置A位,稱為引用位(reference bit).
      • 可以利用這個引用位來實現它的頁替換算法。
    • D位
      • 每次對一個頁進行了寫 就會設置D位,又稱臟位(dirty bit).
      • 臟位告訴內核在拷貝替換頁前是否要寫回。
      • 內核通過調用一條特殊的內核模式指令來清除引用位或臟位。

四級頁表如何將VPN翻譯成物理地址

1065400-20171214124622232-2114746218.png

  • 每個VPN被用作頁表的偏移量
  • CR3寄存器包含L1頁的物理地址
9.7.2 Linux 虛擬存儲系統

1065400-20171214124643170-98124458.png

內核虛擬存儲器

  • 內核虛擬存儲器包含內核中的代碼數據
    • 內核虛擬存儲器的某些區域被映射到所有進程共享的物理頁面
      • 如:內核代碼,全局數據結構。
    • Linux也將一組連續的虛擬頁面(大小等同于系統DRAM總量)映射到相應的一組物理頁面
  • 內核虛擬存儲器包含每個進程不相同的數據。
    • 頁表,內核在進程上下文中時使用的棧,等等。
Linux 虛擬存儲器區域

Linux將虛擬存儲器組織成一些區域(也叫做段)的集合。

  • 一個區域就是已經存在著的(已分配的) 虛擬存儲器的連續片,這些片/頁已某種形式相關聯。
    • 代碼段數據段共享庫段,用戶棧
    • 所有存在的虛擬頁都保存在某個區域
      • 區域的概念很重要
      • 允許虛擬地址空間有間隙。

一個進程中虛擬存儲器的內核數據結構。

1065400-20171214124952842-1065349220.png

內核為系統中每個進程維護了一個單獨的任務結構任務結構中的元素包含或指向內核運行該進程所需要的全部信息。

  • task_struct
    • mm_struct
      • 描述了虛擬存儲器的當前狀態。
      • pgd
        • 指向第一級頁表的基址。
        • 當進程運行時,內核將pgd存放在CR3控制寄存器
      • mmap
        • vm_area_structs(區域結構)
        • 每個vm_area_structs都描述了當前虛擬地址空間的一個區域(area).
        • vm_start:指向這個區域的起始處。
        • vm_end:指向這個區域的結束處。
        • vm_port:描述這個區域內包含的所有頁的讀寫許可權限。
        • vm_flags:描述這個區域頁面是否與其他進程共享,還是私有。
        • vm_next: 指向鏈表的下一個區域。
Linux 缺頁異常處理

MMU在試圖翻譯虛擬地址A時,觸發缺頁 。這個異常導致控制轉移到缺頁處理程序,執行一下步驟。

1065400-20171214125003170-2009176553.png

  • 虛擬地址A是合法的嗎?
    • A在某個區域結構定義的區域內嗎?
    • 解決方法:

      • 缺頁處理程序 搜索區域結構鏈表。
      • 把A和每個區域的vm_startvm_end 做比較。
        • 通過某種 的數據結構算法查找
    • 如果不合法 ,觸發段錯誤

  • 試圖訪問的存儲器是否合法?
    • 即是否有讀,寫,執行這個頁面的權限?
    • 如果不合法,觸發保護異常終止進程。
      ?
  • 一切正常的話
    • 選擇犧牲頁,替換,重新執行指令

9.8 存儲器映射


存儲器映射: Linux通過將一個虛擬存儲器區域與一個磁盤上的對象關聯起來,以初始化這個虛擬存儲器區域的內容,這個過程叫做存儲器映射

虛擬存儲器區域可以映射到以下兩種類型文件。

  • Unix文件系統中的普通文件:一個區域可以映射到一個普通磁盤文件的連續部分。
    • 例如,一個可執行文件
    • 文件區(section)被分成頁大小的片,每一片包含一個虛擬頁面的初始化內容。
    • 僅僅是初始化,虛擬頁面此時還并未進入物理存儲器
      • 到CPU第一次引用這個頁面。
  • 匿名文件 : 一個區域可以映射到一個匿名文件。
    • 匿名文件由內核創建,包含的全是二進制零。
    • CPU第一次引用這樣區域(匿名文件)的虛擬頁面時。
      • 將存儲器中犧牲頁面全部用二進制零覆蓋
      • 并將虛擬頁面標記為駐留在存儲器中。
      • 注意: 實際上,虛擬頁面并沒有跟存儲器進行數據傳送。

又叫請求二進制零的頁(demand-zero page)

交換文件,交換空間。(win下叫做paging file)

  • 一旦一個虛擬頁面被初始化了,它就在一個由內核維護的專門的交換文件(swap file)之間換來換去。交換文件也叫交換空間或者交換區域
  • 需要意識到,在任何時刻,交換空間都限制著當前運行著的進程分配的虛擬頁面總數。
9.8.1 再看共享對象

共享對象的由來

  • 許多進程有同樣的只讀文本區域
    • printf
    • 運行Uinx shell的tcsh```
    • 如果每個進程都加載進內存一次,極其浪費。
  • 存儲器映射提供一種機制,來共享對象

一個對象被映射到虛擬存儲器的一個區域,一定屬于以下兩種。

  • 共有對象
    • 一個進程將一個共有對象映射到它的虛擬地址空間的一個區域。
      • 進程對這個區域的寫操作,對于那些也把這個共享對象映射它的虛擬存儲器的進程是可見的。
      • 這些變化也會反映到磁盤上的原始對象。
    • 映射到的虛擬存儲器那個區域叫做共享區域。
  • 私有對象
    • 對一個映射到私有對象的區域做出的改變,對于其他進程不可見.
    • 并且進行的寫操作不會反映到磁盤上。
    • 映射到的虛擬存儲器那個區域叫做私有區域
9.8.1.1 共享對象

1065400-20171214125015138-285317387.png

  • 進程1,將共享對象映射到虛擬存儲器中,然后虛擬存儲器將這一段找一塊物理存儲器存儲。

  • 進程2也要引用同樣的共享對象時。
    • 內核迅速判定,進程1已經映射了這個對象。
    • 使進程2的虛擬存儲器直接指向了那一塊進程1指向的物理存儲器。
  • 即使對象被映射到多個共享區域物理存儲器依舊只有一個共享對象的拷貝。
    • 大大解決了物理存儲器內存
9.8.1.2 私有對象

1065400-20171214125024810-401774938.png

私有對象使用一種叫做寫時拷貝(conpy-on-write)的巧妙技術。

  • 私有對象開始生命周期的方式基本與共享對象一樣。
    • 即使對象被多個引用,在物理內存都只保留一個拷貝。
  • 對于每個映射私有對象的進程,相應私有區域的頁表條目都被標記為只讀
    • 并且區域結構(vm_area_structs)被標記為私有的寫時拷貝。
  • 過程:只要有進程試圖寫私有區域內的某個頁面,那么這個寫操作觸發保護異常。
    • 故障處理程序會在物理存儲器中創建被修改頁面的一個新拷貝。
    • 更新頁表條目(PTE)指向這個新的拷貝,恢復被修改頁面的可寫權限。
    • 故障處理程序返回,CPU重新執行這個寫操作。
  • 通過延遲私有對象中的拷貝直到最后可能的時刻,寫時拷貝充分使用了稀缺的物理存儲器
9.8.2 再看fork函數

了解fork函數如何創建一個帶有自己獨立虛擬地址空間的新進程。

  • fork函數被當前進程調用時。
    • 內核為新進程創建內核數據結構,并分配給它唯一一個PID
    • 為了給新進程創建虛擬存儲器
      • 創建了當前進程的mm_struct,區域結構和頁表的原樣拷貝。
      • 將兩個進程的每個頁面都標記為只讀。并給兩個區域進程的每個區域結構都標記為私有的寫時拷貝。
      • 注意:并沒有對物理存儲器進行拷貝哦,利用的是私有對象的寫時拷貝技術。
  • fork函數在新進程返回時。
    • 新進程現在的虛擬存儲器剛好和調用fork時存在的虛擬存儲器相同。
    • 當兩個進程中任一個需要被寫時,觸發寫時拷貝機制。
9.8.3 再看execve函數

理解execve函數實際上如何加載和運行程序。

  • 假設運行在當前的進程中的程序執行了如下的調用:
    • Execve("a.out",NULL,NULL);
  • execve函數在當前進程加載并執行目標文件a.out中的程序,用a.out代替當前程序。
    • 加載并運行需要以下幾個步驟。
      • 刪除已存在的用戶區域
        • 刪除當前進程虛擬地址的用戶部分中已存在的區域結構。
      • 映射私有區域
        • 為新程序的文本數據bss棧區域創建新的區域結構。
          • 所有新的區域結構都是私有的,寫時拷貝的。
          • 文本和數據區域被映射到a.out文件中的文件和數據區。
          • bss區域是請求二進制零,映射到匿名文件。
            • 大小包含在a.out
          • 堆,棧區域也是請求二進制零。
      • 映射共享區域
        • a.out程序與共享對象鏈接。
          • 這些對象都是動態鏈接到這個程序。
          • 然后映射到用戶虛擬地址的共享區域。
      • 設置程序計數器(PC)
        • execve最后一件事設置PC指向文本區域的入口點。

1065400-20171214125037857-2094838902.png

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

Unix進程可以使用mmap函數來創建新的虛擬存儲器區域,并將對象映射到這些區域中。

#include <unistd.h>
#include <sys/mman.h>void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);返回:若成功時則為指向映射區域的指正,若出錯則為MAP_FAILED(-1).

參數解釋:

1065400-20171214125107060-2051579713.png

fd,start,length,offset:

mmap函數要求內核創建一個新的虛擬存儲器區域,最好是從地址start開始的一個區域,并將文件描述符fd指定的對象的一個連續的片chunk映射到這個新的區域。

  • 連續對象片大小為length字節
  • 從據文件開始處偏移量為offset字節的地方開始。
  • statr地址僅僅是個暗示
    • 一般被定義為NULL,讓內核自己安排。

prot

參數prot包含描述新映射的虛擬存儲器區域的訪問權限位。(對應區域結構中的vm_prot位)

  • PROT_EXEC:這個區域內的頁面由可以被CPU執行的指令組成。
  • PROT_READ:這個區域內的頁面可讀。
    -PROT_WRITE:這個區域內的頁面可寫。
  • PROT_NONE: 這個區域內的頁面不能被訪問。

flag

參數flag由描述被映射對象類型的位組成。

  • MAP_ANON標記位:映射對象是一個匿名對象。
  • MAP_PRIVATE標記位:被映射對象是一個私有的,寫時拷貝的對象。
  • MAP_SHARED標記位:被映射對象是一個共享對象。

9.9 動態存儲器分配


雖然可以使用更低級的mmapmunmap函數來創建和刪除虛擬存儲器的區域。

但是C程序員還是覺得用動態存儲器分配器(dynamic memory allocator)更方便。

1065400-20171214125120748-275225408.png

  • 動態存儲器分配器維護著一個進程的虛擬存儲區域,稱為堆(heap)
    • 系統之間細節不同,但是不失通用型。
    • 假設
      • 堆是一個請求二進制零的區域。
      • 緊接著未初始化的bss區域,并向上生長(向更高的地址)。
      • 對于每個進程,內核維護一個變量brk(break),指向堆頂。
  • 分配器將堆視為一組不同大小的塊block的集合來維護。
    • 每個塊就是一個連續的虛擬存儲器片,即頁面大小
    • 要么是已分配,要么是空閑
      • 已分配
        • 已分配的塊顯式地保留供應用程序使用。
        • 已分配的塊保持已分配狀態,直到它被釋放。
          • 這種釋放要么是應用程序顯示執行。
          • 要么是存儲器分配器自身隱式執行(JAVA)。
      • 空閑
        • 空閑塊可用于分配。
        • 空閑塊保持空閑,直到顯式地被應用分配。
  • 分配器有兩種基本分格。
    • 都要求應用顯式分配
    • 不同之處在于那個實體負責釋放已分配的塊
    • 顯式分配器(explict allocator)
      • 要求應用程序顯式地釋放
      • C語言中提供一種叫malloc程序顯示分配器
        • malloc和free
      • C++
        • new和delete
  • 隱式分配器(implicit allocator)
  • 要求分配器檢測一個已分配塊何時不再被程序所使用,那么就釋放這個塊。
  • 隱式分配器又叫做垃圾收集器(garbage collector).
  • 自動釋放未使用的已分配的塊的過程叫做垃圾收集(garbage collection).
    -Lisp,ML以及Java等依賴這種分配器。
9.9.1 malloc和free 函數

malloc

C標準庫提供了一個稱為malloc程序包的顯示分配器。

#include<stdlib.h>
void* malloc(size_t size);返回:成功則為指針,失敗為NULL
  • malloc 返回一個指針,指向大小為至少size字節的存儲器塊。
    • 不一定是size字節,很有可能是4或8的倍數
      • 這個塊會為可能包含在這個塊內的任何數據對象類型做對齊。
      • Unix系統用8字節對齊。
    • malloc不初始化它返回的存儲器。
      • 如果想要初始化,可以用calloc函數。
        • callocmalloc一個包裝函數。
    • 想要改變已分配塊大小。
      • realloch函數
  • 如果malloc遇到問題。
    • 返回NULL, 并設置errno
  • 動態存儲分配器,可以通過使用mmapmunmap函數,顯示分配和釋放堆存儲器。

    • 或者可以使用sbrk函數。
#include<unistd.h>void *sbrk(intptr_t incr);返回:若成功則為舊的brk指針,若出錯則為-1,并設置errno為ENOMEML.

free

程序通過調用free函數來釋放已分配的堆塊。

#include<stdlib.h>void free(void *ptr);返回:無
  • ptr參數必須指向一個從malloc,calloc,realloc獲得的已分配塊的起始位置。
    • 如果不是,那么free行為未定義。
    • 更糟糕的是,free沒有返回值,不知道是否錯了。

1065400-20171214125135435-881199468.png

9.9.2 為什么要使用動態存儲器分配

程序使用動態存儲器分配的最重要原因是:

  • 經常直到程序實際運行時,它們才知道某些數據結構的大小。
9.9.3 分配器的要求和目標

顯式分配器有如下約束條件

  • 處理任意請求序列。
  • 立即響應請求。
    • 不允許為提高性能重新排列或緩沖請求。
  • 只使用
  • 對齊塊
    • 上文的8字節。
  • 不修改已分配的塊。

目標

吞吐率最大化和存儲器使用率最大化。這兩個性能要求通常是相互沖突的

  • 目標1:最大化吞吐率
    • 假定n個分配和釋放請求的某種序列R1,R2,R3.....Rn
      • 吞吐率 :每個單位時間完成的請求數。
    • 通過使分配和釋放請求的平均時間最小化 來最大化吞吐率
  • 目標2:最大化存儲器利用率
    • 設計優秀的分配算法。
    • 需要增加分配和釋放請求的時間。
    • 評估使用堆的效率,最有效的標準是峰值利用率(peak utilization)
  • 吞吐率存儲器利用率是相互牽制的,分配器設計的一個有趣的挑戰就是在兩者之間找到一個平衡
9.9.4 碎片

造成堆利用率很低的主要原因是一種稱為碎片(fragmentation)的現象。

  • 碎片:雖然有未使用的存儲器但不能滿足分配要求時的現象。

    • 內部碎片:已分配塊比有效載荷(實際所需要的)大時發生。
      • 比如:上文中只要5個字(有效載荷),卻給了6個字(已分配塊),那一個多的就是碎片.
      • 任何時刻,內部碎片的數量取決于以前請求的模式和分配器的實現方式。
        • 可計算的可量化的
    • 外部碎片:當空閑存儲器合計起來足夠滿足一個分配請求,但是沒有一個單獨的空閑塊足夠大可以處理這個請求發生的。
      • 外部碎片的量化十分困難。
        • 不僅取決于以前請求的模式和分配器的實現方式,還要知道將來請求的模式。
9.9.5 實現問題

一個實際的分配器要在吞吐率利用率把握平衡,必須考慮一下幾個問題。

  • 空閑塊組織: 如何記錄空閑塊? ( 9.9.6)
  • 放置: 如何選擇一個合適的空閑快來放置一個新分配的塊? (9.9.7)
  • 分割: 將一個新分配的塊放入某個空閑塊后,如何處理這個空閑快中的剩余部分?(9.9.8)
9.9.6 隱式空閑鏈表

1065400-20171214125149513-1438028989.png

組織為一個連續的已分配塊和空閑塊的序列。

1065400-20171214125200998-1482769279.png

這種結構就叫做隱式空閑鏈表

  • 隱式 :
    • 為什么叫隱式鏈表
      • 因為不是通過指針(next)來鏈接起來。
      • 而是通過頭部的長度隱含地鏈接起來。
    • 終止頭部(類似與普通鏈表的NULL)
      • 已分配,大小為零的塊
  • 優缺點:
    • 優點:簡單
    • 缺點1:任何操作的開銷都與已分配塊和空閑塊的總數呈線性關系O(N).
      • 放置分配的塊。
      • 對空閑鏈表的搜索。
    • 缺點2: 即使申請一個字節,也會分配2個字的塊。空間浪費。
9.9.7 放置已分配的塊

有以下幾種搜索放置策略

  • 首次適配
    • 從頭開始搜索空閑鏈表,選擇第一個合適的空閑塊
  • 下一次適配
    • 和首次適配很類似,但不是從頭開始,而是從上一次查詢的地方開始。
  • 最佳適配
    • 檢查每個空閑塊,找一個滿足條件的最小的空閑塊(貪心)

優缺點

  • 首次適配
    • 優點
      • 往往將大的空閑塊保留在鏈表后面。
    • 缺點
      • 小的空閑塊往往在前面,增大了對較大快的搜索時間。
  • 下一次適配
    • 優點
      • 速度塊。
    • 缺點
      • 存儲器利用率低
  • 最佳適配
    • 優點
      • 利用率高
    • 缺點
      • 要完整搜索鏈表,速度慢。
9.9.8 分割空閑塊

兩種策略

  • 占用所有空閑塊
    • 缺點:產生更多的內部碎片(但是如果內部碎片很少,可以接受)
    • 優點:能使得 空閑塊+已分配塊的數量減少
      • 能加快搜索速度。
      • 有的外部碎片(幾個字節,很有可能是外部碎片)可能根本放置不了東西,但是卻占用了搜索時間,還不如當內部碎片算了
    • 放置策略趨向于產生好的匹配中使用。
      • 即占用所有空閑塊內部碎片也很少。
  • 分割空閑塊
    • 缺點:更多的空閑塊和已分配塊,搜索速度降低。
    • 優點:空間利用率更高。
9.9.9 獲取額外的堆存儲器

如果分配器不能為請求塊找到合適的空閑塊將發生什么?

  • 合并相鄰的空閑塊
  • sbrk函數
    • 最大化合并還不行的情況。
    • 向內核請求額外的堆存儲器。
      • 并將其轉為大的空閑塊
      • 插入鏈表
9.9.10 合并空閑塊

假碎片: 因為釋放,使得某些時候會出現相鄰的空閑塊

  • 單獨的放不下請求(碎片),合并卻可以(假性),所以叫假碎片

何時合并?

  • 立即合并
    • 定義:塊被釋放時,合并所有相鄰的塊。
    • 缺點:對于某些請求模式,會產生抖動
  • 推遲合并
    • 定義: 一個稍晚的時候,再合并。
      • 比如:上文中的找不到合適空閑塊的時候。

9.10 GC_垃圾收集


垃圾收集器(garbage collector)是一種動態存儲分配器

  • 垃圾: 它自動釋放不再需要的已分配塊,這些塊稱為垃圾(garbage).
  • 垃圾收集(garbage collection) :自動回收堆存儲的過程叫做垃圾收集
    • 應用顯式分配堆塊,但從不顯式釋放堆塊。
    • 垃圾收集器定期識別垃圾快,并調用相應地free,將這些快放回空閑鏈表。

垃圾收集可以追溯到John McCarthy20世紀60年代早期在MIT開發的Lisp系統

  • 它是Java,ML,Perl和Mathematic等現代語言系統的一個重要部分。
  • 有關文獻描述了大量的垃圾收集方法,數量令人吃驚。
  • 我們討論局限于McCarthy自創的Mark&Sweep(標記&清除)算法
    • 它可以建立已存在的malloc包的基礎上,為C和C++提供垃圾收集。

9.11 C程序中常見的與存儲器有關的錯誤


9.11.1 間接引用壞指正
scanf("%d",&val);
scanf("%d",val);
  • 最好的情況 : 以異常中止。
  • 有可能覆蓋某個合法的讀/寫區域,造成奇怪的困惑的結果。
9.11.2 讀未初始化的存儲器

堆存儲器并不會初始化。

1065400-20171214125431342-1511148045.png

  • 正確做法
    • 使用calloc.
    • 顯示y[i]=0;
9.11.3 允許棧緩沖區溢出

程序不檢查輸入串的大小就寫入棧中的目標緩沖區

  • 那么就有緩沖區溢出錯誤(buffer overflow bug)
  • gets()容易引起這樣的錯誤
    - 用fgets()限制大小。
9.11.4 假設指針和它們所指向對象是相同大小

有的系統里,intint *都是四字節,有的則不同。

9.11.5 越界
9.11.6 引用指針,而不是它所指向的對象

對指針的優先級用錯。

9.11.7 誤解指針的運算

忘記了指針的算術操作是以它們指向的對象的大小為單位來進行的,這種大小不一定是字節

1065400-20171214125444513-915537323.png

9.11.8 引用不存在的變量

1065400-20171214125458013-1047673625.png

返回一個指針,指向里面一個變量的地址。但是這個變量在返回的時候已經從棧里被彈出。

  • 地址是正確的,指向了
  • 但是卻沒有指向想指向的變量。
9.11.9 引用空閑堆塊的數據

引用了某個已經free掉的塊。在C++多態中經常容易犯這個錯誤。

9.11.10 引起存儲器泄露
  • 即是沒有回收垃圾。導致內存中垃圾越來越多。
    • 只有重啟程序,才能釋放。
  • 對于守護進程和服務器這樣的程序,存儲器泄露是十分嚴重的事。
    • 因為一般情況,不能隨便重啟。

9.12 小結


虛擬存儲器是對主存的一個抽象。

  • 使用一種叫虛擬尋址的間接形式來引用主存。
    • 處理器產生虛擬地址,通過一種地址翻譯硬件來轉換為物理地址
      • 通過使用頁表來完成翻譯。
        • 又涉及到各級緩存的應用。
        • 頁表的內容由操作系統提供

虛擬存儲器提供三個功能

  • 它在主存中自動緩存最近使用的存放在磁盤上的虛擬地址空間內容
    • 虛擬存儲器緩存中的塊叫做
  • 簡化了存儲器管理
    • 進而簡化了鏈接
    • 進程間共享數據
    • 進程的存儲器分配以及程序加載。
  • 每條頁表條目里添加保護位,從而簡化了存儲器保護

地址翻譯的過程必須和系統中所有的硬件緩存的操作集合。

  • 大多數條目位于L1高速緩存中。
    • 但是又通過一個TLB的頁表條目的片上高速緩存L1

現代系統通過將虛擬存儲器片和磁盤上的文件片關聯起來,以初始化虛擬存儲器片,這個過程叫做存儲器映射

  • 存儲器映射為共享數據,創建新的進程 以及加載數據提供一種高效的機制。

  • 可以用mmap 手工維護虛擬地址空間區域
    • 大多數程序依賴于動態存儲器分配,例:malloc
      • 管理虛擬地址空間一個稱為堆的區域
      • 分配器兩種類型
        • 顯示分配器
          • C,C++
        • 隱式分配器
          • JAVA

GC是通過不斷遞歸訪問指針來標記已分配塊,在需要的時刻進行Sweep

  • C,C++無法辨認指針導致無法實現完全的GC
    • 只有保守的GC
    • 需要配合平衡樹進行查找p所指向的塊

第九章課后家庭作業

書上課后練習題都有答案,就不在博客里詳細寫了。

9.11

1065400-20171215201256746-517800914.png

A.虛擬地址0x027c

131211109876543210
00001001111100

B.地址翻譯

參數
VPN0x09
TLB索引0x01
TLB標記0x02
TLB命中No
缺頁No
PPN0x17

C.物理地址格式

11109876543210
010111111100

D.物理地址引用

參數
字節偏移0x0
緩存索引0xF
緩存標記0x17
緩存命中No
返回緩存字節-

9.12

1065400-20171215201317543-633167387.png

A.虛擬地址0x03a9

131211109876543210
00001110101001

B.地址翻譯

參數
VPN0x0E
TLB索引0x02
TLB標記0x03
TLB命中No
缺頁No
PPN0x11

C.物理地址格式

11109876543210
010001101001

D.物理地址引用

參數
字節偏移0x1
緩存索引0xA
緩存標記0x11
緩存命中No
返回緩存字節-

9.13

A.虛擬地址0x0040

131211109876543210
00000001000000

B.地址翻譯

參數
VPN0x01
TLB索引0x01
TLB標記0x00
TLB命中No
缺頁YES
PPN-

1065400-20171215201342558-1245078597.png

9.14

1065400-20171215201352996-1653778812.png

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int main()
{int fd;char *start;fd = open("hello.txt", O_RDWR, 0); //打開文件start = mmap(NULL, 1, PROT_WRITE, MAP_SHARED, fd, 0);close(fd);  if(start == MAP_FAILED) return -1;//判斷是否映射成功(*start) = 'J';munmap(start, 1);return 0;
} 

9.15

1065400-20171215201426215-882244503.png

請求塊大小塊頭部
malloc(3)80x9
malloc(11)160x11
malloc(20)240x19
malloc(21)320x21

9.16

1065400-20171215201443949-1381323337.png
對齊要求 | 已分配塊|空閑塊|最小塊大小|
---|---|---|---|
單字 | 頭部和腳部|頭部和腳部|16字節|
單字 | 頭部,但是沒有腳部|頭部和腳部|16字節|
雙字 | 頭部和腳部|頭部和腳部|16字節|
雙字 | 頭部,但是沒有腳部|頭部和腳部|16字節|

空閑塊至少是16字節。已分配塊不需要predsucc,所以這八個字節可以裝數據,加上頭和腳,就是16字節,而且滿足單字雙字對齊。

9.17

1065400-20171215201502496-1781081458.png

我們需要修改find_fit函數(之前的版本在practise習題中寫過,書后有答案)。

代碼可以參考9.18

為此需要先定義一個全局的cur_point指針,表示上次搜索之后指向哪個塊(有效載荷首地址)。

static void *find_fit(size_t asize)
{void *bp = cur_point;do{if( !GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp))) )            return cur_point = bp;bp = (GET_SIZE(HDRP(bp)) == 0) ? heap_listp : NEXT_BLKP(bp);    }while(bp != cur_point);return NULL;
}

另外,需要釋放cur_point指向的指針時,它可能和前面的空閑塊合并,我們應該將cur_point指向前一個空閑塊首地址。在coalesce()函數中需要做如下修改:

else if (!prev_alloc && next_alloc) { /* Case 3 */size += GET_SIZE(HDRP(PREV_BLKP(bp)));PUT(FTRP(bp), PACK(size, 0));PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));if(cur_point == bp) cur_point = PREV_BLKP(bp);bp = PREV_BLKP(bp);
}
else { /* Case 4 */size += GET_SIZE(HDRP(PREV_BLKP(bp))) +GET_SIZE(FTRP(NEXT_BLKP(bp)));PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));if(cur_point == bp) cur_point = PREV_BLKP(bp);bp = PREV_BLKP(bp);
}

9.18

1065400-20171215201514324-1514368729.png

需要考慮四個問題

    1. 初始化的時候,序言塊結尾塊是怎么樣的。

    序言塊八字節8|0x11。

    結尾塊四字節0|0x11.

    1. 申請更多空間的時候(sbrk),如何更改結尾塊

    記錄最后一個塊的alloc

    結尾塊向后延伸申請的空間,并將剛多出的空間作為一個空閑塊。設置為size|(alloc<<1)。再合并該空閑塊。這里如何合并呢?需要判斷最后一塊是否已分配,可通過epilogue來判斷。

    1. 某個空閑塊匹配的時候,如何設置頭和下一塊的頭。

    我們基于以下假設:某個空閑塊匹配,上一個和下一個一定不是空閑塊(否則可以合并)。

    所以頭部就設置為(asize|0x011)。

    如果要分割,則下一塊的頭部設置為(size-asize|0x010),不用合并,因為再下一塊肯定是已經分配。

    1. 釋放某個塊時,如何合并。

    檢查頭部,alloc_prev = 上一塊是否分配

    檢查下一個塊的頭部,alloc_next = 下一個塊是否分配。

    再根據那四種情況分別設置。

    最后,如果下一塊已分配,則需要將下一塊頭部設置為(原頭部&(~0x010))。

另外,在mm_malloc中,分配多大的塊也要發生相應的變化,因為現在最小塊大小可以是DSIZE,而不是2*DSIZE

  • 代碼已上傳至碼云

9.19

1065400-20171215201530965-1949743362.png

  • 1) a; 對于伙伴系統,如果要申請大小為33的空間,那么需要分配64個空間。如果申請大小為65的空間,那么塊大小就需要128,所以最多可能有約50%的空間被浪費。b中,最佳適配要搜索所有空間,所以肯定比首次適配要慢一些。c,邊界標記主要功能是釋放一個塊時,能立即和前后空閑塊合并。如果空閑塊不按順序排列的話,其實也能夠和前一個或者后一個空閑塊進行合并,但如果要和前后一起合并,可能會有些困難,那需要搜索前后塊在空閑鏈表中的位置,并且刪除一個再進行合并。可以參考P576,LIFO方法。d,其實任何分配器都可能有外部碎片,只要剩余的空閑塊大小和足夠但是單個都不夠,就會產生外部碎片

  • 2) d; 塊大小遞增,那么最佳適配法找到的塊和首次適配找到的塊是同一個,因為最佳適配總是想找一個剛好大于請求塊大小的空閑塊。a,塊大小遞減,首次適配很容易找到,所以分配性能會很高。b,最佳適配方法無論怎樣,都要搜索所有的鏈表(除非維護成塊大小遞增的鏈表)。c,是匹配的最小的。

  • 3) c; 保守的意思就是所有可能被引用的堆都會被標記,int像指針,所以可能認為它表示的地址是正在被引用的(實際上它只是個int)。

9.20

1065400-20171215201541402-1172098227.png

不會……

教材學習中的問題及解決

在深入學習之前我和同伴是帶著這些疑問的,學習之后通過討論,對這些問題有了一點理解。

  • 問題1Linux虛擬地址空間如何分布?
  • 問題1解決

    Linux 使用虛擬地址空間,大大增加了進程的尋址空間,由低地址到高地址分別為:
    • 1、只讀段:該部分空間只能讀,不可寫;(包括:代碼段、rodata 段(C常量字符串和#define```定義的常量) )
    • 2、數據段:保存全局變量、靜態變量的空間;
    • 3、 :就是平時所說的動態內存, malloc/new 大部分都來源于此。其中堆頂的位置可通過函數 brk 和 sbrk 進行動態調整。
    • 4、文件映射區域 :如動態庫、共享內存等映射物理空間的內存,一般是 mmap 函數所分配的虛擬地址空間。
    • 5、:用于維護函數調用的上下文空間,一般為 8M,可通過 ulimit –s 查看。
    • 6、內核虛擬空間:用戶代碼不可見的內存區域,由內核管理(頁表就存放在內核虛擬空間)。
  • 問題264位系統擁有2^64的地址空間嗎?
  • 問題2解決

    事實上, 64 位系統的虛擬地址空間劃分發生了改變:
    • 1、地址空間大小不是2^32,也不是2^64,而一般是2^48。因為并不需要 2^64 這么大的尋址空間,過大空間只會導致資源的浪費。64Linux一般使用48位來表示虛擬地址空間,40位表示物理地址,這可通過 /proc/cpuinfo來查看
    • 2、其中,0x0000000000000000~0x00007fffffffffff表示用戶空間, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF表示內核空間,共提供 256TB(2^48) 的尋址空間。這兩個區間的特點是,第 47 位與 48~63 位相同,若這些位為 0 表示用戶空間,否則表示內核空間。
    • 3、用戶空間由低地址到高地址仍然是只讀段數據段文件映射區域
  • 問題3:如何查看進程發生缺頁中斷的次數?
  • 問題3解決

    ps -o majflt,minflt -C program命令查看。

    majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。

    這兩個數值表示一個進程自啟動以來所發生的缺頁中斷的次數。

  • 問題4:發成缺頁中斷后,執行了那些操作?
  • 問題4解決

    當一個進程發生缺頁中斷的時候,進程會陷入內核態,執行以下操作:
    • 1、檢查要訪問的虛擬地址是否合法
    • 2、查找/分配一個物理頁
    • 3、填充物理頁內容(讀取磁盤,或者直接置0,或者啥也不干)
    • 4、建立映射關系(虛擬地址到物理地址)

    重新執行發生缺頁中斷的那條指令

  • 問題5堆內碎片不能直接釋放,導致疑似“內存泄露”問題,為什么 malloc不全部使用 mmap 來實現呢(mmap分配的內存可以會通過 munmap 進行 free ,實現真正釋放)?而是僅僅對于大于 128k的大塊內存才使用 mmap
  • 問題5解決

    進程向 OS 申請和釋放地址空間的接口 sbrk/mmap/munmap 都是系統調用,頻繁調用系統調用都比較消耗系統資源的。并且, mmap 申請的內存被 munmap 后,重新申請會產生更多的缺頁中斷。例如使用 mmap 分配 1M 空間,第一次調用產生了大量缺頁中斷 (1M/4K 次) ,當munmap 后再次分配 1M 空間,會再次產生大量缺頁中斷。缺頁中斷是內核行為,會導致內核態CPU消耗較大。另外,如果使用 mmap 分配小內存,會導致地址空間的分片更多,內核的管理負擔更大

    同時堆是一個連續空間,并且堆內碎片由于沒有歸還 OS ,如果可重用碎片,再次訪問該內存很可能不需產生任何系統調用和缺頁中斷,這將大大降低 CPU 的消耗。 因此, glibcmalloc 實現中,充分考慮了 sbrkmmap 行為上的差異及優缺點,默認分配大塊內存 (128k) 才使用 mmap 獲得地址空間,也可通過 mallopt(M_MMAP_THRESHOLD, <SIZE>)來修改這個臨界值。

    上周考試錯題總結

    結對及互評

點評模板:

  • 博客中值得學習的或問題:
    • xxx
    • xxx
    • ...
  • 代碼中值得學習的或問題:
    • xxx
    • xxx
    • ...
  • 其他

本周結對學習情況

-[20155318](http://www.cnblogs.com/lxy1997/)
- 結對照片
- 結對學習內容- 教材第九章內容- 跟著同伴回顧了第十二章的內容- ...

其他(感悟、思考等,可選)

通過這一章的學習,進一步加深了對虛擬存儲器的了解。

代碼托管

(statistics.sh腳本的運行結果截圖)

1065400-20171216193321327-12250046.png

學習進度條

代碼行數(新增/累積)博客量(新增/累積)學習時間(新增/累積)重要成長
目標5000行30篇400小時
第一周133/1331/18/8
第三周159/2921/310/18
第五周121/4131/510/28
第七周835/30052/710/38
第八周1702/47771/810/48
第九周1664/64413/1110/58
第十一周300/67413/1410/68
第十三周743/74842/1610/78

嘗試一下記錄「計劃學習時間」和「實際學習時間」,到期末看看能不能改進自己的計劃能力。這個工作學習中很重要,也很有用。
耗時估計的公式
:Y=X+X/N ,Y=X-X/N,訓練次數多了,X、Y就接近了。

參考:軟件工程軟件的估計為什么這么難,軟件工程 估計方法

  • 計劃學習時間:15小時

  • 實際學習時間:10小時

  • 改進情況:

(有空多看看現代軟件工程 課件
軟件工程師能力自我評價表)

參考資料

  • 《深入理解計算機系統V3》學習指導
  • ...

轉載于:https://www.cnblogs.com/guyanlin/p/8033903.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/252673.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252673.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252673.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

進程間五種通信方式

進程間通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09;是指在不同進程之間傳播或交換信息。 IPC的方式通常有管道&#xff08;包括無名管道和命名管道&#xff09;、消息隊列、信號量、共享存儲、Socket、Streams等。其中 Socket和Streams支持不同主機…

電子書下載:Silverlight 5 in Action

下載&#xff1a;http://www.ctdisk.com/file/8447319

git 的使用方法

git 的使用有3個主要步驟&#xff1a; 1.1 工作區域操作&#xff1a; 在自己的git賬號下構建一個工作目錄&#xff0c; 并往工作目錄里添加文件內容&#xff08;cp /root/data/VIP_Amount_prediction/* ./&#xff09;。 cd 當前工作目錄&#xff0c; git init&#xff0c; 初始…

Codeforces 898E Squares and not squares

題目大意 給定 $n$&#xff08;$n$ 是偶數&#xff0c;$2\le n\le 2\times 10^{5}$&#xff09;個非負整數 $a_1,\dots, a_n$&#xff08;$a_i\le 10^9$&#xff09;。 要求將其中 $n/2$ 個數變成平方數&#xff0c;另外 $n/2$ 個數變成非平方數&#xff0c;變化后的數必須仍是…

UTC時間

每個地區都有自己的本地時間&#xff0c;在網上以及無線電通信中時間轉換的問題就顯得格外突出。我自己就經常混淆于此&#xff0c;特地研究了一下&#xff0c;記錄在此以備忘。 整個地球分為二十四時區&#xff0c;每個時區都有自己的本地時間。在國際無線電通信場合&#xff…

Virtualbox橋接網卡設置

正常情況下&#xff0c;像設置virtualbox虛擬機的橋接網卡非常簡單&#xff0c;只需要點配置&#xff0c;然后在配置界面點擊網絡&#xff0c;然后在右邊的網絡里選擇橋接網絡即可。但是如果這么簡單就好了&#xff0c;今天要說的就是在不正常的情況下是怎么設置的。 工具/原料…

利用CSS、JavaScript及Ajax實現圖片預加載的三大方法

預加載圖片是提高用戶體驗的一個很好方法。圖片預先加載到瀏覽器中&#xff0c;訪問者便可順利地在你的網站上沖浪&#xff0c;并享受到極快的加載速度。這對圖片畫廊及圖片占據很大比例的網站來說十分有利&#xff0c;它保證了圖片快速、無縫地發布&#xff0c;也可幫助用戶在…

ThinkJS前端搭配vue時的Nginx配置

Thinkjs 作為奇舞團開源的nodejs mvc框架之一&#xff0c;引起了很多NodeJS程序員的親賴。但是其關于靜態文件處理部分支持不夠完善&#xff0c;主要是體現在SPA單頁應用&#xff0c;之前在ThinkJS 2.*版本時寫過一個關于處理單頁應用靜態資源的middleware think-resource-spa,…

SQL疑難雜癥【4 】大量數據查詢的時候避免子查詢

前幾天發現系統變得很慢&#xff0c;在Profiler里面發現有的SQL執行了幾十秒才返回結果&#xff0c;當時的SQL如下&#xff1a; 可以看得出來&#xff0c;在652行用了子查詢&#xff0c;恰巧目標表(QS_WIP)中的記錄數為100000000&#xff0c;通過如下SQL可以得到&#xff1a; S…

2020-11-27

總結各種RGB轉YUV的轉換公式 如果數據位寬都以8位來說.ITU709:允許 0~255之間所有數據 ITU601:只允許 16~235之間數據, 601是SDTV的數據結構&#xff1b; 656是SDTV的interface 709是HDTV的數據結構 &#xff1b;1120是HDTV的interface 最近在學習視頻的顏色空間轉換&#x…

python學習筆記1-基礎語法

1 在3版本中print需要加上括號2 多行語句&#xff1a;用\連接 1 item_one1 2 item_two2 3 item_three3 4 total item_one \ 5 item_two \ 6 item_three 7 print (total) 3 引號   字符串通常在引號中 不管是單引號 雙引號還是三引號   必須保證前后一致…

『原創』一個基于Win CE 5.0的Txt文件閱讀器

最近&#xff0c;拿到一臺親戚送的GPS導航儀&#xff0c;其系統是基于WinCE5.0的&#xff0c;所以我覺得可以寫點小程序上去&#xff0c;上網一搜&#xff0c;還附帶破解方法&#xff0c;把GPS破解后就變成一臺屏幕超大的PDA了&#xff0c;于是我想用它看電子書&#xff0c;無奈…

ARM Cortex-A系列(A53、A57、A73等)處理器性能分類與對比

在如今這個電子產品泛濫的年代&#xff0c;僅僅靠品牌或是外觀已經不足以辨別產品的優劣&#xff0c;其內置的處理器自然也就成為了分辨產品是否高端的標準之一。那么我們今天就不妨好好了解一下近幾年來電子產品中較為主流的RAM處理器。 在這之前讓我們先簡單認識一下處理器的…

批量創建10個系統帳號tianda01-tianda10并設置密碼

#1、添加用戶 useradd tianda01#2、非交互式給密碼 echo "pass"|passwd --stdin tianda#3、01-10 加0思路 (1)echo {00..10}(2)seq -w 10#隨機密碼6種方法 (1)echo $RANDOM | md5sum | cut -c 1-8(2)yum -y install expect mkpasswd -l 12 -d 5 #expect隨機mkpasswd …

DIV常用屬性大全自己整理

一、屬性列表 代碼如下:color : #999999 文字顏色 font-family : 宋體 文字字型 font-size : 10pt 文字大小 font-style:itelic 文字斜體育 font-variant:small-caps 小字體 letter-spacing : 1pt 文字間距 line-height : 200% 設定行高 font-weight:bold 文字粗體 vertical-a…

.NET 3.5 - DLINQ(LINQ to SQL)之面向對象的添加、查詢、更新和刪除

步步為營VS 2008 .NET 3.5(8) - DLINQ(LINQ to SQL)之面向對象的添加、查詢、更新和刪除作者&#xff1a;webabcd介紹以Northwind為示例數據庫&#xff0c;DLINQ(LINQ to SQL)之完全面向對象的添加操作、查詢操作、更新操作和刪除操作示例Sample.aspx <% Page Language&quo…

ARM處理器的分類

對于ARM處理器而言&#xff0c;其目前有Classic系列、Cortex-M系列、Cortex-R系列、Cortex-A系列和Cortex-A50系列5個大類。 Classic系列 該系列處理器由三個子系列組成&#xff1a; ARM7系列&#xff1a;基于ARMv3或ARMv4架構 ARM9系列&#xff1a;基于ARMv5架構 ARM11系列…

Poj 1019

傳送門&#xff1a;http://poj.org/problem?id1019 主要是找數學規律 然后用好pow和log函數&#xff0c;由于數組過大&#xff0c;數組的類型用unsigned 1 #include<iostream>2 #include<cmath>3 using namespace std;4 5 int t;6 int k;7 int n;8 unsigned a[312…

ARM版本系列及家族成員梳理

ARM公司簡介 ARM是Advanced RISC Machines的縮寫&#xff0c;它是一家微處理器行業的知名企業&#xff0c;該企業設計了大量高性能、廉價、耗能低的RISC &#xff08;精簡指令集&#xff09;處理器。 1985年第一個ARM原型在英國劍橋誕生。 公司的特點是只設計芯片&#xff0c…

z-index ie無效

首先來個 解釋了三個原因&#xff1a;http://www.cnblogs.com/hakuci/archive/2011/01/05/1926212.html 我這個還比較特殊 爸爸級別在最底層 遮羞層在中間 兒子最外邊 <div>遮羞層</div> z-index2 <div>爺爺 <div>小爸爸</div> <div>爸…