云南专业网站优化,免费建网页软件,呼家楼网站建设,石狮seo14 [虚拟化] 虚存抽象#xff1b;Linux进程的地址空间
南京大学操作系统课蒋炎岩老师网络课程笔记。 视频#xff1a;https://www.bilibili.com/video/BV1N741177F5?p14 讲义#xff1a;http://jyywiki.cn/OS/2021/slides/10.slides#/ 本讲概述
程序 状态机#xff1b;…14 [虚拟化] 虚存抽象Linux进程的地址空间
南京大学操作系统课蒋炎岩老师网络课程笔记。 视频https://www.bilibili.com/video/BV1N741177F5?p14 讲义http://jyywiki.cn/OS/2021/slides/10.slides#/ 本讲概述
程序 状态机进程 状态机的执行路径
状态机的状态由内存和寄存器MR决定 寄存器会在发生中断之后保存到进程的内存内核栈中内存呢
虚存抽象
进程的地址空间分页机制分页机制和虚拟存储
进程的地址空间
进程的地址空间中有什么
进程的地址空间 内存中若干连续的 “段”每一段是可访问的读/写/执行的内存可能映射到某个文件和 / 或在进程间共享。
进程执行指令需要代码、数据、堆栈
代码如main%rip会从此处取出待执行的指令数据如static int x堆栈如int y
地址空间中还有
动态链接库运行时分配的内存
以上这些都可以直接用指针访问。
那么这个地址空间是怎么创建的呢创建之后我们还可以修改它吗肯定是能的如动态链接库可以动态地加载。
管理进程地址空间的系统调用
// 映射
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);// 修改映射权限
int mprotect(void *addr, size_t length, int prot);mmap的作用就是把磁盘文件的一部分直接映射到进程的内存中
说人话在状态机上增加或者删除一段可访问的内存。
把文件映射到地址空间
它们好像的确没什么区别
文件 字节序列内存 字节序列操作系统允许这样映射好像挺合理的下一课中ELF loader用mmap非常容易实现解析出要加载哪部分到内存然后直接mmap就完了。
查看进程的地址空间
pmap
pmap命令可以查看某个进程的地址空间
pmap [PID]动态链接 / 静态链接的地址空间
我们准备一个死循环C程序
int main(){while (1);
}分别用动态链接和静态链接的方式来编译它
gcc test.c -o test_d.out
gcc -static test.c -o test_s.out分别把得到的test_d.out和test_s.out后台执行并用pmap来查看它们的地址空间
$ ./test_d.out
[1] 5002
$ ./test_s.out
[2] 5015pmap 5002
pmap 5015分别得到动态链接和静态链接的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 4384K5015: ./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可以看到动态链接比静态链接多了很多动态链接库.so占用的内存空间也较大。而通过ls -l命令我们发现动态链接生成的可执行文件所占的磁盘空间更小。
pmap的实现
我们不禁好奇pmap是怎样实现的可以通过追踪系统调用的strace工具来查看
strace pmap 5002实际上我们多次强调过的一个概念程序就是一个状态机而这样一个状态机想要得到操作系统里的任何东西都要通过系统调用所以当我们想知道pmap这样的程序是怎样实现的最好的办法就是去看一下它执行了哪些系统调用因此说追踪系统调用的strace工具是十分有用的。
言归正传上述pmap指令的输出中最关键的是这一句
openat(AT_FDCWD, /proc/5002/maps, O_RDONLY) 3我们看到pmap是去读/proc文件中相关进程号的内存信息maps。关于/proclinux /proc 详解
我们发现了什么宝藏
我们直接看一下上面动态链接的可执行文件的进程
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]前面都好理解是我们进程执行时的代码、数据、堆栈、动态链接库等但是最后那三个vvar、vdso、vsyscall是什么鬼
vvar、vdso、vsyscall是什么鬼
让内核和进程共享数据 (内核可写进程只读) vvar: 内核和进程共享的数据 vdso: 系统调用代码实现 (是操作系统的一部分) vsyscall: (ffffffffff600000这么诡异的地址???) 是普通系统调用的包装 曾经的 exception-less syscall 实现但存在安全问题依然存在保持向后兼容
vsyscall 的例子
时间内核维护秒级的时间 (所有进程映射同一个页面) 例子time (2) 我们甚至可以调试它 getcpuper-CPU 映射页面
计算机系统里没有魔法我们理解了 Linux 进程地址空间的全部
使用共享内存与内核通信
有些系统调用不陷入内核也可以执行使用共享内存和内核通信
内核线程在 spinning 等待系统调用的到来收到系统调用请求后立即开始执行进程 spin 等待系统调用完成如果系统调用很多可以打包处理
实现虚拟存储分页机制
需求分析
我们的操作系统看到的内存是真实的物理内存而为各个线程提供的即线程看到的是虚拟内存。那么操作系统怎样事项这一虚拟化呢
操作系统希望实现地址空间的管理mmap、munmap API
进程的地址空间是由若干 “段” 组成的但是操作系统只拥有一个物理地址空间物理内存操作系统需要 为进程存储各个段的信息例如在struct proc里在物理内存中实际分配内存 pmm-alloc() 借助硬件的机制实现虚拟化 CPU在执行用户进程时强制进行地址翻译
所以我们需要一个函数 f:[0,M)→[0,M)f : [0,M) \rightarrow [0,M)f:[0,M)→[0,M)把 ”虚拟地址“ 翻译为 ”物理地址“ 毕竟我们真实的物理内存只有一份fff 应当由操作系统控制即应用程序不可见 fff。
操作系统为每个进程准备一个映射函数 fff 当进程运行时fff 被 ”加载“ 到CPU上此后该进程每次访问内存都需要通过CPU上对应的 fff 来进行从该进程可见的虚拟内存到真实物理内存的映射而该进程的任何越权访问物理内存地址都将触发异常缺页。
应当注意我们的 fff 有以下几方面的要求
支持 fff 在运行时动态地进行修改mmapmunmap非常节约fff 的存储开销必须远小于实际使用的内存总不能为了维护映射函数 fff 所使用的内存比实际要使用的内存还多非常高效因为每次访问内存都要计算 fff 因此其实现需要非常高效
分页机制
把地址空间切成大小为 ppp 的 “页面” 比如在x86中页面大小为4KiB。只维护以页面为单位的映射而非整个物理内存大小的虚拟内存到整个物理内存的映射。这样我们要维护[0,M/p)→[0,M/p)[0,M/p) \rightarrow [0,M/p)[0,M/p)→[0,M/p) 的映射。
我们有这样一个基本假设进程内存地址的空间局部性即绝大部分页面都没有映射且映射一般都是连续的空间
Radix Tree(Trie) TLB(Translation Lookaside Buffer) 32位机和64位机的分页寻址过程如图所示 分页保护实现虚拟化
映射是页面到页面的也就意味着映射的低位永远是04kiB的页面就会有12bits空闲可以用来存储页面的存储保护等信息。 分页机制与虚拟存储
mmap并不需要为进程分配任何页面只需要 “让操作系统知道这么映射” 就够了进程访问页面会进入缺页进入操作系统。
操作系统并不需要在这一段创建的时候就立即给进程分配内存而是操作系统完全可以等到进程真正访问这个页面并发生缺页时再去分配这块内存。当然如果操作系统根据之前的映射发现进程访问的这块内存是不合法的就会Segmentation Fault。
缺页
缺页时操作系统会得到缺页的地址%cr2根据操作系统维护的进程地址信息分配页面。
Memory-Mapped File一致性
这样的设计也有些问题需要明确比如
如果把页面映射到文件 修改什么时候生效立即生效会造成大量的磁盘IO等到unmap或者进程结束在生效又太迟了若干映射到同一个文件的进程共享一份内存各自有本地的副本
Takeaways and Wrap-up
虚拟化
程序 状态机 进程的地址空间里到底有什么操作系统 状态机的管理者借助硬件物理状态机实现多个并发执行的虚拟状态机进程状态机中的地址空间虚拟地址空间 应用视角用mmap系统调用管理硬件视角用分页机制实现