qqwx

导航

8. 进程地址空间 2010-02-21 15:52 553人阅读 评论(0) 收藏

对于进程用户态分配内存请求,总被认为是不紧迫的,为提高效率采用推迟分配。进程请求动态

内存时,并未获得请求的页框,而仅仅获得对一个新的地址区间的使用权。这个区间叫做线性区。即进程

请求动态内存时,请求得到的是一个线性区,线性区被视为一种资源,用来组成线性地址区间,为效率起

见,它一般是4096的倍数。显然,能确认一个进程当前拥有的线性区是内核的基本任务,完成此任务就必

须要有相应的数据结构。
首先,与进程的全部地址空间相关的信息包含在“内存描述符”中,进程描述符的mm字段指向它

。类似于进程描述符,内存描述符也在全局上由双向链表串起。链表头是init_mm,它初始化是进程0使用

的内存描述符。mm_users字段存放共享它的轻量级进程个数。mm_count是使用它的进程个数。要单独引入

mm_count是因为内核线程可以借用其它进程的内存描述符,mm_users为0时释放局部描述符表、线性区描述

符、页表;而当mm_count为0时,就要释放这个内存描述符本身占用内存。内核线程要借用其它进程的内存

描述符是因为它不访问用户空间的线性地址,即它不使用线性区,这样如果给内核线程分配一个内存描述

符会有很多字段用不到,同时又因为任何进程大于0xc0000000的线性地址是相同的,所以内核线程只要随

便借一个进程的内存描述符就可以正常工作。为了支持这种“借”,进程描述符内除了mm还有一个

active_mm字段指出进程当前正在使用的内存描述符。一般进程两者相等,而内核线程的mm为NULL。
其次,由于用户态进程分配的不是页框,而是线性区,为描述这个重要的概念,必须引入线性区

描述符来表示它。它有相关的字段指出线性区本身的起始、末尾及所属的内存描述符。反过来,内存描述

符中也有mmap字段指向第一个线性区,并用mmap_cache字段指向刚访问过的线性区,用map_count指出其中

线性区的数量。为管理方便,进程的线性区也用链表串起来,但当进程是诸如面向对象的数据库这样的拥

有上千个线性区的进程,用mmap->vm_next找链表显然太慢。Linux2.6将线性区也组织在红黑树中加速查找

。因为线性区的起始地址、大小都是4096的倍数,所以它们可由一组连续的页构成。对于页,页表项中有

80x86硬件负责检查的一些权限,在页描述符中,有Linux用的一组权限标志,这里,在线性区描述符中,

又有一组标志指出了相关权限,这里的保护方案是为缺页异常提供的,它规定了什么情况下的访问要产生

缺页异常,而真正的访问权限的实现仍需要通过改变页表项,并且在一些设置下,可以访问但是仍会触发

缺页异常。
对数据结构了解后,就是一些对线性区处理的函数。如给定地址查找邻近或重叠的线性区。在查

找一个空的地址敬意时要注意:用户态线性地址的三分之一是被保留用作可执行文件的正文段、数据段、

bss段的,这样搜索时可避开第一个G,在查找时,为方便,直接从mmap_cache后开始查找。总的来说,分

配线性区间时,先获得新线性区线性地址,再由slab分配函数为新线性区分配描述符并初始化,之后再插

入内存描述符的mm_map和红黑树mm_rb。增加内存描述符的地址空间大小字段。释放时,扫描链表,从链表

中将要删线性区脱开,更新进程页表,将第一阶段找到的线性区删除。
80x86缺页异常处理程序必须区分各种访问情况,做出正确处理。流程较复杂,见P378流程图。
开头提及的分配内存只分线性区,而将页框分配推后到不能推后为止的技术叫“请求调页”。它

由上述的线性区描述符的访问权限配合缺页异常来实现。缺点是必须由内核处理缺页异常,浪费了CPU时钟

周期。而局部性原理可以保证缺页异常发生频率不高,使“请求调页”工作良好。当缺页异常发生且线性

区没有映射到磁盘文件,调用do_anonymous_page()获得页框。处理读时,更无关紧要,只要专门设一个0

页让其读即可。
第一代unix实现了一种傻瓜式的进程创建:直接在fork()时复制整个地址空间。这样太慢且完全

破坏高速缓存内容。现今普遍采用写时复制(Copy On Write),它的思想就是不复制页框,而是将页框设

为共享。此后无论父或子进程想写共享页框,会产生异常,在异常处理中将页复制到一个新页框,标记为

可写,新页框的物理地址最终写入页表项,原页框页描述符_count字段减1,但仍是写保护,除非减1后已

为-1,此时原页框会被释放。
最后,关注进程地址空间的创建与删除:创建时,copy_mm()函数会建立新进程的页表与内存描述

符,如前写时复制,普通进程共享父进程的地址空间,而轻量级进程直接使用它,这也是进程与线程概念

上的本质区别,代码上可表示为:轻量级tsk->mm = current->mm; tsk->active_mm = current->mm;而普

通进程则为tsk->mm = kmem_cache_alloc(mm_cache,SLAB_KERNEL); memcpy(tsk->mm,current-

>mm,sizeof(大小));接下来普通进程会改变复制来的内存描述符的一些字段,最后复制父进程线性区与页

表,将新内存描述符插入全局链表。复制来的线性区插入线性区链表与红黑树,初始化表项以便写时复制

机制。终止进程时,若非内核线程,exit_mm()释放内存描述符和相关数据结构。

版权声明:本文为博主原创文章,未经博主允许不得转载。

 

posted on 2010-02-21 15:52  qqwx  阅读(240)  评论(0编辑  收藏  举报