地址空间
- 把有相同地址空间的进程称为线程
- 一个进程访问了不在有效范围中的内存区域,或者以不正确的方式访问了有效地址,那么内核就会终止进程
内存区域包含的内存对象
- 代码段:可执行文件代码的内存映射
- 数据段:可执行文件已经初始化的全局变量内存映射
- 未初始化的全局变量
- 进程的用户空间栈(注意进程的内核栈独立存在,由内核维护)
- C库或者动态连接程序等共享库的代码段
- 内存映射文件
- 共享内存段
- 匿名的内存映射
内存描述符
- 内存描述符mm_struct结构体表示进程地址空间
mm_strcut
- mm_user记录使用该地址空间进程数
- mm_count表示mm_struct主引用计数,0表示没有线程使用这个地址空间,1表示有
分配内存描述符
- 通常每个进程都有唯一的mm_struct结构体,也就是唯一的地址空间
- 如果父进程希望和子进程共享地址空间,可以在调用clone 时设置CLONE_VM标志,那么这样的进程就变成线程了,实际上这样子进程的mm域就指向父进程的内存描述符
撤销内存描述符
- 每次进程退出就会更新一些统计量,减少mm_user如果减少到为0,那么说明这个内存描述符没有进程使用了,于是调用对应函数将内存描述符释放回slab分配器。
mm_struct与内核线程
- 内核线程没有进程地址空间,没有内存描述符,实际上内核线程–没有用户上下文???
- 但内核线程访问内核内存害是需要一些数据,比如页表,所以为了避面浪费内存,和地址空间切换,直接使用前一个进程的内存描述符???
虚拟内存区域VMA
- 实际上就是把地址空间再分多个区域,每个区域意义不同,比如代码段,用户空间栈。把每个内存区域当做对象管理,有访问权限,操作函数,起始地址和结束地址。使用面向对象思想
VM标志
存储内存区域的数据结构
- 链表用于方便遍历地址空间的所有内存区域
- 红黑树方便,用于查找某个地址在哪个内存区域
- 寻找某个地址在哪个内存区域,还使用了缓存技术,考虑可能有多个连续的对同一个VMA的操作
mmap do_mmap
do__mmap
- 内核使用do_mmap()函数创建新的线性地址空间
- 该函数映射由file指定的文件,具体映射的是文件从偏移量offset开始长度为len字节内的数据
- 如果file = NULL,offset = 0 叫做匿名映射,否则是文件映射,addr搜索空间的起始位置,prot参数指定访问权限
具体实现 - 从虚拟内存中分配一个合适的新的内存区域,如果创建的地址区间和已经存在的相邻,且有相同访问权限,那么就合并。
- 不能合并就从slab中分配一个对应的结构体代表这个区域
- 更新地址空间内存区域链表和红黑树,更新内存描述符中对应的域
- 返回新分配的地址区间的起始位置
mmap系统调用
munmap 和 do_munmap
- 和上面的相反,就是在特定地址空间中删除指定地址区域
页表
- 应用程序使用的是虚拟内存,处理器直接操作虚拟内存,所以需要查询页表找到物理地址
- linux使用3级页表 为什么使用多级页表
- fork进程中的写时拷贝技术,父子进程共享页表,当父子进程中的一方修改特定页表时,内核才去创建对应页表项的拷贝,之后不在共享这个页表项。通过共享页表可以消除fork操作中页表拷贝带来的消耗
为什么使用多级页表
- 避免把全部页表保存在内存中,让页表在内存中离散存储,虚拟地址空间增大,页表数增大,存放页表的连续空间增大,操作系统内存紧张,或者碎片较多时,连续的大内存很宝贵,使用一页存放页表目录项,页表项可以放在内存中其他分散的碎片位置,不用保证页表连续。
- 多级页表可以节省页表内存。因为地址空间在内存的哪个位置都有可能,如果用一级页表那么占用内存 = 内存大小/页大小 * 4 b 固定的。但是有的应用程序占用内存很小,比如4m。那么用多级页表 = 内存大小/应用程序大小 * 4b(一级页表:目录项) + 实际目录项指向的一个大小为4m/页大小(二级页表),但是这边也目录项并不是每一项都指向一个时间二级页表,可能为空,那么就不占内存,也就节约了内存。
|