14 [虚拟化] 虚存抽象;Linux进程的地址空间
14 [虚拟化] 虚存抽象;Linux进程的地址空间
南京大学操作系统课蒋炎岩老师网络课程笔记。
视频:https://www.bilibili.com/video/BV1N741177F5?p=14
讲义:http://jyywiki.cn/OS/2021/slides/10.slides#/
本讲概述
程序 = 状态机;进程 = 状态机的执行(路径)
- 状态机的状态由内存和寄存器(M,R)决定
- 寄存器会在发生中断之后保存到进程的内存(内核栈)中
- 内存呢?
虚存抽象:
- 进程的地址空间
- 分页机制
- 分页机制和虚拟存储
进程的地址空间
进程的地址空间中有什么
进程的地址空间 = 内存中若干连续的 “段”,每一段是可访问的(读/写/执行)的内存,可能映射到某个文件和 / 或在进程间共享。
进程执行指令需要代码、数据、堆栈:
- 代码(如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 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
可以看到动态链接比静态链接多了很多动态链接库.so
,占用的内存空间也较大。而通过ls -l
命令,我们发现动态链接生成的可执行文件所占的磁盘空间更小。
pmap的实现
我们不禁好奇pmap是怎样实现的,可以通过追踪系统调用的strace
工具来查看:
strace pmap 5002
实际上,我们多次强调过的一个概念:程序就是一个状态机,而这样一个状态机想要得到操作系统里的任何东西,都要通过系统调用,所以当我们想知道pmap
这样的程序是怎样实现的,最好的办法就是去看一下它执行了哪些系统调用,因此说追踪系统调用的strace
工具是十分有用的。
言归正传,上述pmap
指令的输出中最关键的是这一句:
openat(AT_FDCWD, "/proc/5002/maps", O_RDONLY) = 3
我们看到,pmap
是去读/proc
文件中相关进程号的内存信息maps。(关于/proc
:linux /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)
- 我们甚至可以调试它
- 例子:time (2)
- getcpu:per-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 在运行时动态地进行修改(mmap,munmap)
- 非常节约: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位机的分页寻址过程如图所示:
分页+保护:实现虚拟化
映射是页面到页面的,也就意味着映射的低位永远是0,4kiB的页面就会有12bits空闲,可以用来存储页面的存储保护等信息。
分页机制与虚拟存储
mmap并不需要为进程分配任何页面,只需要 “让操作系统知道这么映射” 就够了,进程访问页面会进入缺页进入操作系统。
操作系统并不需要在这一段创建的时候,就立即给进程分配内存,而是操作系统完全可以等到进程真正访问这个页面并发生缺页时,再去分配这块内存。当然,如果操作系统根据之前的映射发现进程访问的这块内存是不合法的,就会Segmentation Fault。
缺页
缺页时操作系统会得到缺页的地址(%cr2),根据操作系统维护的进程地址信息分配页面。
Memory-Mapped File:一致性
这样的设计也有些问题需要明确,比如:
- 如果把页面映射到文件
- 修改什么时候生效(立即生效,会造成大量的磁盘IO;等到unmap或者进程结束在生效,又太迟了)
- 若干映射到同一个文件的进程(共享一份内存?各自有本地的副本?)
Takeaways and Wrap-up
虚拟化
- 程序 = 状态机 (进程的地址空间里到底有什么)
- 操作系统 = 状态机的管理者,借助硬件(物理状态机)实现多个并发执行的虚拟状态机(进程)
- 状态机中的地址空间(虚拟地址空间)
- 应用视角:用mmap系统调用管理
- 硬件视角:用分页机制实现
14 [虚拟化] 虚存抽象;Linux进程的地址空间相关推荐
- Linux进程虚拟地址空间
1. 前言 谈到Linux进程虚拟地址空间,还要从程序说起.本文通过分析程序的编译执行过程,分享了Linux进程虚拟地址空间的结构.组织和创建,并通过分析Linux内核源代码,总结了进程.进程虚拟地址 ...
- 面试中常被问到的(14)虚存管理和虚拟地址空间
虚拟内存技术使得每一个进程在运行过程中,始终都是显式独自占用当前系统内存资源,事实上,所有进程共享同一个物理内存,每一个进程只把自己目前需要的虚拟内存空间映射在物理内存上,此过程内核并不会立即把虚拟内 ...
- linux进程的地址空间
要了解进程的地址空间先要了解虚拟内存.虚拟内存:它是一个抽象概念,它为每个进程提供一个假象,每个进程都独占地使用主存.每个进程看到的内存都是一致的,称为虚拟地址空间. 进程的地址空间:操作系统在管理内 ...
- 嵌入式 Linux进程含义知多少
理想情况下,您应该明白在您的系统中运行的每一个进程.要获得所有进程的列表,可以执行命令 ps -ef(POSIX 风格)或 ps ax(BSD 风格).进程名有方括号的是内核级的进程,执行辅助功能(比 ...
- linux 进程 转存储,Linux memory management——(进程虚存空间的管理)(转)
Linux memory management--(进程虚存空间的管理)(转)[@more@]Linux memory management--(进程虚存空间的管理) 1.内核空间和用户空间 进程运行 ...
- linux内核调用( )为进程创建虚存区_Linux内核分析-总结篇(九)
本次内容作为Linux内核的总结内容,主要涉及对Linux系统的总体的一些理解,同时将之前的一些总结贴出来作为大家的一个索引,希望笔者一样的菜鸟有一些帮助和入门的作用.从一个初学者的角度对Linux有 ...
- 抽象思想解读Linux进程描述符
来源 | 嵌入式客栈 责编 | Carol 头图 | CSDN 下载自视觉中国 内核是怎么工作的? 首先要理解进程管理,进程调度,本文开始阅读进程管理部分,首先从进程的抽象描述开始.抽象是软件工程的灵 ...
- 内存管理实战之打印指定进程虚存区
本文将通过内核模块打印出指定进程的虚存区,而且使用两种方 式,因为进程的虚拟区有多种组织方式.一种是单链表,一种是红黑树. 红黑树是基于树的查找,效率很高,主要用来快速定位进程的某个虚存区. 红黑树也 ...
- Linux显存占用无进程清理方法
在跑Caffe.TensorFlow.pytorch之类的需要CUDA的程序时,强行Kill掉进程后发现显存仍然占用,这时候可以使用如下命令查看到top或者ps中看不到的进程,之后再kill掉: fu ...
最新文章
- 【转载】mysql常用函数汇总
- 自学python推荐书籍2019-2019年Python入门书籍推荐
- python3语法错误python_[大数据]Python 3.x中使用print函数出现语法错误(SyntaxError: invalid syntax)的原因 - 码姐姐找文...
- 解决安装XMind出现Invalid Configuration Location The configuration area at ‘C:\Users\Administrator\Applicat
- python读取txt文件_python实现读写txt文件的几种方法
- QT高级编程之QT基本概览
- 循环斐波那契数列_每日一课 | 斐波那契数列的第n个项
- ubuntu16.04安装pycharm,并设置快捷启动方式
- oracle错误 无监听程序,oracle_无监听程序_错误
- 【渝粤教育】广东开放大学 外国文学专题 形成性考核 (55)
- Tomcat中配置文件conf修改的一些常识
- 计算机ppt里怎么应用背景图,如何在电脑ppt软件中添加背景图片
- 企业微信后台应用配置步骤
- JMS Message消息头、消息体、消息属性
- LR参数化,参数化类型:Fille类型--参数分配与取值规律
- mvn编译“Cannot find matching toolchain definitions for the following toolchain types“报错解决方法
- 最简单的直播源抓取方法
- CDATA 数据处理
- Java笔记07——类和对象
- 常用css整理2 转自csdn
热门文章
- ETL异构数据源Datax_使用querySql_08
- oracle11g linux 日期格式设置
- Oracle 11g Java驱动包ojdbc6.jar安装到maven库,并查看jar具体版本号
- Vue优化策略_项目上线_02
- java.sql.SQLSyntaxErrorException: ORA-00923: 未找到要求的 FROM 关键字
- apache gobblin mysql_incubator-gobblin-master
- android 获取通讯录全选反选_Xamarin.Forms读取并展示Android和iOS通讯录 TerminalMACS客户端...
- qt生成无ui界面动态库,有ui界面的动态库,以及含有资源文件和qss文件的动态库
- GPUImage – 亮度平均 GPUImageLuminosity
- 右下角文字如何写_如何提取任意小程序的小程序路径