20145216史婧瑤《資訊保安系統設計基礎》第十四周學習總結
教材內容總結
第九章 虛擬儲存器
虛擬儲存器是計算機系統最重要的概念之一,它是對主存的一個抽象
三個重要能力:
- 它將主存看成是一個儲存在磁碟上的地址空間的快取記憶體,在主存中只儲存活動區域,並根據需要在磁碟和主存之間來回傳送資料,通過這種方式,高效的使用了主存
- 它為每個程式提供了一致的地址空間,從而簡化了儲存器管理
- 它保護了每個程式的地址空間不被其他程式破壞
第一節 物理和虛擬定址
1.實體地址
計算機系統的主存被組織成一個由M個連續的位元組大小的單元組成的陣列,每位元組都有一個唯一的實體地址PA。
根據實體地址定址的是物理定址。
2.虛擬地址
虛擬儲存器被組織為一個由存放在磁碟上的N個連續的位元組大小的單元組成的陣列。
使用虛擬定址時,CPU通過生成一個虛擬地址VA來訪問主存,這個虛擬地址在被送到儲存器之前先轉換成適當的實體地址(這個過程叫做地址翻譯,相關硬體為儲存器管理單元MMU)
第二節 地址空間
1.地址空間
地址空間是一個非負整數地址的有序集合:{0,1,2,……}
2.線性地址空間
地址空間中的整數是連續的。
3.虛擬地址空間
CPU從一個有 N=2^n 個地址的地址空間中生成虛擬地址,這個地址空間成為稱為虛擬地址空間。
4.地址空間的大小
由表示最大地址所需要的位數來描述。
N=2^n:n位地址空間
主存中的每個位元組都有一個選自虛擬地址空間的虛擬地址和一個選自實體地址空間的實體地址。
第三節 虛擬儲存器作為快取的工具
虛擬儲存器——虛擬頁VP,每個虛擬頁大小為P=2^平位元組
物理儲存器——物理頁PP,也叫頁幀,大小也為P位元組。
任意時刻,虛擬頁面的集合都被分為三個不相交的子集:
- 未分配的:VM系統還沒分配/建立的頁,不佔用任何磁碟空間。
- 快取的:當前快取在物理儲存器中的已分配頁
- 未快取的:沒有快取在物理儲存器中的已分配頁
1.DRAM快取的組織結構
需要知道,這種快取結構:
- 不命中處罰很大
- 是全相聯的——任何虛擬頁都可以放在任何的物理頁中。
- 替換演算法精密
- 總是使用寫回而不是直寫。
2.頁表
頁表是一個資料結構,存放在物理儲存器中,將虛擬頁對映到物理頁。
頁表就是一個頁表條目PTE的陣列,組成為:
有效位+n位地址欄位
1.如果設定了有效位:
地址欄位表示DRAM中相應的物理頁的起始位置,這個物理頁中快取了該虛擬頁
2.如果沒有設定有效位:
(1)空地址:
表示該虛擬頁未被分配
(2)不是空地址:
這個地址指向該虛擬頁在磁碟上的起始位置。
3.缺頁
相關定義:
- 缺頁:就是指DRAM快取不命中。
- 缺頁異常:會呼叫核心中的缺頁異常處理程式,選擇一個犧牲頁。
- 頁:虛擬儲存器的習慣說法,就是塊
- 交換=頁面排程:磁碟和儲存器之間傳送頁的活動
- 按需頁面排程:直到發生不命中時才換入頁面的策略,所有現代系統都使用這個。
4.虛擬儲存器中的區域性性
區域性性原則保證了在任意時刻,程式將往往在一個較小的活動頁面集合上工作,這個集合叫做工作集/常駐集。
所以只要程式有良好的時間區域性性,虛擬儲存器系統就能工作的相當好。
顛簸:工作集大小超出了物理儲存器的大小。
第四節 虛擬儲存器作為儲存器管理的工具
- 作業系統為每個程式提供了一個獨立的頁表,也就是一個獨立的虛擬地址空間。
- 多個虛擬頁面可以對映到同一個共享物理頁面上。
- 儲存器對映:將一組連續的虛擬頁對映到任意一個檔案中的任意位置的表示法。
VM簡化了連結和載入、程式碼和資料共享,以及應用程式的儲存器分配。
第五節 虛擬儲存器作為儲存器保護的工具
PTE的三個許可位:
- SUP:表示程式是否必須執行在核心模式下才能訪問該頁
- READ:讀許可權
- WRITE:寫許可權
第六節 地址翻譯
具體符號見上圖
地址翻譯就是一個N元素的虛擬地址空間VAS中的元素和一個M元素的實體地址空間PAS中元素之間的對映。
頁面基址暫存器PTBR指向當前頁表。
MMU利用VPN選擇適當的PTE。
PPO=VPO。
1.當頁面命中時,CPU動作:
- 處理器生成虛擬地址,傳給MMU
- MMU生成PTE地址,並從快取記憶體/主存請求得到他
- 快取記憶體/主存向MMU返回PTE
- MMU構造實體地址,並把它傳給快取記憶體/主存
- 快取記憶體/主存返回所請求的資料給處理器。
2.處理缺頁時:
- 處理器生成虛擬地址,傳給MMU
- MMU生成PTE地址,並從快取記憶體/主存請求得到他
- 快取記憶體/主存向MMU返回PTE
- PTE中有效位為0,觸發缺頁異常
- 確定犧牲頁
- 調入新頁面,更新PTE
- 返回原來的程式,再次執行導致缺頁的指令,會命中
一、結合快取記憶體和虛擬儲存器來看
- 首先,在既使用SRAM快取記憶體又使用虛擬儲存器的系統中,大多數系統選擇物理定址
- 主要思路是地址翻譯發生在快取記憶體之前
- 頁表目錄可以快取,就像其他的資料字一樣
二、利用TLB加速地址翻譯
TLB:翻譯後備緩衝器,是一個小的、虛擬儲存的快取,其中每一行都儲存著一個由單個PTE組成的塊
步驟:
- CPU產生一個虛擬地址
- MMU從TLB中取出相應的PTE
- MMU將這個虛擬地址翻譯成一個實體地址,並且將它傳送到快取記憶體/主存
- 快取記憶體/主存將所請求的資料字返回給CPU
三、多級頁表
多級頁表——採用層次結構,用來壓縮頁表。
1.以兩層頁表層次結構為例,好處是:
- 如果一級頁表中的一個PTE是空的,那麼相應的二級頁表就根本不會存在
- 只有一級頁表才需要總是在主存中,虛擬儲存器系統可以在需要時建立、頁面調入或調出二級頁表,只有最經常使用的二級頁表才快取在主存中。
2.多級頁表的地址翻譯:
第七節 案例研究
一、Core i7地址翻譯
PTE有三個許可權位:
- R/W位:確定內容是讀寫還是隻讀
- U/S位:確定是否能在使用者模式訪問該頁
- XD位:禁止執行位,64位系統中引入,可以用來禁止從某些儲存器頁取指令
還有連個缺頁處理程式涉及到的位:
- A位,引用位,實現頁替換演算法
- D位,髒位,告訴是否必須寫回犧牲頁
二、Linux虛擬儲存器系統
-
Linux為每個程式維持了一個單獨的虛擬地址空間
-
核心虛擬儲存器包括:核心中的程式碼和資料結構
-
一部分割槽域對映到所有程式共享的物理頁面,另一部分包含每個程式都不相同的資料。
1、Linux虛擬儲存器區域
區域:就是已分配的虛擬儲存器的連續片。
區域的例子:
- 程式碼段
- 資料段
- 堆
- 共享庫段
- 使用者棧
每個存在的虛擬頁面都儲存在某個區域中。核心為系統中的每個程式維護一個單獨的任務結構task_struct:
一個具體區域的區域結構包括:
- vm_start:指向起始處
- vm_end:指向結束處
- vm_prot:描述這個區域包含的所有頁的讀寫許可許可權
- vm_flags:是共享的還是私有的
- vm_next:指向下一個區域
2、Linux缺頁異常處理
-
虛擬地址A是否合法
- 不合法,觸發段錯誤,終止程式
- 合法,進入下一條
-
儲存器訪問是否合法,即是否有許可權
- 不合法,觸發保護異常,終止程式
- 合法,進入下一條
-
針對合法的虛擬地址進行合法的操作:選擇一個犧牲頁面,如果被修改過就換新的並更新頁表。
第八節 儲存器對映
Linux通過將一個虛擬儲存器區域與一個磁碟上的物件關聯起來,以初始化這個虛擬儲存器區域的內容的過程。
對映物件:
- Unix檔案系統中的普通檔案
- 匿名檔案(全都是二進位制0)
一、共享物件和私有物件
1.共享物件
- 共享物件對於所有把它對映到自己的虛擬儲存器程式來說都是可見的
- 即使對映到多個共享區域,物理儲存器中也只需要存放共享物件的一個拷貝。
2.私有物件
- 私有物件運用的技術:寫時拷貝(fork函式就是應用了寫時拷貝技術)
- 在物理儲存器中只儲存有私有物件的一份拷貝
二、使用mmap函式的使用者級儲存器對映
1.建立新的虛擬儲存器區域
#include <unistd.h>
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
成功返回指向對映區域的指標,若出錯則為-1
引數含義:
- start:這個區域從start開始
- fd:檔案描述符
- length:連續的物件片大小
- offset:距檔案開始處的偏移量
- prot:訪問許可權位,具體如下:
- PROT_EXEC:由可以被CPU執行的指令組成
- PROT_READ:可讀
- PROT_WRITE:可寫
- PROT_NONE:不能被訪問
- flag:由描述被對映物件型別的位組成,具體如下:
- MAP_ANON:匿名物件,虛擬頁面是二進位制0
- MAP_PRIVATE:私有的、寫時拷貝的物件
- MAP_SHARED:共享物件
2.刪除虛擬儲存器:
include <unistd.h>
include <sys/mman.h>
int munmap(void *start, size_t length);
成功返回0,失敗返回-1
從start開始刪除,由接下來length位元組組成的區域。
第九節 動態儲存器分配
1.堆:
是一個請求二進位制0的區域,緊接在未初始化的bss區域後開始,並向上(更高的地址)生長。有一個變數brk指向堆的頂部
2.分配器的兩種基本風格:
- 顯示分配器-malloc和free
- 隱式分配器/垃圾收集器
一、malloc和free函式:
系統呼叫malloc函式,從堆中分配塊:
#include <stdlib.h>
void *malloc(size_t size);
成功返回指標,指向大小至少為size位元組的儲存器塊,失敗返回NULL
系統呼叫free函式來釋放已分配的堆塊:
#include <stdlib.h>
void free(void *ptr);
無返回值
ptr引數必須指向一個從malloc、calloc或者reallov獲得的已分配塊的起始位置。
為什麼要使用動態儲存器分配?
因為經常知道程式實際執行時,它們才知道某些資料結構的大小。
二、分配器的要求和目標:
1.要求
- 處理任意請求序列
- 立即響應請求
- 只使用堆
- 對齊塊
- 不修改已分配的塊
2.目標:
- 最大化吞吐率(吞吐率:每個單位時間裡完成的請求數)
- 最大化儲存器利用率——峰值利用率最大化
三、碎片
雖然有未使用的儲存器,但是不能用來滿足分配請求時,發生這種現象。
1.內部碎片
-
發生在一個已分配塊比有效載荷大的時候
-
易於量化
2.外部碎片
-
發生在當空閒儲存器合計起來足夠滿足一個分配請求,但是沒有一個單獨的空間塊足以處理這個請求時發生
-
難以量化,不可預測
四、隱式空閒連結串列
-
堆塊的格式:由一個字的頭部,有效荷載,和可能的額外填充組成。
-
將堆組織成一個連續的已分配塊和空閒塊的序列:空閒塊通過頭部中的大小欄位隱含地連線著,分配器可以通過遍歷堆中所有的塊,從而間接地遍歷整個空閒塊的集合。
-
需要:特殊標記的結束塊。
-
系統對齊要求和分配器對塊格式的選擇會對分配器上的最小塊大小有強制的要求。
五、放置已分配的塊——放置策略
1.首次適配
從頭開始搜尋空閒連結串列,選擇第一個合適的空閒塊
2.下一次適配
從上一次搜尋的結束位置開始搜尋
3.最佳適配
檢索每個空閒塊,選擇適合所需請求大小的最小空閒塊
六、申請額外的堆儲存器
用到sbrk函式:
#include <unistd.h>
vid *sbrk(intptr_t incr);
成功則返回舊的brk指標,出錯為-1
通過將核心的brk指標增加incr來擴充套件和收縮堆。
七、合併空閒塊
合併是針對於假碎片問題的,任何實際的分配器都必須合併相鄰的空閒塊。
有兩種策略:
- 立即合併
- 推遲合併
八、帶邊界的合併
這個合併的意思是,因為頭部的存在,所以向後合併是簡單的,但是向前合併是不方便的,所以就在塊的最後加一個腳部,作為頭部的副本,就方便了合併。
九、實現簡單的分配器
關於如何實現一個簡單分配器的設計,需要注意以下幾點:
- 序言塊和結尾塊:序言塊是初始化時建立的,而且永不釋放;結尾塊是一個特殊的塊,總是以它為結束。
- 有一個技巧,就是將重複使用的,操作複雜又有重複性的,這些可以定義成巨集,方便使用也方便修改。
- 需要注意強制型別轉換,尤其是帶指標的,非常複雜。
- 因為規定了位元組對齊方式為雙字,就代表塊的大小是雙字的整數倍,不是的舍入到是。
十、顯式空閒連結串列
1.區別
(1)分配時間
- 隱式——塊總數的線性時間
- 顯式——空閒塊數量的線性時間
(2)連結串列形式
- 隱式——隱式空閒連結串列
- 顯式——雙向連結串列,有前驅和後繼,比頭部腳部好使。
2.排序策略:
- 後進先出
- 按照地址順序維護
十一、分離的空閒連結串列
分離儲存,是一種流行的減少分配時間的方法。一般思路是將所有可能的塊大小分成一些等價類/大小類。
分配器維護著一個空閒連結串列陣列,每個大小類一個空閒連結串列,按照大小的升序排列。
有兩種基本方法:
1.簡單分離儲存
每個大小類的空閒連結串列包含大小相等的塊,每個塊的大小就是這個大小類中最大元素的大小。
(1)操作
- 如果連結串列非空:分配其中第一塊的全部
- 如果連結串列為空:分配器向作業系統請求一個固定大小的額外儲存器片,將這個片分成大小相等的塊,並且連線起來成為新的空閒連結串列。
(2)優缺點
- 優點:時間快,開銷小
- 缺點:容易造成內部、外部碎片
2.分離適配
-
每個空閒連結串列是和一個大小類相關聯的,並且被組織成某種型別的顯示或隱式連結串列,每個連結串列包含潛在的大小不同的塊,這些塊的大小是大小類的成員。
-
這種方法快速並且對儲存器使用很有效率。
3.夥伴系統——分離適配的特例
-
其中每個大小類都是2的冪
-
這樣,給定地址和塊的大小,很容易計算出它的夥伴的地址,也就是說:一個塊的地址和它的夥伴的地址只有一位不同。
-
優點:快速檢索,快速合併。
第十節 垃圾收集
垃圾收集器是一種動態儲存分配器,它自動釋放程式不再需要的已分配塊,這些塊被稱為垃圾,自動回收堆儲存的過程叫做垃圾收集。
1.基本知識
垃圾收集器將儲存器視作一張有向可達圖,只有當存在一條從任意根節點出發併到達p的有向路徑時,才說節點p是可達的,而不可達點就是垃圾。
2.Mark&Sweep垃圾收集器
有兩個階段:
- 標記:標記出根節點的所有可達的和已分配的後繼
- 清楚:釋放每個未被標記的已分配塊。
相關函式:
ptr定義為typedef void *ptr
- ptr isPtr(ptr p):如果p指向一個已分配塊中的某個字,那麼就返回一個指向這個塊的起始位置的指標b,否則返回NULL
- int blockMarked(ptr b):如果已經標記了塊b,那麼就返回true
- int blockAllocated(ptr b):如果塊b是已分配的,那麼久返回ture
- void markBlock(ptr b):標記塊b
- int length(ptr b):返回塊b的以字為單位的長度,不包括頭部
- void unmarkBlock(ptr b):將塊b的狀態由已標記的改為未標記的
- ptr nextBlock(ptr b):返回堆中塊b的後繼
3.C保守的Mark&Sweep——平衡二叉樹
根本原因是C語言不會用型別標記來標記儲存器位置。
第十一節 C程式中常見的與儲存器有關的錯誤
- 間接引用壞指標
常見錯誤——scanf錯誤
- 讀未初始化的儲存器
常見錯誤——假設堆儲存器被初始化為0
- 允許棧緩衝區溢位
常見錯誤——緩衝區溢位錯誤
- 假設指標和它們指向的物件是相同大小的
在遠處起作用action at distance
-
造成錯位錯誤
-
引用指標,而不是它所指向的物件
-
誤解指標運算
-
引用不存在的變數
-
引用空堆塊中的資料
-
引起儲存器洩露
程式碼託管截圖
連結:https://git.oschina.net/sjy519/linux-program-C/tree/master
其他(感悟、思考等,可選)
本週學習了課本中的第九章內容,檢視教學程式時,發現這已經是最後一次學習任務了,不知怎麼了,沒有想象中的如釋重負,心裡反倒有些空空的,這周雖然很累,但是週末我並沒有放鬆對Linux的學習,認真的學完了最後一章,在學習過程中,我知道了很多以前程式設計中出現的錯誤的原因,讓我能在以後的程式設計中避免這些問題。
學習進度條
程式碼行數(新增/累積) | 部落格量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 3000行 | 30篇 | 300小時 | |
第一週 | 0/0 | 1/2 | 25/40 | 學習了Linux基礎知識和核心命令 |
第二週 | 0/0 | 0/2 | 0/40 | |
第三週 | 300/300 | 3/5 | 40/80 |
學習了vim、gcc、gdb指令; 學習了資訊表示和處理
|
第五週 | 200/500 | 1/6 | 45/125 |
學習了程式的機器級表示 |
第六週 | 150/650 | 1/7 | 40/165 |
學習了處理器體系結構
|
第七週 | 100/750 | 1/8 | 40/205 |
學習了儲存器層次結構
|
第八週 | 46/796 | 2/10 | 40/245 |
複習了以前的知識點 |
第九周 | 124/920 | 1/11 | 40/285 |
學習了系統級I/O的相關內容 |
第十週 | 510/1430 | 3/14 | 32/317 |
重點學習了一些命令 |
第十一週 | 440/1870 | 3/17 | 35/352 |
學習了異常控制流的相關知識點 |
第十二週 | 230/2100 | 3/20 | 30/382 |
複習了前三週的程式碼 |
第十三週 | 300/2400 | 1/21 | 30/412 |
學習了網路程式設計和併發程式設計的相關內容 |
第十四周 | 270/2670 | 2/23 | 33/445 |
學習了虛擬儲存器的相關內容 |