正如深入linux内核架构中提到的,插入新的内存区域之前,首先要确认,虚拟内存空间中有足够的空间,可用于给定的长度。
这个判断虚拟内存空间中是否有足够的空间的工作是有arch_get_unmapped_area来实现。
系统中的每一个进程都有一个struct mm_struct的实例,这个实例的作用是保存了进程的内存管理信息。
我们看到进程中能够找到查找空闲虚拟内存的方法 arch_get_unmapped_area。
void arch_pick_mmap_layout(struct mm_struct *mm)
{
/*
* Fall back to the standard layout if the personality
* bit is set, or if the expected stack growth is unlimited:
*/
if (mmap_is_legacy()) {
mm->mmap_base = TASK_UNMAPPED_BASE;
mm->get_unmapped_area = arch_get_unmapped_area;
mm->unmap_area = arch_unmap_area;
} else {
mm->mmap_base = mmap_base();
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
mm->unmap_area = arch_unmap_area_topdown;
}
}
--------------------------------------------------------------------------------------
介绍 arch_get_unmapped_area 这个函数
1 首先是介绍用户进程虚拟地址空间的分布情况。
我们知道 3G-4G 的地址空间的分配给Kernel用的,用户进程可用的地址空间是0-3G
这个分界线就是 TASK_SIZE
TASK_SIZE 的大小为 0xc0000000
当然我是描述的是IA-32系统 。严格意义上用户地址空间并不是0~3G,
用户的地址空间里面分成 代码段、堆,内存映射的区域,栈,
其中
A 代码段的起始地址并不是0,取决于ELF标准。 对于IA-32 ,代码段的起始地址是0x08048000
从0地址到text段起始地址之间的部分,大约128M用于捕获NULL指针。
B 堆,没啥好讲的
C 内存映射的起始地址是 mmap->base,一般是 TASK_SIZE的1/3。即从1G开始
D 栈是从上往下生长的,栈的起始位置记为 STACK_TOP,一般起始地址是用户空间的最高地址 3G,当然
这个位置是可以调整一个随机数,每次进程启动的时候,栈顶位置随机改变。
通过上面得描述可以看到,这种布局存在的问题
用户的堆空间可用的比较少,因为 内存映射的起始位置在1G,堆 长着长着就长到内存映射mmap区域里面去了, 这是不能允许的。
所以后来提出了新的布局方案,就是将内存映射区域 mmap区域上移,让它更靠近 栈,同时要求mmap向下生长。
这种布局就是上面代码中的mm->get_unmapped_area = arch_get_unmapped_area_topdown;。
本文之描述mmap区域向上的经典布局。
2 分析函数arch_get_unmapped_area
addr是个参考位置,如果首先align,按页面对齐。
在寻找vma,满足vma的end_addr 大于 addr。
如果找到了这个vma,并且,空间没有交叉, 即 addr + len <= vma->vm_start,
那么OK,打道回府。
if (addr) {
addr = PAGE_ALIGN(addr);
vma = find_vma(mm, addr);
if (TASK_SIZE - len >= addr &&
(!vma || addr + len <= vma->vm_start))
return addr;
}
接下来的代码比较诡异,其实分析mmap区域中hole的来源,就比较好理解了。
查找的起始位置有两种情况:
如果我要分配的长度比mm->cached_hole_size长,也就是说 mmap区域中记录的那个空洞不够长,
我无法把使用这个空洞,因为它不够长,OK,我从空洞开始找起。
如果mmap记录的空洞很大,我完全可以把我要分配的空间分配到洞里面,充分利用空间,
那么,我要从头开始分配,因为,找着找着,很可能就找到这个空洞了,然后就充分利用空洞的空间。
这种机制其实是为了防止洞越来越多。
至于这个洞的起始地址和洞的大小是怎么来的,后面的代码可以清晰的看出来。
if (len > mm->cached_hole_size) {
start_addr = addr = mm->free_area_cache;
} else {
start_addr = addr = TASK_UNMAPPED_BASE;
mm->cached_hole_size = 0;
}
有了遍历整个mmap空间的起始位置,我们可以使用find_vma开始遍历了。
if (TASK_SIZE - len < addr) 这个判断条件是指 一步小心找到了顶 ,3G的位置,惨了,没找的,都找到
天涯海角去了。
如果是从 TASK_UNMAPPED_BASE 开始找的,表示没戏了,找不到空间了返回用户 ENOMEM。
但是如果是从上面提到的洞的位置开始找的,因为只找了洞后面的空间,虽然没找到,但是洞前面可能会找到,从TASK_UNMAPPED_BASE 继续找。
什么情况下是表示找到了我要的空间呢:
1 !vma 表示现存的空间,没有结束位置没有在addr之前的,表示addr到了一片边缘地带,我最偏远,
OK ,这个无人耕种的地就为我用了,返回addr
2 addr + len <= vma->vm_start
这个条件的意思是,不但 vm_end在我上面,vm_start也在我上面,OK,我从addr开始伸腿,腿长len,
不会骚扰到vma,因为我们没有交集。 这种情况也很好,表示我找到一个空间来伸腿。即addr 。
这种情况下可能会有洞,我伸腿伸到的最远处,到vma的起始位置,这篇区域属于无人认领的区域,
所以把洞的信息更新一下,洞的起始位置为 addr + len;
if (!vma || addr + len <= vma->vm_start) {
/*
* Remember the place where we stopped the search:
*/
mm->free_area_cache = addr + len;
return addr;
}
如果这两种条件都不满足,表示还没找到,需要继续找,因为你当前的位置 你尝试了下伸腿,发现伸到了
vma的区域里面去了,那么你不能用这块小空地。这就有可能有个空洞,(当然也可能没有空洞,就是说你坐在了vma的地盘之内,你伸不伸腿都侵犯了vma的地盘。这并不重要)。既然有空洞,就有可能目前这个洞比之前的洞还大。这种情况下需要更新洞的信息 。
if (addr + mm->cached_hole_size < vma->vm_start)
mm->cached_hole_size = vma->vm_start - addr;
然后从vma_end继续找,即代码addr = vma->vm_end;
-------------------------------------------------------------------------------------
unsigned long
arch_get_unmapped_area(struct file *filp, unsigned long addr,
unsigned long len, unsigned long pgoff, unsigned long flags)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long start_addr;
if (len > TASK_SIZE)
return -ENOMEM;
if (flags & MAP_FIXED)
return 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;
}
if (len > mm->cached_hole_size) {
start_addr = addr = mm->free_area_cache;
} else {
start_addr = addr = TASK_UNMAPPED_BASE;
mm->cached_hole_size = 0;
}
full_search:
for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
/* At this point: (!vma || addr < vma->vm_end). */
if (TASK_SIZE - len < addr) {
/*
* Start a new search - just in case we missed
* some holes.
*/
if (start_addr != TASK_UNMAPPED_BASE) {
addr = TASK_UNMAPPED_BASE;
start_addr = addr;
mm->cached_hole_size = 0;
goto full_search;
}
return -ENOMEM;
}
if (!vma || addr + len <= vma->vm_start) {
/*
* Remember the place where we stopped the search:
*/
mm->free_area_cache = addr + len;
return addr;
}
if (addr + mm->cached_hole_size < vma->vm_start)
mm->cached_hole_size = vma->vm_start - addr;
addr = vma->vm_end;
}
}