第9章 进程地址空间
1 规则简介
进程地址空间:允许进程使用的全部线性地址,也叫进程的线性区
2 内核和用户进程地址空间的区别
- 内核的地址空间是共享的,所有内核线程使用一个地址空间;每个用户进程的地址空间是独立的
- 内核申请动态内存,会立刻进行内存分配;用户进程申请内存会尽可能推迟分配内存
- 因此,内核要随时准备捕获用户态进程的寻址异常
3 进程获取新线性区的情况
- 新建一个进程去执行命令,会分配一个全新的地址空间
- 当前的进程装入完全不同的程序(类似新建进程,只不过使用旧的进程标识符)
- 进程对文件进行内存映射,会把文件内容映射到进程的线性区
- 进程的线性区(堆或者栈)用完,可能会进行线性区扩展
- 进程决定使用IPC共享线性区和其他进程共享数据时,会分配新的线性区来共享
4 内存描述符
与进程地址空间有关的全部信息都放在内存描述符mm_struct中,也就是进程描述符中的mm字段;
和其他常用结构体一样,mm_struct也存放在slab分配器的高速缓存中;
内核线程永远不会访问低于TASK_SIZE(也叫PAGE_OFFSET,3GB)的地址,一般访问3GB-4GB之间的地址。
每个进程有两个进程描述符:
- mm表示进程所拥有的内存描述符
- active_mm表示进程运行时所使用的内存描述符
对于内核线程来说,不用任何进程描述符(所有内核线程共用一个内存描述符),因此mm为NULL;
对于内核线程来说,active_mm字段被初始化为前一个运行的内核进程的active_mm。
5 线性区
5.1 线性区数据结构
进程所拥有的线性区从来不重叠;
内核尽力把新分配的线性区与紧邻的线性区合并——线性区表示物理地址+offset,如果可以合并,就会减少内存描述符使用;
因此,删除某个线性区,可能反而会导致新增内存描述符。
进程所拥有的线性区用链表连接在一起,且按照地址升序排列;
默认情况下,一个进程可以拥有最多65536个线性区;
内核频繁查找线性区,为了提高效率,内核把内存描述符存放在红黑树中。
5.2 线性区和页
线性区和页关系紧密,是线性地址的两种表述形式,比如我们把0-4095之间的线性区成为第0页;
页大小为4KB,线性区包含多个页,大小是4KB的倍数;
页的标志用vm_flags表示,常见的有:VM_READ、VM_WRITE、VM_SHARED等;
线性区的权限和页的权限并不完全一致,比如“写时复制”技术中,线性区的地址都是可以访问的,但是对该页的访问还是会产生缺页(框)异常:
内核把两个进程的页放在同一个页框中,把页框设置为只读,导致访问线性区的时候产生缺页异常。
6 分配释放线性地址空间
do_mmap()
- 为当前进程创建并初始化一个新的线性区
- 接收参数:file + offset(如果需要,用于文件映射)、addr(从何处开始查找空闲区间)、len(线性区长度)、prot(线性区访问权限)、flag(其他标志)
- 新的线性区可以与该进程已有的其他相邻线性区合并
do_unmap()
- 从当前进程的地址空间删除一个线性地址空间
- 接收参数:进程内存描述符mm、地址区间的起始地址start、长度len
- 要删除的区间并不总是对应一个线性区,或许是线性区的一部分,也可能横跨多个线性区
7 缺页异常处理程序
7.1 do_page_fault()简介
缺页指的是进程访问某个线性地址,页表会找线性地址对应的页框,结果没有找到。
缺页异常处理程序是一个中断处理程序:do_page_fault();
缺页异常处理程序必须可以区分编程错误引起的异常,还是由“属于进程地址空间但是尚未分配页框”引起的异常;
do_page_fault()的输入参数中有一个3位的error_code,当异常发生时由控制单元压入栈中:
- bit0:0:访问不存在的页;1:无效的访问权限
- bit1:0:异常由读或执行访问引起;1:异常由写访问引起
- bit2:0:异常发生在处理器处于内核态时;1:异常发生在处理器处于用户态时;
7.2 do_page_fault()步骤
① 读取引起缺页的线性地址
② 检查线性地址是否属于第4个GB(3GB-4GB),如果是则表明线性地址属于内核地址空间,执行vmalloc_fault(),处理两种情况:内核态访问非连续内存区 或 地址错误
③ 检查当前进程所拥有的线性地址空间是否包含引起缺页的地址空间 —— 出错才会检查线性地址是否属于进程地址空间!
7.3 处理地址空间以外的错误地址
异常发生在用户态进程:发送一个SIGSEGV信号给current进程
异常发生在内核态进程:
- 该线性地址是系统调用的参数传递给内核的:向当前进程发送SIGSEGV信号,并终止系统调用程序
- 内核缺陷引起:CPU会dump CPU寄存器和内核态堆栈,并杀死当前进程,这就是内核漏洞错误(Kernel opps)
7.4 处理地址空间内的错误地址
- 请求调页:被访问的地址(页)对应的页框不存在,也就是说,这个页还没有存放在实际的页框中
- 写时复制:被访问的页对应的页框存在,但是页框被标记为只读并且属于多个进程共享,那么内核会分配一个新的页框,把旧页框的数据拷贝到新页框中
7.5 写时复制
来源:
内核通过fork()系统调用创建进程时,子进程要继承父进程的地址空间,涉及到地址空间页的复制;
许多子进程装入新的程序开始执行,会完全丢弃继承到的地址空间。
实现:
父进程和子进程共享页框——共享页框都是写保护的;
无论是父进程还是子进程想要写共享页框时,会产生异常,内核就是把页复制到新的页框并标记为可写,原来的页只有唯一属主,也标记为可写的。
8 堆的管理
堆属于进程拥有的特殊的线性区,可以满足进程的动态内存的请求;
堆的起始地址和结束地址分别为start_brk和brk字段;
堆可以进程扩大和缩小,使用brk(addr)可以直接修改堆的结束地址current->mm->brk,以调整堆的大小;
扩大堆实际调用do_mmap(),缩小堆实际调用do_munmap();
堆不能与进程代码所在的线性区重叠:mm->brk > mm->end_code。
本文来自博客园,作者:moonのsun,转载请注明原文链接:https://www.cnblogs.com/moon-sun-blog/p/18778272

浙公网安备 33010602011771号