2017-2018-1 20155227 《信息安全系统设计基础》第十三周学习总结
2017-2018-1 20155227 《信息安全系統(tǒng)設(shè)計基礎(chǔ)》第十三周學習總結(jié)
找出全書你認為最重要的一章,深入重新學習一下,要求(期末占10分): 完成這一章所有習題詳細總結(jié)本章要點給你的結(jié)對學習搭檔講解你的總結(jié)并獲取反饋我選擇教材第九章的內(nèi)容來深入學習,一方面,虛擬內(nèi)存這一部分內(nèi)容本身就十分重要,另一方面,我在操作系統(tǒng)課上對這一部分的知識理解的很淺,在十一周自學教材這一部分內(nèi)容時也因為知識點太多而沒有學得很深入,正好借此機會來重新學習這一章的內(nèi)容,加深理解。
教材學習內(nèi)容總結(jié)
第九章 虛擬存儲器
為了更加有效地管理存儲器且少出錯,現(xiàn)代系統(tǒng)提供了對主存的抽象概念,叫做虛擬存儲器(VM)。
虛擬存儲器是硬件異常,硬件地址翻譯,主存,磁盤文件和內(nèi)核軟件的完美交互。
為每個進程提供一個大的,一致的和 私有的地址空間。
- 提供了3個重要能力。
將主存看成磁盤地址空間的高速緩存。
- 只保留了活動區(qū)域,并根據(jù)需要在磁盤和主存間來回傳送數(shù)據(jù),高效使用主存。
- 為每個進程提供一致的地址空間
- 簡化存儲器管理
- 保護了每個進程的地址空間不被其他進程破壞。
虛擬內(nèi)存在幕后工作,程序員為什么要理解它呢?
- 虛擬存儲器是中心的。
- 虛擬存儲器是強大的。
- 可以創(chuàng)建和銷毀存儲器片(chunk)
- 將存儲器片映射到磁盤文件的某個部分。
- 其他進程共享存儲器。
例子
能讀寫存儲器位置來修改磁盤文件內(nèi)容。 加載文件到存儲器不需要顯式的拷貝。
虛擬存儲器是危險的
- 引用變量,間接引用指正,調(diào)用malloc動態(tài)分配程序,就會和虛擬存儲器交互。
- 如果使用不當,將遇到復雜危險的與存儲器有關(guān)的錯誤。
例子
一個帶有錯誤指針的程序可以立即崩潰于段錯誤或者保護錯誤。運行完成,卻不產(chǎn)生正確結(jié)果。
9.1 物理與虛擬尋址
- 物理地址(Physical Address,PA):計算機系統(tǒng)的主存被組織為M個連續(xù)的字節(jié)大小的單元組成的數(shù)組。每個字節(jié)的地址叫物理地址.
- CPU訪問存儲器的最自然的方式使用物理地址,這種方式稱為物理尋址。
早期的PC,數(shù)字信號處理器,嵌入式微控制器以及Cray超級計算機使用物理尋址。 - 現(xiàn)代處理器使用的是虛擬尋址(virtual addressing)的尋址形式。
- CPU通過生成一個虛擬地址(Virtual address,VA)來訪問主存。
- 將虛擬地址轉(zhuǎn)換為物理地址叫做地址翻譯(address translation)。
- 地址翻譯也需要CPU硬件和操作系統(tǒng)之間的緊密結(jié)合。
CPU芯片上有叫做存儲器管理單元(Memory Management Unit,MMU)的專用硬件。
利用存儲在主存中的查詢表來動態(tài)翻譯虛擬地址。查詢表由操作系統(tǒng)管理。
9.2 地址空間
地址空間(address space)是一個非負整數(shù)地址的有序集合。
如果地址空間中整數(shù)是連續(xù)的,我們說它是線性地址空間(linear address space)。
- 我們總是假設(shè)使用線性地址空間。
- 在一個帶虛擬存儲器的系統(tǒng)中,CPU從一個有N=2^n個地址的地址空間中生成虛擬地址,這個地址空間稱為虛擬地址空間(virtual address space)。
- 一個地址空間大小是由表示最大地址所需要的位數(shù)來描述的。
- 如N=2^n個地址的虛擬地址空間叫做n位地址空間。
- 現(xiàn)在操作系統(tǒng)支持32位或64位。
一個系統(tǒng)還有物理地址空間,它與系統(tǒng)中物理存儲器的M=2^m(假設(shè)為2的冪)個字節(jié)相對應(yīng)。
地址空間的概念很重要,因為它區(qū)分了數(shù)據(jù)對象(字節(jié))和 它們的屬性(地址)。
每個字節(jié)(數(shù)據(jù)對象)一般有多個 獨立的地址(屬性)。每個地址都選自不同的地址空間。
- 字節(jié)有一個在虛擬地址空間的虛擬地址。
- 還有一個在物理地址空間的物理地址。
- 兩個地址都能訪問到這個字節(jié)。
9.3 虛擬存儲器作為緩存的工具
虛擬存儲器(VM) 被組織為一個存放在磁盤上的N個連續(xù)字節(jié)大小的單元組成的數(shù)組。
每個字節(jié)都有一個唯一的虛擬地址,這個虛擬地址作為到數(shù)組的索引。
磁盤上數(shù)組的內(nèi)容被緩存到主存中。
同存儲器層次結(jié)構(gòu)其他緩存一樣,磁盤上的數(shù)據(jù)被分割成塊。
這些塊作為磁盤和主存之間的傳輸單元。虛擬頁(Virtual Page,VP)就是這個塊- 物理存儲器被分割為物理頁,大小也為P字節(jié)`` 也被稱為``頁幀(page frame)。
- 任何時候,虛擬頁的集合都被分為3個不相交的子集。
- 未分配的: VM系統(tǒng)還未分配(或者創(chuàng)建)的頁。未分配的塊沒有任何數(shù)據(jù)與之相關(guān)聯(lián)。
- 不占用磁盤空間
- 通過malloc來分配
- 緩存的: 當前緩存在物理存儲器的已分配頁。
- 未緩存的: 沒有緩存在物理頁面存儲器中的已分配頁。
- 未分配的: VM系統(tǒng)還未分配(或者創(chuàng)建)的頁。未分配的塊沒有任何數(shù)據(jù)與之相關(guān)聯(lián)。
9.3.1 DRAM緩存的組織結(jié)構(gòu)
DRAM表示虛擬存儲器系統(tǒng)的緩存,在主存中緩存虛擬頁,有兩個特點。
DRAM緩存不命中處罰十分嚴重。
- 因為磁盤比DRAM慢100000多倍。
- 訪問一字節(jié)開銷
- 從一個磁盤的一個扇區(qū)讀取第一個字節(jié)的時間開銷要比從該扇區(qū)中讀連續(xù)的字節(jié)慢大約100000倍
DRAM緩存的組織結(jié)構(gòu)由這種巨大的不命中開銷驅(qū)動。因此有以下特點。
虛擬頁往往很大。
- 4KB~2MB
- DRAM緩存是全相聯(lián)
- 任何虛擬頁都能放在任何物理頁中。
- 原因在于大的不命中懲罰
- 更精密的替換算法
- 替換錯了虛擬頁的懲罰很高。
- DRAM緩存總是寫回
- 因為對磁盤的訪問時間很長
- 而不用直寫
9.3.2 頁表
判斷命中和替換由多種軟硬件聯(lián)合提供。
- 操作系統(tǒng)軟件,MMU中的地址翻譯硬件和頁表(page table)。
- 頁表是存放在物理存儲器的數(shù)據(jù)結(jié)構(gòu)。
- 頁表將虛擬頁映射到物理頁。
- 地址翻譯硬件將虛擬地址轉(zhuǎn)換為物理地址都會讀取頁表。
- 操作系統(tǒng)負責維護頁表的內(nèi)容,以及磁盤及DRAM之間來回傳送頁。
- 頁表是存放在物理存儲器的數(shù)據(jù)結(jié)構(gòu)。
- 頁表就是一個頁表條目(Page Table Entry,PTE)的數(shù)組.
- 虛擬地址空間中每個頁在頁表的固定偏移量處都有一個PTE.
每個PTE有一個有效位和n位地址字段。
有效位表明虛擬頁是否被緩存。 如果有效位存在,那么地址字段指向?qū)?yīng)的物理存儲器。 如果有效位不存在。 地址字段要么為NULL,要么指向虛擬頁在磁盤所在的位置。
9.3.3 頁命中
- 一個頁命中的過程。
- 一個虛擬地址轉(zhuǎn)換為物理地址的過程。
9.3.4 缺頁
DRAM緩存不命中稱為缺頁。
處理過程如下:
- 讀取虛擬地址所指向的PT。
- 讀取PTE有效位,發(fā)現(xiàn)未被緩存,觸發(fā)缺頁異常。
- 調(diào)用缺頁異常處理程序
- 選擇犧牲頁。
- 如果犧牲頁發(fā)生了改變,將其拷貝回磁盤(因為是寫回)
- 需要讀取的頁代替了犧牲頁的位置。
- 結(jié)果:犧牲也不被緩存,需要讀取的頁被緩存。
中斷結(jié)束,重新執(zhí)行最開始的指令。
在DRAM中讀取成功。
9.3.5 分配頁面
比如某個頁面所指向地址為NULL,將這個地址指向磁盤某處,那么這就叫分配頁面。
此時虛擬頁從未分配狀態(tài) 變?yōu)?未緩存。
9.4 虛擬存儲器作為存儲器的管理工具
操作系統(tǒng)為每個進程提供一個獨立的頁表。
因此,VM簡化了鏈接和加載,代碼和數(shù)據(jù)共享,以及應(yīng)用程序的存儲器分配。
- 簡化鏈接
- 獨立的空間地址意味著每個進程的存儲器映像使用相同的格式。
- 文本節(jié)總是從0x08048000(32位)處或0x400000(64位)處開始。
- 然后是數(shù)據(jù),bss節(jié),棧。
- 一致性極大簡化了鏈接器的設(shè)計和實現(xiàn)。
- 獨立的空間地址意味著每個進程的存儲器映像使用相同的格式。
- 簡化加載
- 加載器可以從不實際拷貝任何數(shù)據(jù)從磁盤到存儲器。
- 基本都是虛擬存儲系統(tǒng)完成。
簡化共享
獨立地址空間為操作系統(tǒng)提供了一個管理用戶進程和操作系統(tǒng)自身之間的一致共享機制
- 操作相同的操作系統(tǒng)內(nèi)核代碼
- C標準庫的printf.
- 因此操作系統(tǒng)需要將不同進程的適當?shù)奶摂M頁映射到相同的物理頁面。
- 多個進程共享這部分代碼的一個拷貝。
- 而不是每個進程都要加載單獨的內(nèi)核和C標準庫的拷貝。
簡化存儲器分配
即虛擬頁連續(xù)(虛擬頁還是單獨的),物理頁可以不連續(xù)。使得分配更加容易。
9.5 虛擬存儲器作為存儲器保護的工具
任何現(xiàn)代操作系統(tǒng)必須為操作系統(tǒng)提供手段來控制對存儲器系統(tǒng)的訪問。
- 不應(yīng)該允許用戶進程修改它的只讀文本段。
- 不允許它讀或修改任何內(nèi)核的代碼和數(shù)據(jù)結(jié)構(gòu)
- 不允許讀寫其他進程的私有存儲器。
- 不允許修改共享的虛擬頁,除非所有共享者顯示允許這么做(通過調(diào)用明確的進程間通信)
- SUP: 是否只有在內(nèi)核模式下才能訪問?
- READ: 讀權(quán)限。
- WRITE: 寫權(quán)限。
如果指令違反了許可條件,觸發(fā)一般保護性異常,然后交給異常處理程序,Shell一般會報告為段錯誤(segmentaion fault)。
9.6 地址翻譯
- 形式上來說,地址翻譯是一個N元素的虛擬地址空間(VAS)中的元素和一個M元素的物理地址空間(PAS)元素之間的映射,
- 以下展示了MMU(Memory Management Unit,存儲器管理單元)如何利用頁表實現(xiàn)這樣的功能
- 頁表基址寄存器(Page Table Base Register,PTBR)指向當前頁表。
- n位的虛擬地址包含兩個部分
- 一個p位的虛擬頁面偏移(Virtual Page Offset,VPO)
- 一個n-p位的虛擬頁號(Virtual Page Number,VPN)
- 頁面條目 (PTE)中物理頁號(PPN)和虛擬地址中的VPO串聯(lián)起啦,即是物理地址
- PPO和VPO是相同的
- 記VPN,PPN都是塊,都是首地址而已,所以需要偏移地址PPO,VPO
圖(a)展示頁面命中,CPU硬件執(zhí)行過程:
- 第一步:處理器生成虛擬地址,把它傳送給MMU。
- 第二步: MMU生成PTE地址(PTEA),并從高速緩存/主存請求中得到它。
- 第三步: 高速緩存/主存向MMU返回PTE。
- 第四步: MMU構(gòu)造物理地址(PA),并把它傳送給高速緩存/主存。
- 第五步: 高速緩存/主存返回所請求的數(shù)據(jù)字給處理器。
頁面命中完全由硬件處理,與之不同的是,處理缺頁需要 硬件和操作系統(tǒng)內(nèi)核協(xié)作完成。
- 第一到三步: 與命中時的一樣
- 第四步:PTE有效位是零,所以MMU觸發(fā)異常,傳遞CPU中的控制到操作系統(tǒng)內(nèi)核中的 缺頁異常處理程序。
- 第五步:缺頁異常處理程序確定出物理存儲頁中的犧牲頁,如果這個頁面已經(jīng)被修改,則把它換出到磁盤。
- 第六步:缺頁異常處理程序調(diào)入新的頁面,并更新存儲器中的PTE。
- 第七部:缺頁異常處理程序返回到原來的進程,再次執(zhí)行導致缺頁的指令,之后就是頁面命中一樣的步驟。
9.6.1 結(jié)合高速緩存和虛擬存儲器
在任何使用虛擬存儲器又使用SRAM高速緩存的系統(tǒng)中,都存在應(yīng)該使用虛擬地址 還是 使用 物理地址 來訪問SRAM高速緩存的問題。
大多數(shù)系統(tǒng)是選擇物理尋址。
- 使用物理尋址,多個進程同時在高速緩存中有存儲塊和共享來自相同虛擬頁面的塊成為簡單的事。
- 而且還無需處理保護問題,因為 訪問權(quán)限的檢查在地址翻譯中(PTE)的一部分。
- 以下是一個例子(將PTE進行高速緩存)。
9.6.2 利用TLB加速地址翻譯
每次CPU產(chǎn)生一個虛擬地址,MMU就必須查閱一個PTE,以便將虛擬地址翻譯為 物理地址。
- 在最糟糕的情況下,會從內(nèi)存中取數(shù)據(jù),代價是幾十 到幾百個周期
- 如果PTE碰巧緩存在L1中,那么開銷就下降到一到兩個周期
許多系統(tǒng)都試圖消除這樣的開銷,他們在MMU中包含了一個關(guān)于PTE的小緩存,稱為翻譯后備緩沖器(Translation Lookaside Buffer,TLB)。
- TLB是一個小的,虛擬尋址的緩存。
- 每一行都保存著一個由單個PTE組成的塊。
- TLB通常用于高度的相連性
- 如圖所示- 用于組選擇和行匹配的索引和標記字段是從虛擬地址中的虛擬頁號中提取出來的。
- 如果TLB有T=2^t個組
- 那么TLB索引(TLBI)是由VPN的t個最低位組成。(對應(yīng)于VPO)
- TLB標記(TLBT)是由VPN中剩余位組成(對應(yīng)于VPN)
- 下圖展示了TLB命中步驟
- 關(guān)鍵點:所有的地址翻譯步驟都是在芯片上的MMU中執(zhí)行的,因此非常快
- TLB命中
- 第一步:CPU產(chǎn)生虛擬地址。
- 第二步和第三部:MMU從TLB取出對應(yīng)的PTE。
- 第四步:MMU將這個虛擬地址翻譯成一個物理地址,發(fā)送到高速緩存/主存
- 第五步:高速緩存/主存所請求的數(shù)據(jù)字返回給CPU
- 當TLB不命中的時候,MMU必須從L1緩存或內(nèi)存中取出相應(yīng)的PTE,并進行類似缺頁處理過程。
9.6.3 多級頁表
如果我們有一個32位地址空間,4KB大小的頁面(p=2^12)和一個4B的PTE,即使應(yīng)用所引用的只是虛擬地址空間中很小的一部分,也總是需要一個4MB的頁表駐留在存儲器中。
所以多級頁表的誕生用于解決在很少使用時有一個很大的頁表常駐于內(nèi)存。
用來壓縮頁表的常用方式是使用層次結(jié)構(gòu)的頁表。
以下用上圖的兩層作為例子。
- 總共有9KB個頁面,PTE為4個字節(jié)。
- 前2KB個頁面分配給代碼和數(shù)據(jù)。
- 接下來6KB個頁面未分配
- 再接下來1023個頁面也未分配
- 接下一個頁面分配給用戶棧
- 一級頁表中的每個PTE負責映射虛擬地址空間中一個4MB大小的片(chunk).
- 每一個片都是由1024個連續(xù)的頁面組成。
- 4MB=1024個頁面*PTE大小4字節(jié)。
- 如果片i中每個頁面都沒有分配,那么一級PTE i就為空。
- 例如圖中的PTE 2~PTE 7
- 但是如果片i中有一個被分配了,那么PTE i就不能為空。
- 這種方法從兩個方面減少了存儲器要求。
- 如果一級頁表PTE為空,那么相應(yīng)的二級頁表就根本不會存在。
- 一種巨大的潛在節(jié)約,大部分時候內(nèi)存都是未分配的。
- 如果一級頁表PTE為空,那么相應(yīng)的二級頁表就根本不會存在。
- 只有一級頁表才需要總是在主存中。
- 虛擬存儲器系統(tǒng)可以在需要時創(chuàng)建,頁面調(diào)入,調(diào)出二級頁面,減少主存壓力。
k級頁表層次結(jié)構(gòu)的地址翻譯。
- 虛擬地址被分為k個VPN和一個VPO。每個VPN i都是i-1級頁表到i級頁表的索引。
- PPN存于k級頁表。
- PPO依舊與VPO相同。
此時TLB能發(fā)揮作用,因為層次更細,更利于緩存。使得多級頁表的地址翻譯不比單級頁表慢很多。
9.6.4 綜合:端到端的地址翻譯
一個在有一個TLB和L1 d-cache的小系統(tǒng)上。作出如下假設(shè):
- 存儲器都是按字節(jié)尋址的。
- 存儲器訪問是針對一字節(jié)的字的。
- 虛擬地址是14位長(n=14)
- 物理地址是12位長(m=12)
- 頁面大小是64字節(jié)(P=2^6)
- TLB是四路組相連的,總共有16個條目
- L1 d-cache是物理尋址,高速緩存,直接映射(E=1)的,行大小為4字節(jié),而總共有16個組。
存儲結(jié)構(gòu)快照:
- TLB: TLB利用VPN的位進行緩存。
- 頁表: 這個頁表是一個單級設(shè)計。一個有256個,但是這里只列出16個。
- 高速緩存:直接映射的緩存通過物理地址的字段來尋址。
- 因為是直接映射,通過索引就能直接找到。且E=1。
- 直接能判定是否命中。
9.7 案例研究: Intel Core i7/Linux 存儲器系統(tǒng)
處理器包(processor package)
- 四個核
- 層次結(jié)構(gòu)的TLB
- 虛擬尋址
- 四路組相連
- Linux 一頁4kb
- 層次結(jié)構(gòu)的數(shù)據(jù)和指令高速緩存。
- 物理尋址
- L1,L2 八路組相連
- L3 十六路組相連
- 塊大小64字節(jié)
- 快速的點到點鏈接。
- 基于Intel QuickPath技術(shù)
- 為了讓核與其他核和外部I/O橋直接通信
- 層次結(jié)構(gòu)的TLB
- L3高速緩存
- DDR3存儲器控制器
9.7.1 Core i7地址翻譯
上圖完整總結(jié)了Core i7地址翻譯過程,從虛擬地址到找到數(shù)據(jù)傳入CPU。
- Core i7采用四級頁表層次結(jié)構(gòu)。
- CR3 控制寄存器指向第一級頁表(L1)的起始位置
- CR3也是每個進程上下文的一部分。
- 上下文切換的時候,CR3也要被重置。
- CR3 控制寄存器指向第一級頁表(L1)的起始位置
一級,二級,三級頁表PTE的格式:
P=1時 地址字段包含了一個40位物理頁號(PPN),指向適當?shù)捻摫黹_始處。
- 強加了一個要求,要求物理頁4kb對齊。
- 因為PPO為12位 = 4kb
- PPO的大小就跟物理頁的大小有關(guān)。
四級頁表的PTE格式:
- PTE有三個權(quán)限位,控制對頁的訪問
- R/W位確定頁的內(nèi)容是可以 讀寫還是 只讀。
- U/S位確定用戶模式是否能夠訪問,從而保護操作系統(tǒng)內(nèi)核代碼不被用戶程序訪問。
- XD (禁止執(zhí)行) 位是在64位系統(tǒng)引入,禁止某些存儲器頁取指令。
- 這是一個重要的新特性,限制只能執(zhí)行只讀文本段,降低緩沖區(qū)溢出的風險。
- 當MMU翻譯虛擬地址時,還會更新兩個內(nèi)核缺頁處理程序會用到的位。
- A位
- 每次訪問一個頁,MMU都會設(shè)置A位,稱為引用位(reference bit).
- 可以利用這個引用位來實現(xiàn)它的頁替換算法。
- D位
- 每次對一個頁進行了寫 就會設(shè)置D位,又稱臟位(dirty bit).
- 臟位告訴內(nèi)核在拷貝替換頁前是否要寫回。
- 內(nèi)核通過調(diào)用一條特殊的內(nèi)核模式指令來清除引用位或臟位。
- A位
四級頁表如何將VPN翻譯成物理地址
- 每個VPN被用作頁表的偏移量。
- CR3寄存器包含L1頁的物理地址
9.7.2 Linux 虛擬存儲系統(tǒng)
內(nèi)核虛擬存儲器
- 內(nèi)核虛擬存儲器包含內(nèi)核中的代碼和數(shù)據(jù)。
- 內(nèi)核虛擬存儲器的某些區(qū)域被映射到所有進程共享的物理頁面
- 如:內(nèi)核代碼,全局數(shù)據(jù)結(jié)構(gòu)。
- Linux也將一組連續(xù)的虛擬頁面(大小等同于系統(tǒng)DRAM總量)映射到相應(yīng)的一組物理頁面。
- 內(nèi)核虛擬存儲器的某些區(qū)域被映射到所有進程共享的物理頁面
- 內(nèi)核虛擬存儲器包含每個進程不相同的數(shù)據(jù)。
- 頁表,內(nèi)核在進程上下文中時使用的棧,等等。
Linux 虛擬存儲器區(qū)域
Linux將虛擬存儲器組織成一些區(qū)域(也叫做段)的集合。
- 一個區(qū)域就是已經(jīng)存在著的(已分配的) 虛擬存儲器的連續(xù)片,這些片/頁已某種形式相關(guān)聯(lián)。
- 代碼段,數(shù)據(jù)段,堆,共享庫段,用戶棧。
- 所有存在的虛擬頁都保存在某個區(qū)域。
- 區(qū)域的概念很重要
- 允許虛擬地址空間有間隙。
一個進程中虛擬存儲器的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
內(nèi)核為系統(tǒng)中每個進程維護了一個單獨的任務(wù)結(jié)構(gòu)。任務(wù)結(jié)構(gòu)中的元素包含或指向內(nèi)核運行該進程所需要的全部信息。
- task_struct
- mm_struct
- 描述了虛擬存儲器的當前狀態(tài)。
- pgd
- 指向第一級頁表的基址。
- 當進程運行時,內(nèi)核將pgd存放在CR3控制寄存器
- mmap
- vm_area_structs(區(qū)域結(jié)構(gòu))
- 每個vm_area_structs都描述了當前虛擬地址空間的一個區(qū)域(area).
- vm_start:指向這個區(qū)域的起始處。
- vm_end:指向這個區(qū)域的結(jié)束處。
- vm_port:描述這個區(qū)域內(nèi)包含的所有頁的讀寫許可權(quán)限。
- vm_flags:描述這個區(qū)域頁面是否與其他進程共享,還是私有。
- vm_next: 指向鏈表的下一個區(qū)域。
- mm_struct
Linux 缺頁異常處理
MMU在試圖翻譯虛擬地址A時,觸發(fā)缺頁 。這個異常導致控制轉(zhuǎn)移到缺頁處理程序,執(zhí)行一下步驟。
- 虛擬地址A是合法的嗎?
- A在某個區(qū)域結(jié)構(gòu)定義的區(qū)域內(nèi)嗎?
解決方法:
- 缺頁處理程序 搜索區(qū)域結(jié)構(gòu)鏈表。
- 把A和每個區(qū)域的vm_start 和vm_end 做比較。
- 通過某種樹 的數(shù)據(jù)結(jié)構(gòu)算法查找
如果不合法 ,觸發(fā)段錯誤 。
- 試圖訪問的存儲器是否合法?
- 即是否有讀,寫,執(zhí)行這個頁面的權(quán)限?
- 如果不合法,觸發(fā)保護異常,終止進程。
?
- 一切正常的話
- 選擇犧牲頁,替換,重新執(zhí)行指令
9.8 存儲器映射
存儲器映射: Linux通過將一個虛擬存儲器區(qū)域與一個磁盤上的對象關(guān)聯(lián)起來,以初始化這個虛擬存儲器區(qū)域的內(nèi)容,這個過程叫做存儲器映射。
虛擬存儲器區(qū)域可以映射到以下兩種類型文件。
- Unix文件系統(tǒng)中的普通文件:一個區(qū)域可以映射到一個普通磁盤文件的連續(xù)部分。
- 例如,一個可執(zhí)行文件。
- 文件區(qū)(section)被分成頁大小的片,每一片包含一個虛擬頁面的初始化內(nèi)容。
- 僅僅是初始化,虛擬頁面此時還并未進入物理存儲器。
- 直到CPU第一次引用這個頁面。
- 匿名文件 : 一個區(qū)域可以映射到一個匿名文件。
- 匿名文件由內(nèi)核創(chuàng)建,包含的全是二進制零。
- CPU第一次引用這樣區(qū)域(匿名文件)的虛擬頁面時。
- 將存儲器中犧牲頁面全部用二進制零覆蓋。
- 并將虛擬頁面標記為駐留在存儲器中。
- 注意: 實際上,虛擬頁面并沒有跟存儲器進行數(shù)據(jù)傳送。
又叫請求二進制零的頁(demand-zero page)。
交換文件,交換空間。(win下叫做paging file)
- 一旦一個虛擬頁面被初始化了,它就在一個由內(nèi)核維護的專門的交換文件(swap file)之間換來換去。交換文件也叫交換空間或者交換區(qū)域。
- 需要意識到,在任何時刻,交換空間都限制著當前運行著的進程分配的虛擬頁面總數(shù)。
9.8.1 再看共享對象
共享對象的由來
- 許多進程有同樣的只讀文本區(qū)域。
- printf
- 運行Uinx shell的tcsh```
- 如果每個進程都加載進內(nèi)存一次,極其浪費。
- 存儲器映射提供一種機制,來共享對象。
一個對象被映射到虛擬存儲器的一個區(qū)域,一定屬于以下兩種。
- 共有對象
- 一個進程將一個共有對象映射到它的虛擬地址空間的一個區(qū)域。
- 進程對這個區(qū)域的寫操作,對于那些也把這個共享對象映射它的虛擬存儲器的進程是可見的。
- 這些變化也會反映到磁盤上的原始對象。
- 映射到的虛擬存儲器那個區(qū)域叫做共享區(qū)域。
- 一個進程將一個共有對象映射到它的虛擬地址空間的一個區(qū)域。
- 私有對象
- 對一個映射到私有對象的區(qū)域做出的改變,對于其他進程不可見.
- 并且進行的寫操作不會反映到磁盤上。
- 映射到的虛擬存儲器那個區(qū)域叫做私有區(qū)域。
9.8.1.1 共享對象
進程1,將共享對象映射到虛擬存儲器中,然后虛擬存儲器將這一段找一塊物理存儲器存儲。
- 當進程2也要引用同樣的共享對象時。
- 內(nèi)核迅速判定,進程1已經(jīng)映射了這個對象。
- 使進程2的虛擬存儲器直接指向了那一塊進程1指向的物理存儲器。
- 即使對象被映射到多個共享區(qū)域,物理存儲器依舊只有一個共享對象的拷貝。
- 大大解決了物理存儲器內(nèi)存。
9.8.1.2 私有對象
私有對象使用一種叫做寫時拷貝(conpy-on-write)的巧妙技術(shù)。
- 私有對象開始生命周期的方式基本與共享對象一樣。
- 即使對象被多個引用,在物理內(nèi)存都只保留一個拷貝。
- 對于每個映射私有對象的進程,相應(yīng)私有區(qū)域的頁表條目都被標記為只讀。
- 并且區(qū)域結(jié)構(gòu)(vm_area_structs)被標記為私有的寫時拷貝。
- 過程:只要有進程試圖寫私有區(qū)域內(nèi)的某個頁面,那么這個寫操作觸發(fā)保護異常。
- 故障處理程序會在物理存儲器中創(chuàng)建被修改頁面的一個新拷貝。
- 更新頁表條目(PTE)指向這個新的拷貝,恢復被修改頁面的可寫權(quán)限。
- 故障處理程序返回,CPU重新執(zhí)行這個寫操作。
- 通過延遲私有對象中的拷貝直到最后可能的時刻,寫時拷貝充分使用了稀缺的物理存儲器
9.8.2 再看fork函數(shù)
了解fork函數(shù)如何創(chuàng)建一個帶有自己獨立虛擬地址空間的新進程。
- 當fork函數(shù)被當前進程調(diào)用時。
- 內(nèi)核為新進程創(chuàng)建內(nèi)核數(shù)據(jù)結(jié)構(gòu),并分配給它唯一一個PID。
- 為了給新進程創(chuàng)建虛擬存儲器。
- 創(chuàng)建了當前進程的mm_struct,區(qū)域結(jié)構(gòu)和頁表的原樣拷貝。
- 將兩個進程的每個頁面都標記為只讀。并給兩個區(qū)域進程的每個區(qū)域結(jié)構(gòu)都標記為私有的寫時拷貝。
- 注意:并沒有對物理存儲器進行拷貝哦,利用的是私有對象的寫時拷貝技術(shù)。
- 當fork函數(shù)在新進程返回時。
- 新進程現(xiàn)在的虛擬存儲器剛好和調(diào)用fork時存在的虛擬存儲器相同。
- 當兩個進程中任一個需要被寫時,觸發(fā)寫時拷貝機制。
9.8.3 再看execve函數(shù)
理解execve函數(shù)實際上如何加載和運行程序。
- 假設(shè)運行在當前的進程中的程序執(zhí)行了如下的調(diào)用:
- Execve("a.out",NULL,NULL);
- execve函數(shù)在當前進程加載并執(zhí)行目標文件a.out中的程序,用a.out代替當前程序。
- 加載并運行需要以下幾個步驟。
- 刪除已存在的用戶區(qū)域。
- 刪除當前進程虛擬地址的用戶部分中已存在的區(qū)域結(jié)構(gòu)。
- 映射私有區(qū)域。
- 為新程序的文本,數(shù)據(jù),bss和棧區(qū)域創(chuàng)建新的區(qū)域結(jié)構(gòu)。
- 所有新的區(qū)域結(jié)構(gòu)都是私有的,寫時拷貝的。
- 文本和數(shù)據(jù)區(qū)域被映射到a.out文件中的文件和數(shù)據(jù)區(qū)。
- bss區(qū)域是請求二進制零,映射到匿名文件。
- 大小包含在a.out中
- 堆,棧區(qū)域也是請求二進制零。
- 為新程序的文本,數(shù)據(jù),bss和棧區(qū)域創(chuàng)建新的區(qū)域結(jié)構(gòu)。
- 映射共享區(qū)域
- a.out程序與共享對象鏈接。
- 這些對象都是動態(tài)鏈接到這個程序。
- 然后映射到用戶虛擬地址的共享區(qū)域。
- a.out程序與共享對象鏈接。
- 設(shè)置程序計數(shù)器(PC)
- execve最后一件事設(shè)置PC指向文本區(qū)域的入口點。
- 刪除已存在的用戶區(qū)域。
- 加載并運行需要以下幾個步驟。
9.8.4 使用mmap函數(shù)的用戶級存儲器映射
Unix進程可以使用mmap函數(shù)來創(chuàng)建新的虛擬存儲器區(qū)域,并將對象映射到這些區(qū)域中。
#include <unistd.h> #include <sys/mman.h>void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);返回:若成功時則為指向映射區(qū)域的指正,若出錯則為MAP_FAILED(-1).參數(shù)解釋:
fd,start,length,offset:
mmap函數(shù)要求內(nèi)核創(chuàng)建一個新的虛擬存儲器區(qū)域,最好是從地址start開始的一個區(qū)域,并將文件描述符fd指定的對象的一個連續(xù)的片chunk映射到這個新的區(qū)域。
- 連續(xù)對象片大小為length字節(jié)
- 從據(jù)文件開始處偏移量為offset字節(jié)的地方開始。
- statr地址僅僅是個暗示
- 一般被定義為NULL,讓內(nèi)核自己安排。
prot
參數(shù)prot包含描述新映射的虛擬存儲器區(qū)域的訪問權(quán)限位。(對應(yīng)區(qū)域結(jié)構(gòu)中的vm_prot位)
- PROT_EXEC:這個區(qū)域內(nèi)的頁面由可以被CPU執(zhí)行的指令組成。
- PROT_READ:這個區(qū)域內(nèi)的頁面可讀。
-PROT_WRITE:這個區(qū)域內(nèi)的頁面可寫。 - PROT_NONE: 這個區(qū)域內(nèi)的頁面不能被訪問。
flag
參數(shù)flag由描述被映射對象類型的位組成。
- MAP_ANON標記位:映射對象是一個匿名對象。
- MAP_PRIVATE標記位:被映射對象是一個私有的,寫時拷貝的對象。
- MAP_SHARED標記位:被映射對象是一個共享對象。
9.9 動態(tài)存儲器分配
雖然可以使用更低級的mmap和munmap函數(shù)來創(chuàng)建和刪除虛擬存儲器的區(qū)域。
但是C程序員還是覺得用動態(tài)存儲器分配器(dynamic memory allocator)更方便。
- 動態(tài)存儲器分配器維護著一個進程的虛擬存儲區(qū)域,稱為堆(heap)。
- 系統(tǒng)之間細節(jié)不同,但是不失通用型。
- 假設(shè)
- 堆是一個請求二進制零的區(qū)域。
- 緊接著未初始化的bss區(qū)域,并向上生長(向更高的地址)。
- 對于每個進程,內(nèi)核維護一個變量brk(break),指向堆頂。
- 分配器將堆視為一組不同大小的塊block的集合來維護。
- 每個塊就是一個連續(xù)的虛擬存儲器片,即頁面大小。
- 要么是已分配,要么是空閑。
- 已分配
- 已分配的塊顯式地保留供應(yīng)用程序使用。
- 已分配的塊保持已分配狀態(tài),直到它被釋放。
- 這種釋放要么是應(yīng)用程序顯示執(zhí)行。
- 要么是存儲器分配器自身隱式執(zhí)行(JAVA)。
- 已分配的塊顯式地保留供應(yīng)用程序使用。
- 空閑
- 空閑塊可用于分配。
- 空閑塊保持空閑,直到顯式地被應(yīng)用分配。
- 已分配
- 分配器有兩種基本分格。
- 都要求應(yīng)用顯式分配
- 不同之處在于那個實體負責釋放已分配的塊
- 顯式分配器(explict allocator)
- 要求應(yīng)用程序顯式地釋放
- C語言中提供一種叫malloc程序顯示分配器
- malloc和free
- C++
- new和delete
- 隱式分配器(implicit allocator)
- 要求分配器檢測一個已分配塊何時不再被程序所使用,那么就釋放這個塊。
- 隱式分配器又叫做垃圾收集器(garbage collector).
- 自動釋放未使用的已分配的塊的過程叫做垃圾收集(garbage collection).
-Lisp,ML以及Java等依賴這種分配器。
9.9.1 malloc和free 函數(shù)
malloc
C標準庫提供了一個稱為malloc程序包的顯示分配器。
#include<stdlib.h> void* malloc(size_t size);返回:成功則為指針,失敗為NULL- malloc 返回一個指針,指向大小為至少size字節(jié)的存儲器塊。
- 不一定是size字節(jié),很有可能是4或8的倍數(shù)
- 這個塊會為可能包含在這個塊內(nèi)的任何數(shù)據(jù)對象類型做對齊。
- Unix系統(tǒng)用8字節(jié)對齊。
- malloc不初始化它返回的存儲器。
- 如果想要初始化,可以用calloc函數(shù)。
- calloc是malloc一個包裝函數(shù)。
- 如果想要初始化,可以用calloc函數(shù)。
- 想要改變已分配塊大小。
- 用realloch函數(shù)
- 不一定是size字節(jié),很有可能是4或8的倍數(shù)
- 如果malloc遇到問題。
- 返回NULL, 并設(shè)置errno。
動態(tài)存儲分配器,可以通過使用mmap和munmap函數(shù),顯示分配和釋放堆存儲器。
- 或者可以使用sbrk函數(shù)。
free
程序通過調(diào)用free函數(shù)來釋放已分配的堆塊。
#include<stdlib.h>void free(void *ptr);返回:無- ptr參數(shù)必須指向一個從malloc,calloc,realloc獲得的已分配塊的起始位置。
- 如果不是,那么free行為未定義。
- 更糟糕的是,free沒有返回值,不知道是否錯了。
9.9.2 為什么要使用動態(tài)存儲器分配
程序使用動態(tài)存儲器分配的最重要原因是:
- 經(jīng)常直到程序?qū)嶋H運行時,它們才知道某些數(shù)據(jù)結(jié)構(gòu)的大小。
9.9.3 分配器的要求和目標
顯式分配器有如下約束條件
- 處理任意請求序列。
- 立即響應(yīng)請求。
- 不允許為提高性能重新排列或緩沖請求。
- 只使用堆。
- 對齊塊。
- 上文的8字節(jié)。
- 不修改已分配的塊。
目標
吞吐率最大化和存儲器使用率最大化。這兩個性能要求通常是相互沖突的。
- 目標1:最大化吞吐率
- 假定n個分配和釋放請求的某種序列R1,R2,R3.....Rn
- 吞吐率 :每個單位時間完成的請求數(shù)。
- 通過使分配和釋放請求的平均時間最小化 來最大化吞吐率
- 假定n個分配和釋放請求的某種序列R1,R2,R3.....Rn
- 目標2:最大化存儲器利用率
- 設(shè)計優(yōu)秀的分配算法。
- 需要增加分配和釋放請求的時間。
- 評估使用堆的效率,最有效的標準是峰值利用率(peak utilization)
- 吞吐率和存儲器利用率是相互牽制的,分配器設(shè)計的一個有趣的挑戰(zhàn)就是在兩者之間找到一個平衡。
9.9.4 碎片
造成堆利用率很低的主要原因是一種稱為碎片(fragmentation)的現(xiàn)象。
碎片:雖然有未使用的存儲器但不能滿足分配要求時的現(xiàn)象。
- 內(nèi)部碎片:已分配塊比有效載荷(實際所需要的)大時發(fā)生。
- 比如:上文中只要5個字(有效載荷),卻給了6個字(已分配塊),那一個多的就是碎片.
- 任何時刻,內(nèi)部碎片的數(shù)量取決于以前請求的模式和分配器的實現(xiàn)方式。
- 可計算的,可量化的。
- 外部碎片:當空閑存儲器合計起來足夠滿足一個分配請求,但是沒有一個單獨的空閑塊足夠大可以處理這個請求發(fā)生的。
- 外部碎片的量化十分困難。
- 不僅取決于以前請求的模式和分配器的實現(xiàn)方式,還要知道將來請求的模式。
- 外部碎片的量化十分困難。
- 內(nèi)部碎片:已分配塊比有效載荷(實際所需要的)大時發(fā)生。
9.9.5 實現(xiàn)問題
一個實際的分配器要在吞吐率和利用率把握平衡,必須考慮一下幾個問題。
- 空閑塊組織: 如何記錄空閑塊? ( 9.9.6)
- 放置: 如何選擇一個合適的空閑快來放置一個新分配的塊? (9.9.7)
- 分割: 將一個新分配的塊放入某個空閑塊后,如何處理這個空閑快中的剩余部分?(9.9.8)
9.9.6 隱式空閑鏈表
將堆組織為一個連續(xù)的已分配塊和空閑塊的序列。
這種結(jié)構(gòu)就叫做隱式空閑鏈表
- 隱式 :
- 為什么叫隱式鏈表。
- 因為不是通過指針(next)來鏈接起來。
- 而是通過頭部的長度隱含地鏈接起來。
- 終止頭部(類似與普通鏈表的NULL)
- 已分配,大小為零的塊
- 為什么叫隱式鏈表。
- 優(yōu)缺點:
- 優(yōu)點:簡單
- 缺點1:任何操作的開銷都與已分配塊和空閑塊的總數(shù)呈線性關(guān)系O(N).
- 放置分配的塊。
- 對空閑鏈表的搜索。
- 缺點2: 即使申請一個字節(jié),也會分配2個字的塊。空間浪費。
9.9.7 放置已分配的塊
有以下幾種搜索放置策略:
- 首次適配
- 從頭開始搜索空閑鏈表,選擇第一個合適的空閑塊。
- 下一次適配
- 和首次適配很類似,但不是從頭開始,而是從上一次查詢的地方開始。
- 最佳適配
- 檢查每個空閑塊,找一個滿足條件的最小的空閑塊(貪心)。
優(yōu)缺點
- 首次適配
- 優(yōu)點
- 往往將大的空閑塊保留在鏈表后面。
- 缺點
- 小的空閑塊往往在前面,增大了對較大快的搜索時間。
- 優(yōu)點
- 下一次適配
- 優(yōu)點
- 速度塊。
- 缺點
- 存儲器利用率低
- 優(yōu)點
- 最佳適配
- 優(yōu)點
- 利用率高
- 缺點
- 要完整搜索鏈表,速度慢。
- 優(yōu)點
9.9.8 分割空閑塊
兩種策略:
- 占用所有空閑塊
- 缺點:產(chǎn)生更多的內(nèi)部碎片(但是如果內(nèi)部碎片很少,可以接受)
- 優(yōu)點:能使得 空閑塊+已分配塊的數(shù)量減少
- 能加快搜索速度。
- 有的外部碎片(幾個字節(jié),很有可能是外部碎片)可能根本放置不了東西,但是卻占用了搜索時間,還不如當內(nèi)部碎片算了
- 放置策略趨向于產(chǎn)生好的匹配中使用。
- 即占用所有空閑塊,內(nèi)部碎片也很少。
- 分割空閑塊
- 缺點:更多的空閑塊和已分配塊,搜索速度降低。
- 優(yōu)點:空間利用率更高。
9.9.9 獲取額外的堆存儲器
如果分配器不能為請求塊找到合適的空閑塊將發(fā)生什么?
- 合并相鄰的空閑塊。
- sbrk函數(shù)
- 在最大化合并還不行的情況。
- 向內(nèi)核請求額外的堆存儲器。
- 并將其轉(zhuǎn)為大的空閑塊
- 將塊插入鏈表。
9.9.10 合并空閑塊
假碎片: 因為釋放,使得某些時候會出現(xiàn)相鄰的空閑塊。
- 單獨的放不下請求(碎片),合并卻可以(假性),所以叫假碎片。
何時合并?
- 立即合并
- 定義:塊被釋放時,合并所有相鄰的塊。
- 缺點:對于某些請求模式,會產(chǎn)生抖動。
- 推遲合并
- 定義: 一個稍晚的時候,再合并。
- 比如:上文中的找不到合適空閑塊的時候。
- 定義: 一個稍晚的時候,再合并。
9.10 GC_垃圾收集
垃圾收集器(garbage collector)是一種動態(tài)存儲分配器。
- 垃圾: 它自動釋放不再需要的已分配塊,這些塊稱為垃圾(garbage).
- 垃圾收集(garbage collection) :自動回收堆存儲的過程叫做垃圾收集。
- 應(yīng)用顯式分配堆塊,但從不顯式釋放堆塊。
- 垃圾收集器定期識別垃圾快,并調(diào)用相應(yīng)地free,將這些快放回空閑鏈表。
垃圾收集可以追溯到John McCarthy在20世紀60年代早期在MIT開發(fā)的Lisp系統(tǒng)。
- 它是Java,ML,Perl和Mathematic等現(xiàn)代語言系統(tǒng)的一個重要部分。
- 有關(guān)文獻描述了大量的垃圾收集方法,數(shù)量令人吃驚。
- 我們討論局限于McCarthy自創(chuàng)的Mark&Sweep(標記&清除)算法。
- 它可以建立已存在的malloc包的基礎(chǔ)上,為C和C++提供垃圾收集。
9.11 C程序中常見的與存儲器有關(guān)的錯誤
9.11.1 間接引用壞指正
scanf("%d",&val); scanf("%d",val);- 最好的情況 : 以異常中止。
- 有可能覆蓋某個合法的讀/寫區(qū)域,造成奇怪的困惑的結(jié)果。
9.11.2 讀未初始化的存儲器
堆存儲器并不會初始化。
- 正確做法
- 使用calloc.
- 顯示y[i]=0;
9.11.3 允許棧緩沖區(qū)溢出
程序不檢查輸入串的大小就寫入棧中的目標緩沖區(qū)
- 那么就有緩沖區(qū)溢出錯誤(buffer overflow bug)。
- gets()容易引起這樣的錯誤
- 用fgets()限制大小。
9.11.5 越界
9.11.6 引用指針,而不是它所指向的對象
對指針的優(yōu)先級用錯。
9.11.7 誤解指針的運算
忘記了指針的算術(shù)操作是以它們指向的對象的大小為單位來進行的,這種大小不一定是字節(jié)。
9.11.8 引用不存在的變量
返回一個指針,指向棧里面一個變量的地址。但是這個變量在返回的時候已經(jīng)從棧里被彈出。
- 地址是正確的,指向了棧。
- 但是卻沒有指向想指向的變量。
9.11.9 引用空閑堆塊的數(shù)據(jù)
引用了某個已經(jīng)free掉的塊。在C++多態(tài)中經(jīng)常容易犯這個錯誤。
9.11.10 引起存儲器泄露
- 即是沒有回收垃圾。導致內(nèi)存中垃圾越來越多。
- 只有重啟程序,才能釋放。
- 對于守護進程和服務(wù)器這樣的程序,存儲器泄露是十分嚴重的事。
- 因為一般情況,不能隨便重啟。
9.12 小結(jié)
虛擬存儲器是對主存的一個抽象。
- 使用一種叫虛擬尋址的間接形式來引用主存。
- 處理器產(chǎn)生虛擬地址,通過一種地址翻譯硬件來轉(zhuǎn)換為物理地址。
- 通過使用頁表來完成翻譯。
- 又涉及到各級緩存的應(yīng)用。
- 頁表的內(nèi)容由操作系統(tǒng)提供
- 通過使用頁表來完成翻譯。
- 處理器產(chǎn)生虛擬地址,通過一種地址翻譯硬件來轉(zhuǎn)換為物理地址。
虛擬存儲器提供三個功能
- 它在主存中自動緩存最近使用的存放在磁盤上的虛擬地址空間內(nèi)容。
- 虛擬存儲器緩存中的塊叫做頁
- 簡化了存儲器管理,
- 進而簡化了鏈接
- 進程間共享數(shù)據(jù)。
- 進程的存儲器分配以及程序加載。
- 每條頁表條目里添加保護位,從而簡化了存儲器保護。
地址翻譯的過程必須和系統(tǒng)中所有的硬件緩存的操作集合。
- 大多數(shù)條目位于L1高速緩存中。
- 但是又通過一個TLB的頁表條目的片上高速緩存L1。
現(xiàn)代系統(tǒng)通過將虛擬存儲器片和磁盤上的文件片關(guān)聯(lián)起來,以初始化虛擬存儲器片,這個過程叫做存儲器映射。
存儲器映射為共享數(shù)據(jù),創(chuàng)建新的進程 以及加載數(shù)據(jù)提供一種高效的機制。
- 可以用mmap 手工維護虛擬地址空間區(qū)域。
- 大多數(shù)程序依賴于動態(tài)存儲器分配,例:malloc
- 管理虛擬地址空間一個稱為堆的區(qū)域
- 分配器兩種類型。
- 顯示分配器
- C,C++
- 隱式分配器
- JAVA
- 顯示分配器
- 大多數(shù)程序依賴于動態(tài)存儲器分配,例:malloc
GC是通過不斷遞歸訪問指針來標記已分配塊,在需要的時刻進行Sweep。
- C,C++無法辨認指針導致無法實現(xiàn)完全的GC。
- 只有保守的GC。
- 需要配合平衡樹進行查找p所指向的塊
第九章課后家庭作業(yè)
書上課后練習題都有答案,就不在博客里詳細寫了。
9.11
A.虛擬地址0x027c
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
B.地址翻譯
| VPN | 0x09 |
| TLB索引 | 0x01 |
| TLB標記 | 0x02 |
| TLB命中 | No |
| 缺頁 | No |
| PPN | 0x17 |
C.物理地址格式
| 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
D.物理地址引用
| 字節(jié)偏移 | 0x0 |
| 緩存索引 | 0xF |
| 緩存標記 | 0x17 |
| 緩存命中 | No |
| 返回緩存字節(jié) | - |
9.12
A.虛擬地址0x03a9
| 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 |
B.地址翻譯
| VPN | 0x0E |
| TLB索引 | 0x02 |
| TLB標記 | 0x03 |
| TLB命中 | No |
| 缺頁 | No |
| PPN | 0x11 |
C.物理地址格式
| 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 |
D.物理地址引用
| 字節(jié)偏移 | 0x1 |
| 緩存索引 | 0xA |
| 緩存標記 | 0x11 |
| 緩存命中 | No |
| 返回緩存字節(jié) | - |
9.13
A.虛擬地址0x0040
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
B.地址翻譯
| VPN | 0x01 |
| TLB索引 | 0x01 |
| TLB標記 | 0x00 |
| TLB命中 | No |
| 缺頁 | YES |
| PPN | - |
9.14
#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
| malloc(3) | 8 | 0x9 |
| malloc(11) | 16 | 0x11 |
| malloc(20) | 24 | 0x19 |
| malloc(21) | 32 | 0x21 |
9.16
對齊要求 | 已分配塊|空閑塊|最小塊大小|
---|---|---|---|
單字 | 頭部和腳部|頭部和腳部|16字節(jié)|
單字 | 頭部,但是沒有腳部|頭部和腳部|16字節(jié)|
雙字 | 頭部和腳部|頭部和腳部|16字節(jié)|
雙字 | 頭部,但是沒有腳部|頭部和腳部|16字節(jié)|
空閑塊至少是16字節(jié)。已分配塊不需要pred和succ,所以這八個字節(jié)可以裝數(shù)據(jù),加上頭和腳,就是16字節(jié),而且滿足單字雙字對齊。
9.17
我們需要修改find_fit函數(shù)(之前的版本在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指向的指針時,它可能和前面的空閑塊合并,我們應(yīng)該將cur_point指向前一個空閑塊首地址。在coalesce()函數(shù)中需要做如下修改:
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
需要考慮四個問題:
- 初始化的時候,序言塊和結(jié)尾塊是怎么樣的。
- 為堆申請更多空間的時候(sbrk),如何更改結(jié)尾塊
- 某個空閑塊匹配的時候,如何設(shè)置頭和下一塊的頭。
- 釋放某個塊時,如何合并。
序言塊八字節(jié)8|0x11。
結(jié)尾塊四字節(jié)0|0x11.
記錄最后一個塊的alloc。
結(jié)尾塊向后延伸申請的空間,并將剛多出的空間作為一個空閑塊。設(shè)置為size|(alloc<<1)。再合并該空閑塊。這里如何合并呢?需要判斷最后一塊是否已分配,可通過epilogue來判斷。
我們基于以下假設(shè):某個空閑塊匹配,上一個和下一個一定不是空閑塊(否則可以合并)。
所以頭部就設(shè)置為(asize|0x011)。
如果要分割,則下一塊的頭部設(shè)置為(size-asize|0x010),不用合并,因為再下一塊肯定是已經(jīng)分配。
檢查頭部,alloc_prev = 上一塊是否分配
檢查下一個塊的頭部,alloc_next = 下一個塊是否分配。
再根據(jù)那四種情況分別設(shè)置。
最后,如果下一塊已分配,則需要將下一塊頭部設(shè)置為(原頭部&(~0x010))。
另外,在mm_malloc中,分配多大的塊也要發(fā)生相應(yīng)的變化,因為現(xiàn)在最小塊大小可以是DSIZE,而不是2*DSIZE。
- 代碼已上傳至碼云
9.19
1) a; 對于伙伴系統(tǒng),如果要申請大小為33的空間,那么需要分配64個空間。如果申請大小為65的空間,那么塊大小就需要128,所以最多可能有約50%的空間被浪費。b中,最佳適配要搜索所有空間,所以肯定比首次適配要慢一些。c,邊界標記主要功能是釋放一個塊時,能立即和前后空閑塊合并。如果空閑塊不按順序排列的話,其實也能夠和前一個或者后一個空閑塊進行合并,但如果要和前后一起合并,可能會有些困難,那需要搜索前后塊在空閑鏈表中的位置,并且刪除一個再進行合并。可以參考P576,LIFO方法。d,其實任何分配器都可能有外部碎片,只要剩余的空閑塊大小和足夠但是單個都不夠,就會產(chǎn)生外部碎片。
2) d; 塊大小遞增,那么最佳適配法找到的塊和首次適配找到的塊是同一個,因為最佳適配總是想找一個剛好大于請求塊大小的空閑塊。a,塊大小遞減,首次適配很容易找到,所以分配性能會很高。b,最佳適配方法無論怎樣,都要搜索所有的鏈表(除非維護成塊大小遞增的鏈表)。c,是匹配的最小的。
3) c; 保守的意思就是所有可能被引用的堆都會被標記,int像指針,所以可能認為它表示的地址是正在被引用的(實際上它只是個int)。
9.20
不會……
教材學習中的問題及解決
在深入學習之前我和同伴是帶著這些疑問的,學習之后通過討論,對這些問題有了一點理解。
- 問題1:Linux虛擬地址空間如何分布?
問題1解決:
Linux 使用虛擬地址空間,大大增加了進程的尋址空間,由低地址到高地址分別為:- 1、只讀段:該部分空間只能讀,不可寫;(包括:代碼段、rodata 段(C常量字符串和#define```定義的常量) )
- 2、數(shù)據(jù)段:保存全局變量、靜態(tài)變量的空間;
- 3、堆 :就是平時所說的動態(tài)內(nèi)存, malloc/new 大部分都來源于此。其中堆頂?shù)奈恢每赏ㄟ^函數(shù) brk 和 sbrk 進行動態(tài)調(diào)整。
- 4、文件映射區(qū)域 :如動態(tài)庫、共享內(nèi)存等映射物理空間的內(nèi)存,一般是 mmap 函數(shù)所分配的虛擬地址空間。
- 5、棧:用于維護函數(shù)調(diào)用的上下文空間,一般為 8M,可通過 ulimit –s 查看。
- 6、內(nèi)核虛擬空間:用戶代碼不可見的內(nèi)存區(qū)域,由內(nèi)核管理(頁表就存放在內(nèi)核虛擬空間)。
- 問題2:64位系統(tǒng)擁有2^64的地址空間嗎?
問題2解決:
事實上, 64 位系統(tǒng)的虛擬地址空間劃分發(fā)生了改變:- 1、地址空間大小不是2^32,也不是2^64,而一般是2^48。因為并不需要 2^64 這么大的尋址空間,過大空間只會導致資源的浪費。64位Linux一般使用48位來表示虛擬地址空間,40位表示物理地址,這可通過 /proc/cpuinfo來查看
- 2、其中,0x0000000000000000~0x00007fffffffffff表示用戶空間, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF表示內(nèi)核空間,共提供 256TB(2^48) 的尋址空間。這兩個區(qū)間的特點是,第 47 位與 48~63 位相同,若這些位為 0 表示用戶空間,否則表示內(nèi)核空間。
- 3、用戶空間由低地址到高地址仍然是只讀段、數(shù)據(jù)段、堆、文件映射區(qū)域和棧;
- 問題3:如何查看進程發(fā)生缺頁中斷的次數(shù)?
問題3解決:
用ps -o majflt,minflt -C program命令查看。
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。
這兩個數(shù)值表示一個進程自啟動以來所發(fā)生的缺頁中斷的次數(shù)。
- 問題4:發(fā)成缺頁中斷后,執(zhí)行了那些操作?
問題4解決:
當一個進程發(fā)生缺頁中斷的時候,進程會陷入內(nèi)核態(tài),執(zhí)行以下操作:- 1、檢查要訪問的虛擬地址是否合法
- 2、查找/分配一個物理頁
- 3、填充物理頁內(nèi)容(讀取磁盤,或者直接置0,或者啥也不干)
- 4、建立映射關(guān)系(虛擬地址到物理地址)
重新執(zhí)行發(fā)生缺頁中斷的那條指令
- 問題5:堆內(nèi)碎片不能直接釋放,導致疑似“內(nèi)存泄露”問題,為什么 malloc不全部使用 mmap 來實現(xiàn)呢(mmap分配的內(nèi)存可以會通過 munmap 進行 free ,實現(xiàn)真正釋放)?而是僅僅對于大于 128k的大塊內(nèi)存才使用 mmap ?
問題5解決:
進程向 OS 申請和釋放地址空間的接口 sbrk/mmap/munmap 都是系統(tǒng)調(diào)用,頻繁調(diào)用系統(tǒng)調(diào)用都比較消耗系統(tǒng)資源的。并且, mmap 申請的內(nèi)存被 munmap 后,重新申請會產(chǎn)生更多的缺頁中斷。例如使用 mmap 分配 1M 空間,第一次調(diào)用產(chǎn)生了大量缺頁中斷 (1M/4K 次) ,當munmap 后再次分配 1M 空間,會再次產(chǎn)生大量缺頁中斷。缺頁中斷是內(nèi)核行為,會導致內(nèi)核態(tài)CPU消耗較大。另外,如果使用 mmap 分配小內(nèi)存,會導致地址空間的分片更多,內(nèi)核的管理負擔更大。
同時堆是一個連續(xù)空間,并且堆內(nèi)碎片由于沒有歸還 OS ,如果可重用碎片,再次訪問該內(nèi)存很可能不需產(chǎn)生任何系統(tǒng)調(diào)用和缺頁中斷,這將大大降低 CPU 的消耗。 因此, glibc 的 malloc 實現(xiàn)中,充分考慮了 sbrk和 mmap 行為上的差異及優(yōu)缺點,默認分配大塊內(nèi)存 (128k) 才使用 mmap 獲得地址空間,也可通過 mallopt(M_MMAP_THRESHOLD, <SIZE>)來修改這個臨界值。
上周考試錯題總結(jié)
無
結(jié)對及互評
點評模板:
- 博客中值得學習的或問題:
- xxx
- xxx
- ...
- 代碼中值得學習的或問題:
- xxx
- xxx
- ...
- 其他
本周結(jié)對學習情況
-[20155318](http://www.cnblogs.com/lxy1997/) - 結(jié)對照片 - 結(jié)對學習內(nèi)容- 教材第九章內(nèi)容- 跟著同伴回顧了第十二章的內(nèi)容- ...其他(感悟、思考等,可選)
通過這一章的學習,進一步加深了對虛擬存儲器的了解。
代碼托管
(statistics.sh腳本的運行結(jié)果截圖)
學習進度條
| 目標 | 5000行 | 30篇 | 400小時 | |
| 第一周 | 133/133 | 1/1 | 8/8 | |
| 第三周 | 159/292 | 1/3 | 10/18 | |
| 第五周 | 121/413 | 1/5 | 10/28 | |
| 第七周 | 835/3005 | 2/7 | 10/38 | |
| 第八周 | 1702/4777 | 1/8 | 10/48 | |
| 第九周 | 1664/6441 | 3/11 | 10/58 | |
| 第十一周 | 300/6741 | 3/14 | 10/68 | |
| 第十三周 | 743/7484 | 2/16 | 10/78 |
嘗試一下記錄「計劃學習時間」和「實際學習時間」,到期末看看能不能改進自己的計劃能力。這個工作學習中很重要,也很有用。
耗時估計的公式
:Y=X+X/N ,Y=X-X/N,訓練次數(shù)多了,X、Y就接近了。
參考:軟件工程軟件的估計為什么這么難,軟件工程 估計方法
計劃學習時間:15小時
實際學習時間:10小時
改進情況:
(有空多看看現(xiàn)代軟件工程 課件
軟件工程師能力自我評價表)
參考資料
- 《深入理解計算機系統(tǒng)V3》學習指導
- ...
轉(zhuǎn)載于:https://www.cnblogs.com/guyanlin/p/8033903.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的2017-2018-1 20155227 《信息安全系统设计基础》第十三周学习总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小丑女与堕落女巫一同出游:身材火辣、性感
- 下一篇: 批量创建10个系统帐号tianda01-