暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

linux的进程地址空间(三)

术道经纬 2019-06-19
124

mm_struct

上文vm_area_struct中还有一个vm_mm没讲到,而这个vm_mm,则是联系vm_area_struct和它所属进程的关键纽带。它指向的是负责管理内存的mm_struct结构体,而这个mm_struct又可以从task_struct这个几乎记录了一个进程所有信息的结构体中获取。

来看下struct mm_struct中与vma相关的元素有哪些:

struct mm_struct
{
struct vm_area_struct * mmap;
struct rb_root mm_rb;
int map_count;
unsigned long total_vm;
struct vm_area_struct * mmap_cache;
unsigned long (*)()get_unmapped_area;
...
}

其中mmap指向vma链表的头节点,mm_rb指向vma红黑树的根节点。map_count是vma的总个数,total_vm是进程地址空间的总大小(以page为单位)。

mmap_cache保存了上一次找到的vma,根据局部性原理,下一次要用到的vma正好是上次使用的vma的可能性是比较大的,因此查找vma时(使用find_vma())会首先从mmap_cache中找,找到了就直接返回(命中率通常大约是35%)。

vma = mm->mmap_cache;    
if (vma && vma->vm_end > addr && vma->vm_start <= addr)
return vma;

没找到再去红黑树里面找:

rb_node = mm->mm_rb.rb_node;    
vma = NULL;
while (rb_node) {
vma_tmp = rb_entry(rb_node, struct vm_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;

对vma的操作除了查找,还有增加和删除。

加载一个动态链接库或者通过mmap创建映射时,都需要在进程地址空间中增加一个vma。具体过程是首先通过get_unmapped_area()找到虚拟地址空间中一块空闲且大小满足要求的区域(根据你上报的你家的人数,给你街道中一个住的下你家所有人的空房子),分配给新vma并设置其flag属性(限制你家对这个房子的使用,比如只能住,不能私自改建),返回该vma起始处的虚拟地址(告诉你这个房子的门牌号)。当然,你出于某种目的,也可以指定就街道上的某间房子(调用mmap()时指定参数addr),如果这间房子正好是空的,就可以分配给你。

if (addr) {
addr = PAGE_ALIGN(addr);
vma = find_vma(mm, addr);
if (TASK_SIZE -len >= addr &&
(!vma || addr + len <= vma->vm_start))
return addr;
}

这里的房子有点特殊,街道上房间的总数是固定的,每个房间的大小是4平方米(页面大小4KB),只要是相邻的空房间,就可以组成一个空房子。房间总数也是有限的(3GB内存的话差不多是75万个房间),你来晚了,或者你狮子大开口,要一个50万房间的空房子(比如通过malloc(2G)),那就有可能出现分配不到的情况(可用虚拟地址空间不足)。

如果新建的vma和它地址上紧挨着的vma有相同的属性,且基于相同的映射对象(比如是同一个文件),则还会产生vma的合并(上下两层楼打通,做成一个跃层)。减少vma的数量有利于减轻内核的管理工作量,降低系统开销。如果没有发生合并,则需要调用insert_vm_struct( )在vma链表和vma红黑树中分别插入代表新vma的节点(给你家的房子被街道办事处登记,方便日后管理)。

要注意的是,房子的分配是按照你上报的人数,但具体给你几个房间的钥匙(分配几个物理页面),取决于你家实际住进来的人数,比如你申请的是10个房间,但只住进来3个人,就只有3个房间的钥匙,剩下的钥匙等真正有人搬进来再给,房间资源有限,占着不住不是浪费么。分配的vma只是这段虚拟地址的使用权,而不是物理地址的使用权。

那是不是我申请成功10个房间,就可以保证10个人都能住进来呢?这个嘛,街道(进程)最开始也是这样以为的,后来出现了房间申请成功,结果拿钥匙开不了门的情况,街道就向上级管理者(内核)反映啊,这才被告知了一个残酷的现实:除了本条街道,还有很多条其他街道,大家处在一个平行空间中(虚拟地址空间都是0~3GB),这70多万个房间,其实是被所有街道共享的,谁先拿到一个房间的钥匙(使用物理页面),谁才真正拥有这个房间。一切都是假象……然后街道问上级:那你为啥一直允许我们街道上的人一口气申请那么多房间呢?上级若有所思的说:我当时设计这个制度啊,主要是考虑到很多人可能申请的多,但实际用不了那么多(比如malloc(2G),但实际只用了1M),我也不知道实际谁会用的多一点,为了让资源(这些房间)得到最充分的利用,我只能先允许他们申请着。以后这种事情多了,大家也渐渐明白,别一下申请那么多,不合理的需求,到了上级那里是通不过的,这种申请超过实际可用物理内存的现象,被称为memory overcommit。

通过munmap()解除映射时,则需要在进程地址空间中删除对应的vma,并释放该vma占有的虚拟地址资源。


参考:

《understanding the linux kernel》

《professional linux kernel architecture》

《understanding the linux virtual memory manager》


文章转载自术道经纬,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论