Linux进程地址空间

目录

一、进程地址空间

二、内存描述符

三、线性区

 四、缺页异常处理


一、进程地址空间

     进程地址空间是进程允许使用的一组虚拟地址空间的集合,这些虚拟地址空间称为线性区,包含起始虚拟地址,长度和访问权限等属性,线性区的起始地址和长度必须是4096的整数倍,从而确保线性区对应的数据可以填满分配的页框,减少内碎片。不同进程所使用的线性区是不同的而且是彼此独立的,内核可以增加或者删除某些线性区从而动态调整进程地址空间。同一个进程所拥有的线性区从来不重叠,在访问权限相同的情况下,内核会尽量把新创建的线性区合并进已有的线性区,也会把两个相邻的线性区合并成一个。内核需要准确记录一个进程所拥有的线性区,从而有效区分两种无效虚拟地址:一种是编程错误引起的无效虚拟地址;一种是缺页引发的,对应的虚拟地址尚未分配页框,但是在线性区范围内,内核根据此异常触发页框的分配,并让进程继续正常执行。

二、内存描述符

     Linux用一个叫做内存描述符的数据结构来保存与进程地址空间有关的全部信息,该数据结构的类型为mm_struct。进程描述符的数据结构task_struct中有两个指针变量mm和active_mm,他们的数据类型就是mm_struct,mm表示进程所拥有的用户空间内存描述符,内核线程不拥有任何内存描述符,其mm属性总是设为NULL,active_mm表示进程运行时所使用的内存描述符,当进程从用户态切换到内核态时内核线程将进程的mm拷贝到active_mm,用来访问进程的地址空间。当前进程创建一个新的轻量级进程时,轻量级进程的mm属性会直接复制自父进程,即父子进程的mm属性都指向同一个内存描述符,从而实现跟父进程共享内存数据。

      内存描述符中有两个重要字段mm_users和mm_count来记录当前内存描述符的使用情况,mm_users表示引用了该内存描述符的轻量级进程的个数,mm_count表示当前正在使用的该内存描述符的内核线程的个数+1,所有轻量级进程和父进程只算后面的1,当mm_count递减时,内核会判断mm_count是否等于0,如果等于则该内存描述符没有被任何进程或者内核线程使用,则销毁该内存描述符。

     内存描述符中的pgd属性表示页目录的地址,内核将该地址放到cr3控制寄存器中,借助MMU分页单元完成虚拟地址到真实物理地址的转换。其他部分字段用于描述进程各个通用的线性区,也叫segment的起始地址,如下图:

其中共享区是指通用的共享库文件对应的线性区,实际上各个线性区不一定按照上图是连在一起的,不同进程各线性区的起止地址基本都不同;栈区是进程被调度执行时用于分配栈空间的;堆区是进程运行时用来保存大对象,可用malloc函数从该线性区动态申请内存,返回所分配内存单元的第一个字节的线性地址。

     所有的内存描述符通过自身的mmlist域链接在一个双向链表上,该属性表示上一个相邻的内存描述符,该链表的首元素是init_mm内存描述符,代表init进程的地址空间,该内存描述符保存了主内核页全局目录,用户进程可以根据此链表访问主内核页全局目录,当发生缺页异常,虚拟地址不在当前进程页表而在主内核页表时就会从主内核页表复制对应的记录到进程页表中,从而实现内核线程对主内核页表的修改对所有的进程页表都有效,避免了同时去修改所有的内核页表。

     参考: Linux源码解析-内存描述符(mm_struct)

                 Linux进程地址管理之mm_struct

