Linux内核之进程地址空间

来源:互联网 发布:企业彩铃录音软件 编辑:程序博客网 时间:2024/04/20 04:01

1.进程地址空间概念:

进程使用的是虚拟内存中的地址,也叫线性地址,由操作系统协助相关硬件(如MMU),映射到物理地址。线性地址是通过页表(Page Table)映射到物理内存,页表由操作系统维护并被处理器引用。内核空间在页表中拥有较高特权级,因此用户态程序试图访问这些页时会导致一个页错误。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化。在32位机器上linux操作系统中的进程的地址空间大小是4G,其中0-3G是用户空间,3G-4G是内核空间。其实,这个4G的地址空间是不存在的,也就是我们所说的虚拟内存空间。

2.进程地址空间、内存描述符、线性区链表关系:

进程地址空间由允许进程使用的全部线性地址组成,与进程地址空间有关的全部信息都包含在一个叫做内存描述符的数据结构中,这个结构的类型为mm_struct。Linux通过类型为vm_area_struct的对象实现线性区,每个线性区描述符表示一个线性地址空间,进程所拥有的线性区从不重叠,并且内核尽力把新分配的线性区与紧邻的现有线性区进行合并。如果两个相邻区的访问权限匹配,就能把他们合并在一起。下图描述了进程地址空间、内存描述符、线性区链表之间的关系:

进程地址空间、内存描述符、线性区链表之间的关系

内核频繁执行的一个操作就是查找包含指定线性地址空间的线性区,为了提高效率Linux2.6把内存描述符存放在叫做红黑树(read-black-tree)的数据结构中。为了存放进程的线性区,linux既使用了链表,又使用了红黑树,一般的,红黑树用来确定含有指定地址的线性区,而链表通常在扫描整个线性区集合时使用。线性区的大小是4KB的倍数(必须包含完整的页),而栈的大小却是任意的。c语言中的每个段空间就是通过vm_area_struct表示,他们关系如下 :

程序中段到物理地址的映射
当一个程序被执行时,该程序的内容必须被放到进程的虚拟地址空间(注意,是虚拟地址空间),对于可执行程序的共享库也是如此。可执行程序并非真正读到物理内存中,而只是链接到进程的虚拟内存中(此时都是在进程的虚拟地址空间)。当一个可执行程序映射到进程虚拟地址空间时,一组vm_area_struct数据结构将被产生。每个vm_area_struct数据结构表示可执行印象的一部分;是可执行代码,或是初始化的数据,以及未初始化的数据等。

3.请求调页和缺页异常:

请求调页是 一种动态内存分配技术,它把页框的分配推迟到不能再推迟为止。这种技术的动机是:进程开始运行的时候并不访问地址空间中的全部内容。事实上,有一部分地址 也许永远也不会被进程所使用。程序的局部性原理也保证了在程序执行的每个阶段,真正使用的进程页只有一小部分,对于临时用不到的页,其所在的页框可以由其 它进程使用。因此,请求分页技术增加了系统中的空闲页框的平均数,使内存得到了很好的利用。从另外一个角度来看,在不改变内存大小的情况下,请求分页能够 提高系统的吞吐量。当进程要访问的页不在内存中的时候,就通过缺页异常处理将所需页调入内存中。

CPU通过地址总线可以访问连接在地址总线上的所有外设,包括物理内存、IO设备等等,但从CPU发出的访问地址并非是这些外设在地址总线上的物理地址,而是一个虚拟地址,由MMU将虚拟地址转换成物理地址再从地址总线上发出,MMU上的这种虚拟地址和物理地址的转换关系是需要创建的,并且MMU还可以设置这个物理页是否可以进行写操作,当没有创建一个虚拟地址到物理地址的映射,或者创建了这样的映射,但那个物理页不可写的时候,MMU将会通知CPU产生了一个缺页异常。
下面总结下缺页异常的几种情况:

  • 当MMU中确实没有创建虚拟页物理页映射关系,并且在该虚拟地址之后再没有当前进程的线性区vma的时候,可以肯定这是一个编码错误,这将杀掉该进程;
  • 当MMU中确实没有创建虚拟页物理页映射关系,并且在该虚拟地址之后存在当前进程的线性区vma的时候,这很可能是缺页异常,并且可能是栈溢出导致的缺页异常;
  • 当使用malloc/mmap等希望访问物理空间的库函数/系统调用后,由于linux并未真正给新创建的vma映射物理页,此时若先进行写操作,将如上面的2的情况产生缺页异常,若先进行读操作虽也会产生缺页异常,将被映射给默认的零页(zero_pfn),等再进行写操作时,仍会产生缺页异常,这次必须分配物理页了,进入写时复制的流程;
  • 当使用fork等系统调用创建子进程时,子进程不论有无自己的vma,“它的”vma都有对于物理页的映射,但它们共同映射的这些物理页属性为只读,即linux并未给子进程真正分配物理页,当父子进程任何一方要写相应物理页时,导致缺页异常的写时复制;

目前来看,应该就是这四种情况,还是比较清晰的,可发现一个重要规律就是,linux是直到实在不行的时候才会分配物理页。从用户空间的缺页异常分为两种情况:

  • 触发异常的线性地址处于用户空间的vma中,但还未分配物理页,如果访问权限没问题的话内核就给进程分配相应的物理页了;
  • 触发异常的线性地址不处于用户空间的vma中,这种情况得判断是不是因为用户进程的栈空间消耗完而触发的缺页异常,如果是的话则在用户空间对栈区域进行扩展,并且分配相应的物理页,如果不是则作为一次非法地址访问来处理,内核将终结进程。

缺页处理函数为arch/arm/mm/fault.c文件中的do_page_fault函数。