linux内存布局的内核实现–用户空间的映射方式

引用牛人的一个表格(本人绘制能力实在有限,引自:http://blog.csdn.net/absurd/archive/2006/06/19/814268.aspx)

Linux的内存模型,一般为:

地址

作用

说明

>=0xc000 0000

内核虚拟存储器

用户代码不可见区域

<0xc000 0000

Stack(用户栈)

ESP指向栈顶

 

空闲内存

>=0x4000 0000

文件映射区

 

<0x4000 0000

空闲内存

 

Heap(运行时堆)

通过brk/sbrk系统调用扩大堆,向上增长。

 

.data、.bss(读写段)

从可执行文件中加载

>=0x0804 8000

.init、.text、.rodata(只读段)

从可执行文件中加载

<0x0804 8000

保留区域

 

这 里的数据是怎么得到的呢?我们一般用到的malloc是从哪里分配的内存呢?为什么从这里分配呢?实际上malloc是c库的内存分配管理函数,其种种弊 端使得人们不太喜欢它,从而使人们写出很多自己的内存分配函数实现,不管哪种实现在linux下都是调用系统调用brk实现的,在内核当中就是 sys_brk。
我们知道,一个进程的所有的地址空间是按照区域组织的,每个区域都是一个vm_area_struct的结构体,每个结构体内部的地址是连续的,而不同的 结构体之间可能留有空洞,既然这样,不管brk出来的空间还是其它,比如说映射得到的空间都是vm_area_struct的表现,那么为何0x4000 0000是个分界点呢?之上的就是映射空间而之下的就是brk空间即堆空间呢?我们只有看一下这两种实现的内核源代码,分别是:sys_mmap2和 sys_brk。在可能具体代码之前首先要明白一件事就是:为了管理方便,所有的vm_area_struct的首地址和末地址都是页对齐的。好了,现在 可以看相关的代码了(我省略了很多不相关的代码,那些很重要,但不是这篇文章要说的):
asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
                           unsigned long prot, unsigned long flags,
                           unsigned long fd, unsigned long pgoff)
{
...
         down_write(&mm->mmap_sem);
         error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);
...         return error;
}
unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,
                         unsigned long len, unsigned long prot,
                         unsigned long flags, unsigned long pgoff)
{
         len = PAGE_ALIGN(len); //对齐了长度,使得首地址加上长度也是页对齐
...
         addr = get_unmapped_area(file, addr, len, pgoff, flags);//此函数内部对齐了addr
         if (addr & ~PAGE_MASK)  //如果现在都不是页对齐的,那么返回的肯定是错误码,返回之
                 return addr;
...
}
unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
                 unsigned long pgoff, unsigned long flags)
{
         unsigned long (*get_area)(struct file *, unsigned long,
                                   unsigned long, unsigned long, unsigned long);
         get_area = current->mm->get_unmapped_area;
         if (file && file->f_op && file->f_op->get_unmapped_area)
                 get_area = file->f_op->get_unmapped_area;
         addr = get_area(file, addr, len, pgoff, flags);
         if (IS_ERR_VALUE(addr))  //这个宏很有意思,值得研究,如果返回真,那么addr就是一个错误码了,返回之
                 return addr;
         if (addr > TASK_SIZE - len)//跨到了内核空间,返回错误码
                 return -ENOMEM;
         if (addr & ~PAGE_MASK)  //到此还是非页对齐,返回错误码
                 return -EINVAL;
         return arch_rebalance_pgtables(addr, len);
}
在很多平台get_unmapped_area就是mm/mmap.c中定义的arch_get_unmapped_area
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)  //如果有MAP_FIXED标志那么就直接返回addr,其实在MAP_FIXED
设置的前提下,用户传入的addr必须是页对齐的,若不是则出错,这里将addr返回,如果addr不是页对齐,那么get_unmapped_area中的if (addr & ~PAGE_MASK)验证将无法通过。
                 return addr;
         if (addr) {  //如果用户提供了addr则优先考虑用户提供的addr,但是不保证这个addr就是最后返回的addr。下面还是要说一下这里的要点。
                 addr = PAGE_ALIGN(addr);
                 vma = find_vma(mm, addr);
                 if (TASK_SIZE - len >= addr &&
                     (!vma || addr + len <= vma->vm_start))//addr的地址没有被映射,而且空洞足够我们这次的映射,那么返回addr以准备这次的映射
                         return addr;
         }
         if (len > mm->cached_hole_size) {  //我们需要的长度大于当前的vm区域间的空洞
                 start_addr = addr = mm->free_area_cache;//从上次的查询位置开始
         } else {  //需要的长度小于当前空洞,为了不至于时间浪费,那么从0开始搜寻,这里的
搜寻基地址TASK_UNMAPPED_BASE很重要,用户mmap的地址的基地址必须在TASK_UNMAPPED_BASE之上,但是一定这样严格 吗?看上面的if (addr)判断,如果用户给了一个地址在TASK_UNMAPPED_BASE之下,映射实际上还是会发生的。
                 start_addr = addr = TASK_UNMAPPED_BASE;
                 mm->cached_hole_size = 0;//空洞大小从0开始,以后逐渐增加
         }
