进程地址空间

6.4.2 进程的虚拟空间

如前所述,每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G的范围内可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用。

那么,内核怎样管理每个进程3G的虚存空间呢?概括地说,用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如图6.16所示:

 



                              

进程虚拟空间(3G)

 

 

 

       

 图6.16  进程虚拟空间的划分

由图可以看出,堆栈空间安排在虚存空间的顶部,运行时由顶向下延伸;代码段和数据段则在低部,运行时并不向上延伸。从数据段的顶部到堆栈段地址的下沿这个区间是一个巨大的空洞,这就是进程在运行时可以动态分配的空间(也叫动态内存)。

进程在运行过程中,可能会通过系统调用mmap动态申请虚拟内存或释放已分配的内存,新分配的虚拟内存必须和进程已有的虚拟地址链接起来才能使用;Linux 进程可以使用共享的程序库代码或数据,这样,共享库的代码和数据也需要链接到进程已有的虚拟地址中。在后面我们还会看到,系统利用了请页机制来避免对物理内存的过分使用。因为进程可能会访问当前不在物理内存中的虚拟内存,这时,操作系统通过请页机制把数据从磁盘装入到物理内存。为此,系统需要修改进程的页表,以便标志虚拟页已经装入到物理内存中,同时,Linux 还需要知道进程虚拟空间中任何一个虚拟地址区间的来源和当前所在位置,以便能够装入物理内存。

由于上面这些原因,Linux 采用了比较复杂的数据结构跟踪进程的虚拟地址。在进程的 task_struct结构中包含一个指向 mm_struct 结构的指针。进程的mm_struct 则包含装入的可执行映象信息以及进程的页目录指针pgd。该结构还包含有指向 vm_area_struct 结构的几个指针,每个 vm_area_struct 代表进程的一个虚拟地址区间。

 




图6.17  进程虚拟地址示意图

图 6.17是某个进程的虚拟内存简化布局以及相应的几个数据结构之间的关系。从图中可以看出,系统以虚拟内存地址的降序排列 vm_area_struct。在进程的运行过程中,Linux 要经常为进程分配虚拟地址区间,或者因为从交换文件中装入内存而修改虚拟地址信息,因此,vm_area_struct结构的访问时间就成了性能的关键因素。为此,除链表结构外,Linux 还利用 红黑(Red_black)树来组织 vm_area_struct。通过这种树结构,Linux 可以快速定位某个虚拟内存地址。

当进程利用系统调用动态分配内存时,Linux 首先分配一个 vm_area_struct 结构,并链接到进程的虚拟内存链表中,当后续的指令访问这一内存区间时,因为 Linux 尚未分配相应的物理内存,因此处理器在进行虚拟地址到物理地址的映射时会产生缺页异常(请看请页机制),当 Linux 处理这一缺页异常时,就可以为新的虚拟内存区分配实际的物理内存。

在内核中,经常会用到这样的操作:给定一个属于某个进程的虚拟地址,要求找到其所属的区间以及vma_area_struct结构,这是由find_vma()来实现的,其实现代码在mm/mmap.c中:

 

   * Look up the first VMA which satisfies  addr< vm_end,  NULL if none. */

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

{

        struct vm_area_struct *vma = NULL;

 

        if (mm) {

                /* Check the cache first. */

                /* (Cache hit rate is typically around 35%.) */

               vma = mm->mmap_cache;

                if (!(vma && vma->vm_end > addr&& vma->vm_start <= addr)) {

                       rb_node_t* rb_node;

 

                        rb_node = mm->mm_rb.rb_node;

                        vma = NULL;

 

                       while (rb_node) {

                              struct vm_area_struct * vma_tmp;

 

                             vma_tmp = rb_entry(rb_node, structvm_area_struct, vm_rb);

 

                              if (vma_tmp->vm_end > addr) {

                                        vma = vma_tmp;

                                       if (vma_tmp->vm_start <= addr)

                                               break;

                                      rb_node = rb_node->rb_left;

                                } else

                                        rb_node= rb_node->rb_right;

                       }

                        if (vma)

                                mm->mmap_cache = vma;

               }

        }

        return vma;

}

   这个函数比较简单,我们对其主要点给予解释:

·      参数的含义:函数有两个参数,一个是指向mm_struct结构的指针,这表示一个进程的虚拟地址空间;一个是地址,表示该进程虚拟地址空间中的一个地址。

·      条件检查:首先检查这个地址是否恰好落在上一次(最近一次)所访问的区间中。根据代码作者的注释,命中率一般达到35%,这也是mm_struct结构中设置mmap_cache指针的原因。如果没有命中,那就要在红黑树中进行搜索,红黑树与AVL树类似。

·      查找节点:如果已经建立了红黑树结构(rb_rode不为空),就在红黑树中搜索。

·      如果找到指定地址所在的区间,就把mmap_cache指针设置成指向所找到的vm_area_struct结构。

·      如果没有找到,说明该地址所在的区间还没有建立,此时,就得建立一个新的虚拟区间,再调用insert_vm_struct()函数将新建立的区间插入到vm_struct中的线性队列或红黑树中。


(GFree_Wind 2011-06-01 23:29

Bean_lee: 我的理解是这样的,你说的BIOS 显卡ROM等是映射到的是linux 内核空间,
3G 以上的空间。因为 Linux 内核的代码段是从c0100000开始,前面的1M空间是留给 BIOS 等.....
是我记错了,那个BIOS确实是映射到内存空间的开头部分。
找到了一种解释,为啥是从0x0804800开始。
The reason that SVR4 chose that particular address is that the stacktop was located at 0x08000000 (growing downward, of course), and thenthe area between 0x08000000 and 0x08048000 was reserved for libc andpossibly other system service code.
)




Bruce--->vma如何排列(升序or降序)?从find_vma_prepare()返回的prev来看,应该是升序。

查找使用红黑树(注意,是树形结构, 当更新左右树时, 新的树的左右不是原来的, 差点以为是死循环!),

http://blog.csdn.net/dog250/article/details/5303243


以上转。


get_unmapped_area():

2个条件match(使用find_vma查找,)

 1. vma == null (addr > all vm_end), or

2. vm_start>=addr+len(addr修正为page_size对齐)


insert_vm_struct():调用find_vma_prepare和vm_link

新的vm是在vm mm_rb中没有,它会作为mm_rm的叶子节点存在。所以在find_vma_prepare返回的__vma_tmp的vm_start是应该比新的vm的vm_end大的。


do_map: 是上述函数的调用加一些检查。 如果设置了VM_LOCK标志, 就调用make_pages_present()连续分配线性区的所有页,如果没有页框映射, 就调用handle_mm_fault()分配页框。


do_munmap():主要多了一个unmap后的线性区的处理(分成两个小线性区), split_vma().


handle_mm_fault()完成请求调页(被访问的页不存在)和写时复制(写只读页框)

内存描述符的rss字段记录了分配给进程的页框总数。


关于写时复制: 任何父子进程写时将分配页框, 保持原有页框的写保护属性。当检查到该进程是这个页框的唯一属主, 就把该页框标志为该进程可写(页描述符的_count字段为0)。


非连续内存缺页,see p390








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值