14 [虚拟化] 虚存抽象;Linux进程的地址空间
14 [虛擬化] 虛存抽象;Linux進(jìn)程的地址空間
南京大學(xué)操作系統(tǒng)課蔣炎巖老師網(wǎng)絡(luò)課程筆記。
視頻:https://www.bilibili.com/video/BV1N741177F5?p=14
講義:http://jyywiki.cn/OS/2021/slides/10.slides#/
本講概述
程序 = 狀態(tài)機(jī);進(jìn)程 = 狀態(tài)機(jī)的執(zhí)行(路徑)
- 狀態(tài)機(jī)的狀態(tài)由內(nèi)存和寄存器(M,R)決定
- 寄存器會(huì)在發(fā)生中斷之后保存到進(jìn)程的內(nèi)存(內(nèi)核棧)中
- 內(nèi)存呢?
虛存抽象:
- 進(jìn)程的地址空間
- 分頁(yè)機(jī)制
- 分頁(yè)機(jī)制和虛擬存儲(chǔ)
進(jìn)程的地址空間
進(jìn)程的地址空間中有什么
進(jìn)程的地址空間 = 內(nèi)存中若干連續(xù)的 “段”,每一段是可訪問(wèn)的(讀/寫/執(zhí)行)的內(nèi)存,可能映射到某個(gè)文件和 / 或在進(jìn)程間共享。
進(jìn)程執(zhí)行指令需要代碼、數(shù)據(jù)、堆棧:
- 代碼(如main,%rip會(huì)從此處取出待執(zhí)行的指令)
- 數(shù)據(jù)(如static int x)
- 堆棧(如int y)
地址空間中還有:
- 動(dòng)態(tài)鏈接庫(kù)
- 運(yùn)行時(shí)分配的內(nèi)存
以上這些都可以直接用指針訪問(wèn)。
那么,這個(gè)地址空間是怎么創(chuàng)建的呢?創(chuàng)建之后,我們還可以修改它嗎?肯定是能的,如動(dòng)態(tài)鏈接庫(kù)可以動(dòng)態(tài)地加載。
管理進(jìn)程地址空間的系統(tǒng)調(diào)用
// 映射 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);// 修改映射權(quán)限 int mprotect(void *addr, size_t length, int prot);mmap的作用就是把磁盤文件的一部分直接映射到進(jìn)程的內(nèi)存中
說(shuō)人話:在狀態(tài)機(jī)上增加或者刪除一段可訪問(wèn)的內(nèi)存。
把文件映射到地址空間?
它們好像的確沒(méi)什么區(qū)別:
- 文件 = 字節(jié)序列
- 內(nèi)存 = 字節(jié)序列
- 操作系統(tǒng)允許這樣映射好像挺合理的,下一課中,ELF loader用mmap非常容易實(shí)現(xiàn),解析出要加載哪部分到內(nèi)存,然后直接mmap就完了。
查看進(jìn)程的地址空間
pmap
pmap命令可以查看某個(gè)進(jìn)程的地址空間:
pmap [PID]動(dòng)態(tài)鏈接 / 靜態(tài)鏈接的地址空間
我們準(zhǔn)備一個(gè)死循環(huán)C程序:
int main(){while (1); }分別用動(dòng)態(tài)鏈接和靜態(tài)鏈接的方式來(lái)編譯它:
gcc test.c -o test_d.out gcc -static test.c -o test_s.out分別把得到的test_d.out和test_s.out后臺(tái)執(zhí)行并用pmap來(lái)查看它們的地址空間:
$ ./test_d.out & [1] 5002 $ ./test_s.out & [2] 5015pmap 5002 pmap 5015分別得到動(dòng)態(tài)鏈接和靜態(tài)鏈接的pmap如下:
5002: ./test_d.out 000055cfab135000 4K r-x-- test_d.out 000055cfab335000 4K r---- test_d.out 000055cfab336000 4K rw--- test_d.out 00007f26750a9000 1948K r-x-- libc-2.27.so 00007f2675290000 2048K ----- libc-2.27.so 00007f2675490000 16K r---- libc-2.27.so 00007f2675494000 8K rw--- libc-2.27.so 00007f2675496000 16K rw--- [ anon ] 00007f267549a000 164K r-x-- ld-2.27.so 00007f2675691000 8K rw--- [ anon ] 00007f26756c3000 4K r---- ld-2.27.so 00007f26756c4000 4K rw--- ld-2.27.so 00007f26756c5000 4K rw--- [ anon ] 00007fff1d64d000 132K rw--- [ stack ] 00007fff1d6cd000 12K r---- [ anon ] 00007fff1d6d0000 4K r-x-- [ anon ] ffffffffff600000 4K --x-- [ anon ]total 4384K 5015: ./test_s.out 0000000000400000 728K r-x-- test_s.out 00000000006b6000 24K rw--- test_s.out 00000000006bc000 4K rw--- [ anon ] 0000000000e17000 140K rw--- [ anon ] 00007fff1bf5b000 132K rw--- [ stack ] 00007fff1bfc5000 12K r---- [ anon ] 00007fff1bfc8000 4K r-x-- [ anon ] ffffffffff600000 4K --x-- [ anon ]total 1048K可以看到動(dòng)態(tài)鏈接比靜態(tài)鏈接多了很多動(dòng)態(tài)鏈接庫(kù).so,占用的內(nèi)存空間也較大。而通過(guò)ls -l命令,我們發(fā)現(xiàn)動(dòng)態(tài)鏈接生成的可執(zhí)行文件所占的磁盤空間更小。
pmap的實(shí)現(xiàn)
我們不禁好奇pmap是怎樣實(shí)現(xiàn)的,可以通過(guò)追蹤系統(tǒng)調(diào)用的strace工具來(lái)查看:
strace pmap 5002實(shí)際上,我們多次強(qiáng)調(diào)過(guò)的一個(gè)概念:程序就是一個(gè)狀態(tài)機(jī),而這樣一個(gè)狀態(tài)機(jī)想要得到操作系統(tǒng)里的任何東西,都要通過(guò)系統(tǒng)調(diào)用,所以當(dāng)我們想知道pmap這樣的程序是怎樣實(shí)現(xiàn)的,最好的辦法就是去看一下它執(zhí)行了哪些系統(tǒng)調(diào)用,因此說(shuō)追蹤系統(tǒng)調(diào)用的strace工具是十分有用的。
言歸正傳,上述pmap指令的輸出中最關(guān)鍵的是這一句:
openat(AT_FDCWD, "/proc/5002/maps", O_RDONLY) = 3我們看到,pmap是去讀/proc文件中相關(guān)進(jìn)程號(hào)的內(nèi)存信息maps。(關(guān)于/proc:linux /proc 詳解)
我們發(fā)現(xiàn)了什么寶藏?
我們直接看一下上面動(dòng)態(tài)鏈接的可執(zhí)行文件的進(jìn)程:
cat /proc/5--2/maps輸出:
55cfab135000-55cfab136000 r-xp 00000000 103:02 28869833 /home/song/CppProjects/test_d.out 55cfab335000-55cfab336000 r--p 00000000 103:02 28869833 /home/song/CppProjects/test_d.out 55cfab336000-55cfab337000 rw-p 00001000 103:02 28869833 /home/song/CppProjects/test_d.out 7f26750a9000-7f2675290000 r-xp 00000000 103:02 8393695 /lib/x86_64-linux-gnu/libc-2.27.so 7f2675290000-7f2675490000 ---p 001e7000 103:02 8393695 /lib/x86_64-linux-gnu/libc-2.27.so 7f2675490000-7f2675494000 r--p 001e7000 103:02 8393695 /lib/x86_64-linux-gnu/libc-2.27.so 7f2675494000-7f2675496000 rw-p 001eb000 103:02 8393695 /lib/x86_64-linux-gnu/libc-2.27.so 7f2675496000-7f267549a000 rw-p 00000000 00:00 0 7f267549a000-7f26754c3000 r-xp 00000000 103:02 8393690 /lib/x86_64-linux-gnu/ld-2.27.so 7f2675691000-7f2675693000 rw-p 00000000 00:00 0 7f26756c3000-7f26756c4000 r--p 00029000 103:02 8393690 /lib/x86_64-linux-gnu/ld-2.27.so 7f26756c4000-7f26756c5000 rw-p 0002a000 103:02 8393690 /lib/x86_64-linux-gnu/ld-2.27.so 7f26756c5000-7f26756c6000 rw-p 00000000 00:00 0 7fff1d64d000-7fff1d66e000 rw-p 00000000 00:00 0 [stack] 7fff1d6cd000-7fff1d6d0000 r--p 00000000 00:00 0 [vvar] 7fff1d6d0000-7fff1d6d1000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]前面都好理解,是我們進(jìn)程執(zhí)行時(shí)的代碼、數(shù)據(jù)、堆棧、動(dòng)態(tài)鏈接庫(kù)等,但是最后那三個(gè):vvar、vdso、vsyscall是什么鬼?
vvar、vdso、vsyscall是什么鬼?
讓內(nèi)核和進(jìn)程共享數(shù)據(jù) (內(nèi)核可寫,進(jìn)程只讀)
-
vvar: 內(nèi)核和進(jìn)程共享的數(shù)據(jù)
-
vdso: 系統(tǒng)調(diào)用代碼實(shí)現(xiàn) (是操作系統(tǒng)的一部分)
-
vsyscall: (ffffffffff600000這么詭異的地址???)
-
是普通系統(tǒng)調(diào)用的包裝
- 曾經(jīng)的 exception-less syscall 實(shí)現(xiàn),但存在安全問(wèn)題
- 依然存在,保持向后兼容
vsyscall 的例子
- 時(shí)間:內(nèi)核維護(hù)秒級(jí)的時(shí)間 (所有進(jìn)程映射同一個(gè)頁(yè)面)
- 例子:time (2)
- 我們甚至可以調(diào)試它
- 例子:time (2)
- getcpu:per-CPU 映射頁(yè)面
計(jì)算機(jī)系統(tǒng)里沒(méi)有魔法!我們理解了 Linux 進(jìn)程地址空間的全部!
使用共享內(nèi)存與內(nèi)核通信
有些系統(tǒng)調(diào)用不陷入內(nèi)核也可以執(zhí)行,使用共享內(nèi)存和內(nèi)核通信!
- 內(nèi)核線程在 spinning 等待系統(tǒng)調(diào)用的到來(lái)
- 收到系統(tǒng)調(diào)用請(qǐng)求后立即開(kāi)始執(zhí)行
- 進(jìn)程 spin 等待系統(tǒng)調(diào)用完成
- 如果系統(tǒng)調(diào)用很多,可以打包處理
實(shí)現(xiàn)虛擬存儲(chǔ):分頁(yè)機(jī)制
需求分析
我們的操作系統(tǒng)看到的內(nèi)存是真實(shí)的物理內(nèi)存,而為各個(gè)線程提供的,即線程看到的是虛擬內(nèi)存。那么,操作系統(tǒng)怎樣事項(xiàng)這一虛擬化呢?
操作系統(tǒng)希望實(shí)現(xiàn)地址空間的管理(mmap、munmap API)
- 進(jìn)程的地址空間是由若干 “段” 組成的,但是操作系統(tǒng)只擁有一個(gè)物理地址空間(物理內(nèi)存)
- 操作系統(tǒng)需要
- 為進(jìn)程存儲(chǔ)各個(gè)段的信息(例如在struct proc里)
- 在物理內(nèi)存中實(shí)際分配內(nèi)存( pmm->alloc() )
- 借助硬件的機(jī)制實(shí)現(xiàn)虛擬化 (CPU在執(zhí)行用戶進(jìn)程時(shí),強(qiáng)制進(jìn)行地址翻譯)
所以,我們需要一個(gè)函數(shù) f:[0,M)→[0,M)f : [0,M) \rightarrow [0,M)f:[0,M)→[0,M),把 ”虛擬地址“ 翻譯為 ”物理地址“ ,畢竟我們真實(shí)的物理內(nèi)存只有一份,fff 應(yīng)當(dāng)由操作系統(tǒng)控制,即應(yīng)用程序不可見(jiàn) fff。
操作系統(tǒng)為每個(gè)進(jìn)程準(zhǔn)備一個(gè)映射函數(shù) fff ,當(dāng)進(jìn)程運(yùn)行時(shí),fff 被 ”加載“ 到CPU上,此后該進(jìn)程每次訪問(wèn)內(nèi)存,都需要通過(guò)CPU上對(duì)應(yīng)的 fff 來(lái)進(jìn)行從該進(jìn)程可見(jiàn)的虛擬內(nèi)存到真實(shí)物理內(nèi)存的映射,而該進(jìn)程的任何越權(quán)訪問(wèn)物理內(nèi)存地址,都將觸發(fā)異常(缺頁(yè)?)。
應(yīng)當(dāng)注意,我們的 fff 有以下幾方面的要求:
- 支持 fff 在運(yùn)行時(shí)動(dòng)態(tài)地進(jìn)行修改(mmap,munmap)
- 非常節(jié)約:fff 的存儲(chǔ)開(kāi)銷必須遠(yuǎn)小于實(shí)際使用的內(nèi)存,總不能為了維護(hù)映射函數(shù) fff 所使用的內(nèi)存比實(shí)際要使用的內(nèi)存還多
- 非常高效:因?yàn)槊看卧L問(wèn)內(nèi)存都要計(jì)算 fff, 因此其實(shí)現(xiàn)需要非常高效
分頁(yè)機(jī)制
把地址空間切成大小為 ppp 的 “頁(yè)面” ,比如在x86中,頁(yè)面大小為4KiB。只維護(hù)以頁(yè)面為單位的映射,而非整個(gè)物理內(nèi)存大小的虛擬內(nèi)存到整個(gè)物理內(nèi)存的映射。這樣我們要維護(hù):[0,M/p)→[0,M/p)[0,M/p) \rightarrow [0,M/p)[0,M/p)→[0,M/p) 的映射。
我們有這樣一個(gè)基本假設(shè):進(jìn)程內(nèi)存地址的空間局部性,即絕大部分頁(yè)面都沒(méi)有映射,且映射一般都是連續(xù)的空間
Radix Tree(Trie) + TLB(Translation Lookaside Buffer):
32位機(jī)和64位機(jī)的分頁(yè)尋址過(guò)程如圖所示:
分頁(yè)+保護(hù):實(shí)現(xiàn)虛擬化
映射是頁(yè)面到頁(yè)面的,也就意味著映射的低位永遠(yuǎn)是0,4kiB的頁(yè)面就會(huì)有12bits空閑,可以用來(lái)存儲(chǔ)頁(yè)面的存儲(chǔ)保護(hù)等信息。
分頁(yè)機(jī)制與虛擬存儲(chǔ)
mmap并不需要為進(jìn)程分配任何頁(yè)面,只需要 “讓操作系統(tǒng)知道這么映射” 就夠了,進(jìn)程訪問(wèn)頁(yè)面會(huì)進(jìn)入缺頁(yè)進(jìn)入操作系統(tǒng)。
操作系統(tǒng)并不需要在這一段創(chuàng)建的時(shí)候,就立即給進(jìn)程分配內(nèi)存,而是操作系統(tǒng)完全可以等到進(jìn)程真正訪問(wèn)這個(gè)頁(yè)面并發(fā)生缺頁(yè)時(shí),再去分配這塊內(nèi)存。當(dāng)然,如果操作系統(tǒng)根據(jù)之前的映射發(fā)現(xiàn)進(jìn)程訪問(wèn)的這塊內(nèi)存是不合法的,就會(huì)Segmentation Fault。
缺頁(yè)
缺頁(yè)時(shí)操作系統(tǒng)會(huì)得到缺頁(yè)的地址(%cr2),根據(jù)操作系統(tǒng)維護(hù)的進(jìn)程地址信息分配頁(yè)面。
Memory-Mapped File:一致性
這樣的設(shè)計(jì)也有些問(wèn)題需要明確,比如:
- 如果把頁(yè)面映射到文件
- 修改什么時(shí)候生效(立即生效,會(huì)造成大量的磁盤IO;等到unmap或者進(jìn)程結(jié)束在生效,又太遲了)
- 若干映射到同一個(gè)文件的進(jìn)程(共享一份內(nèi)存?各自有本地的副本?)
Takeaways and Wrap-up
虛擬化
- 程序 = 狀態(tài)機(jī) (進(jìn)程的地址空間里到底有什么)
- 操作系統(tǒng) = 狀態(tài)機(jī)的管理者,借助硬件(物理狀態(tài)機(jī))實(shí)現(xiàn)多個(gè)并發(fā)執(zhí)行的虛擬狀態(tài)機(jī)(進(jìn)程)
- 狀態(tài)機(jī)中的地址空間(虛擬地址空間)
- 應(yīng)用視角:用mmap系統(tǒng)調(diào)用管理
- 硬件視角:用分頁(yè)機(jī)制實(shí)現(xiàn)
總結(jié)
以上是生活随笔為你收集整理的14 [虚拟化] 虚存抽象;Linux进程的地址空间的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 为什么可乐成为大众饮品的原因 探究可乐的
- 下一篇: linux 其他常用命令