Linux内核-进程地址空间

Posted by keys961 on April 9, 2019

1. 地址空间

进程地址空间由进程可寻址的虚拟内存组成,它是平坦的。

每个进程的内存地址空间互不相干。

一个进程的地址空间的所有虚拟地址不一定有权限访问,若访问非法内存,则会报“段错误”。

内存区域是可访问的合法地址空间,包含:

  • 代码段
  • 数据段(已初始化的全局变量)
  • bss段(未初始化的全局变量)
  • 用户栈
  • 动态链接库的代码段、数据段和bss段
  • 内存映射文件
  • 共享内存段
  • 匿名的内存映射(如malloc()分配的内存)

2. 内存描述符

用于描述进程的地址空间,由struct mm_struct表示,定义在<linux/sched.h>中,task_struct中的mm域就是内存描述符。

它维护2个引用计数:

  • mm_count:主引用计数
  • mm_users:使用该地址空间的进程数,只有它为0时,mm_count才能递减,当它为0时才能被回收

内存区域由2种数据结构表示,它们的内容是一样的:

  • mmapvm_area_struct链表
  • mm_rbrb_root红黑树

前者时候遍历,后者适合查找。

2.1. 分配内存描述符

fork()时,通过copy_mm()(写时)复制父进程的内存描述符;而子进程通过allocate_mm()从slab缓存中分配到内存描述符。

clone()时指定CLONE_VMallocate_mm()不会被调用。

2.2. 撤销内存描述符

进程退出时,内核调用exit_mm()(定义在kernel/exit.c中)撤销。

它减少mm_users,若减到0,则调用mmdrop()mm_count递减,若它也为0,则会调用free_mm()将它归还到slab缓存中。

2.3. 内核线程

内核线程没有进程地址空间,因此task_struct->mm域为空。

当一个进程被调度时,该进程的mm域指向的地址空间会被装载到内存,若mm为空(如内核线程),则保留前一个进程的地址空间。

3. 虚拟内存区域(VMA)

struct vm_area_struct描述,定义在<linux/mm_types.h>中。

它描述了某个地址空间一个独立的连续内存范围。每个VMA可代表不同类型的内存区域,不同类型的区域不能重叠。

3.1. VMA标志

vm_flags定义,标记了该区域所包含的页面的行为和信息,如可写、可读、可执行、可共享等等。

不同于物理访问权限,它反映的是内核处理要遵守的行为标准

3.2. VMA操作

vm_ops域指向一个vm_operations_struct结构体表示(与VFS设计很类似)。

操作包括:

  • 将指定内存区域加入一个地址空间/从一个地址空间删除
  • 物理内存中,对应页面不存在(页错误)时,处理页错误

3.3. 实际使用中的内存区域

可使用/proc/<pid>/maps或者pmap工具查看某个进程的内存空间和包含的内存区域。

4. 操作内存区域

4.1. find_vma()

struct vm_area_struct * find_vma(strcut mm_struct *mm, unsigned long addr);

查找第一个vm_end大于addr的VMA,找不到则返回空。结果会被缓存。

4.2. find_vma_prev()

struct vm_area_struct * find_vma_prev(strcut mm_struct *mm, unsigned long addr, struct vm_area_struct **pprev);

查找第一个小于addr的VMA,pprev存放先于addr的VMA指针。

4.3. find_vma_intersection()

struct vm_area_struct* find_vma_intersection(struct mm_struct *mm, unsigned long start_addr, unsigned long end_addr);

返回第一个和指定区间相交的VMA。

5. mmap()do_mmap():创建地址区间

unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset);

它可创建一个新的线性地址区间(VMA),但是若创建的区间和某个区间相邻,且权限相同,则会被合并。

函数中,file指定文件,映射从offset开始,长度为len。若file为空且offset为0,则为“匿名映射”,否则是”文件映射”。

addr可选,指定空闲区域的起始位置;prot指定区域中页面的访问权限;flag指定VMA标志。

mmap()mmap2()do_mmap()的包装。

6. mummap()do_mummap():删除地址区间

int do_mummap(struct mm_struct *mm, unsigned long start, size_t len)

从特定进程地址空间中删除指定的地址区间(VMA),删除位置从start开始,长度为len

mummap()是对do_mummap()的简单包装。

7. 页表

Linux中采用三级页表实现,之后也有四级页表的实现。以4KB页为例:

对于三级页表:

  • 顶层是页全局目录PGD,指向PMD,包含pgd_t数组(31~30)
  • 二级页表是PMD,指向页目录,包含pmd_t数组(29~21)
  • 最后一级是PTE(20~12),指向页表,配合偏移(11~0)转化成物理内存地址

对于四级页表,类似:

  • PML4,指向一个PGD(47~39)
  • PGD,指向一个PMD(38~30)
  • PMD,指向一个页目录(29~21)
  • PTE(20~12),指向页表,配合偏移(11~0)转换为物理地址

为了提高效率,引入TLB,在查询物理地址时首先在TLB中查询,找不到时采取内存中查找页表。