full_search:
         for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
                 if (TASK_SIZE - len < addr) {//跨越了内核边界,但是可能是当前空洞值太小造成的,而我们开始搜索时的基地址在需要长度大于当前空洞长度时是 mm->free_area_cache,因此下面的if判断当然可以通过,所以从新开始,从TASK_UNMAPPED_BASE开始搜索,若开 始地址就是TASK_UNMAPPED_BASE,那么肯定出错了
                         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) {
                         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;
         }
}
TASK_UNMAPPED_BASE就是mmap时的地址限制,我们看一眼TASK_UNMAPPED_BASE的定义:
#define TASK_UNMAPPED_BASE      (PAGE_ALIGN(TASK_SIZE / 3))
就是1g的位置,即0x40000000的位置。可是这种限制并不是强制的,如果我做以下程序:
#include

int main()

{

    char * buf;

    buf = (char*)mmap(0x30000000,1024,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,-1,0);

}

用gdb调试可以看出buf的地址确实是0x30000000,也就是0x40000000以下的地址,这么说,linux的内存布局并不是强制用户执行 的,把策略完全留给用户,内核真是惯坏了用户,仅仅提供给用户一个建议而已。说完了sys_mmap2的逻辑简单谈谈sys_brk就可以了,每个 mm_struct都有一个brk字段,这个字段记录着用户堆的位置,每次用户调用brk的时候sys_brk都回扩展或者缩减堆空间,本质上也是映射 vm_area_struct结构,而brk的限制是在elf文件里面确定的,elf文件格式规定了堆,bss变量,栈在哪里,但是这些都不是强制的。

通过这里的分析,我们看到,linux的内存布局是非常灵活的,看到很多人问怎么把内核空间扩展成2g,正如windows一样,这个其实是很简单的,将 sys_mmap和sys_brk的检测条件一改就可以,实际不用改动任何这些代码,仅仅改TASK_SIZE宏就可以了,用户空间分配内存的唯一限制就 是不能超越内核边界,别的限制就不是那么重要了,那只是用户程序自己的事情,而分配用户内存就是映射vm_area_struct结构,进一步仅仅在 sys_mmap2和sys_brk里会有如此动作,因此扩展内核空间的任务就会很简单,那么内核的内存怎么映射呢?这就完全是内核的事情了,无非有两种 方式,一种是一一映射,另一种是非线性映射(包括类似高端内存的临时映射方式)。

原文链接: https://blog.csdn.net/dog250/article/details/5303627

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    linux内存布局的内核实现--用户空间的映射方式

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/410033

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午11:46
下一篇 2023年4月26日 上午11:46

相关推荐