第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。

 

posted @ 2025-04-01 09:14  moonのsun  阅读(68)  评论(0)    收藏  举报