三、线性区

      线性区描述符的数据结构为vm_area_struct,其中vm_mm属性指向线性区所在的内存描述符,vm_start表示线性区的起始地址,vm_end表示线性区的结束地址,vm_end-vm_start即为该线性区的长度。vm_ops属性对应的数据结构vm_operations_struct,包含了创建/删除线性区的回调方法,缺页异常的回调方法等。

     进程所拥有的所有线性区通过链表链接在一起,链表内的线性区按照内存地址升序排序,便于查找指定虚拟地址所属的线性区。为了解决线性区比较多的情况下链表查找效率较低的问题,Linux同时用红黑树来维护同一个进程拥有的多个线性区,根据内存地址大小区分左右子节点。通常用红黑树来执行查找操作,包括查找给定虚拟地址所属的线性区,查找目标线性区前后的线性区,链表则用于扫描所有的线性区。vm_area_struct中的vm_next指向当前线性区的下一个线性区,mm_struct中的mmap指向链表中的第一个线性区,map_count表示当前进程所拥有的线性区的个数,mmap_cache指向最近使用的一个线性区,默认情况下一个进程最多可以有65536个线性区。vm_area_struct中的vm_rb字段表示当前线性区在红黑树种的节点,包含该节点的颜色,指向左右子节点,父节点的指针等属性,mm_struct中的mm_rb字段指向红黑树的根节点。

    vm_area_struct的vm_flags字段表示了当前线性区的特性,如是否可读,可写,可执行,可共享等页的访问权限信息,是映射自一个可执行文件或者一个只读文件等线性区内容信息,应用程序应该顺序或者随机访问线性区等访问方式信息,线性区向低地址或者高地址扩展等如何扩展信息。Linux定义了多种标识符,允许这些标识符任意的组合。因为线性区包含了页,线性区定义的页相关的访问权限必须复制到对应的页表项中,以便分页单元执行检查。页表访问权限相关的标识的初值保存在vm_area_struct的vm_page_prot字段中,新增一个页时,根据该字段的值设置页表项中相应的标识。

    如果线性区映射自一个文件,则vm_area_struct的vm_file字段便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。

    线性区被创建时,只是说明进程可以访问这个虚存空间,但并没有分配相应的页框和建立对应的页表。在进程执行访问线性区时会产生缺页异常,然后通过vm_area_struct结构里面的vm_ops->nopage所指向的函数处理缺页异常,最终完成相关页框的分配。

      参考:  《深入理解Linux内核》(第三版)

 四、缺页异常处理

      缺页异常是因为虚拟地址翻译成物理地址时在页表中找不到对应的页框地址,即该虚拟地址对应的页未分配页框。采用缺页异常的方式来动态分配内存的方式称为请求调页,该技术将页框的分配一直推迟到进程要访问的页因未分配页框而触发缺页异常为止。因为进程在运行的时候并不会访问地址空间内所有地址,并且程序的局部性特性导致程序执行的每个阶段,只有一部分页会被使用,所以如果把进程暂时用不到的页对应的页框提供给其他需要的进程使用,就可以显著提高页框的使用效率,提高系统的吞吐量,这就是请求调页的设计初衷。被访问的页不在内存中,可能是因为进程从未访问过该页,或者该页对应的页框被回收了,无论哪种情况都需要内核重新分配页框。除请求调页和访问非法虚拟地址外,主要还有以下两种情形会触发缺页异常:

     1、内核访问非连续性地址,vmalloc函数分配内存时只更新了内核页表未更新进程页表,进程访问该函数返回的虚拟地址时,该虚拟地址不在进程地址空间而在内核空间,从而触发一个缺页异常,将对应的内核页表记录复制到进程页表,然后正常访问该页。

     2、写时复制:当fork()一个进程时,子进程并未完整的复制父进程的地址空间,而是共享相关的资源,父进程的页表被设为只读的,当子进程进行写操作时,会触发缺页异常,从而为子进程分配页框,并将该页框标记为可写的。

      do_page_fault()函数是内核的缺页中断处理程序,当判断是因为未分配页框导致的缺页异常时会区分对目标页的多种访问,并选择对应的适当的处理办法,如下图:

        参考:  缺页异常详解

                    linux内存管理--缺页异常处理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值