内核内存布局

电子说

1.3w人已加入

描述

 

一、内核内存布局

64位Linux一般使用48位表示虚拟地址空间,43位表示物理地址,通过命令:cat /proc/cpuinfo

处理器
  • ARM64架构处理器采用48位物理寻址机制,最大可寻找256TB的物理地址空间。对于 目前应用完全足够,不需要扩展到64位的物理寻址。虚拟地址也同样最大支持48位寻址,所以 在处理器架构设计上,把虚拟地址空间划分为两个空间,每个空间最大支持256TB,linux内核 在大多数体系结构上都把两个地址划分为:用户空间和内核空间。

  • 用户空间:0x0000_0000_0000_0000至0x0000_ffff_ffff_ffff;

  • 内核空间:0xffff_0000_0000_0000至0xffff_ffff_ffff_ffff;

QEMU平台,可以打印ARM64架构linux内核内存分布情况

处理器

二、堆管理

堆是进程中主要用于动态分配变量和数据的内存区域,堆的管理对应程序员不是直接可见的。因为它依赖标准库提供的各个辅助函数(其中最重要的是malloc)来分配任意长度的内存区。malloc和内核之间的经典接口是brk系统调用,负责扩展/收缩堆。

处理器
  • 堆是一个连续的内存区域,在扩展时自下至上增长。其中mm_struct结构,包含堆在虚拟地 址空间中的起始和当前结束地址(start_brk和brk)。
  • brk系统调用用于指定堆在虚拟地址空间中新的结束地址(如果堆将要收缩,当然可以小于当前值)。brk系统调用通过do_brk增长动态分配区(内核源码分mm/mmap.c)

三、sys_brk流程

  1. 检查资源限制;

  2. 将brk值对齐到页;

  3. 是否想增加brk值?(这个地方要结合源码看)

    是-->do_brk();返回新的brk的值;

    否-->do_munmap();返回新的brk的值;

brk机制不是一个独立的内核概念,而是基于匿名映射实现,以减少内部的开销。在检查过用brk的值的新地址未超出推的限制之后,sys_brk第一个重要操作是请求的地址按页长对齐。brk()用于进程向内核申请空间,用于扩展用户堆栈空间,或者回收堆栈空间。

  • malloc为小空间申请,brk()为大块空间申请。do_brk()用于增长动态分配区。do_munmap()释放动态分配区;
  • do_brk()源码分析:
static unsigned long do_brk(unsigned long addr, unsigned long len)
{
 struct mm_struct *mm = current->mm;
 struct vm_area_struct *vma, *prev;
 unsigned long flags;
 struct rb_node **rb_link, *rb_parent;
 pgoff_t pgoff = addr >> PAGE_SHIFT;
 int error;

 // 首先对len这个长度进行页面对齐去判断页面对齐之后是否超出边界
 len = PAGE_ALIGN(len);
 if (!len)
  return addr;

 flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;

 // 检查是否有足够内存空间来分析len大小的内存。判断虚拟地址空间是否足够
 error = get_unmapped_area(NULL, addr, len, 0, MAP_FIXED);
 if (offset_in_page(error))
  return error;

 error = mlock_future_check(mm, mm->def_flags, len);
 if (error)
  return error;

 /*
  * mm->mmap_sem is required to protect against another thread
  * changing the mappings in case we sleep.
  */
 verify_mm_writelocked(mm);

 /*
  * Clear old maps.  this also does some error checking for us
  */
  // 循环遍历用户进程红黑树中VMA,然后根据addr来查找合适的插入点
 while (find_vma_links(mm, addr, addr + len, &prev, &rb_link,
         &rb_parent)) {
  if (do_munmap(mm, addr, len))
   return -ENOMEM;
 }

 /* Check against address space limits *after* clearing old maps... */
    // 检查是否要对此虚拟区间进行扩充
 if (!may_expand_vm(mm, len >> PAGE_SHIFT))
  return -ENOMEM;

 if (mm->map_count > sysctl_max_map_count)
  return -ENOMEM;
 // 判断系统是否有足够内存
 if (security_vm_enough_memory_mm(mm, len >> PAGE_SHIFT))
  return -ENOMEM;

 /* Can we just expand an old private anonymous mapping? */
    // 判读是否可以合并,如果可以合并就合并成为一个vam区
 vma = vma_merge(mm, prev, addr, addr + len, flags,
   NULLNULL, pgoff, NULL, NULL_VM_UFFD_CTX);
 
    // 如果能合并直接goto out
    if (vma)
  goto out;

 /*
  * create a vma struct for an anonymous mapping
  */
    
    //如果没有办法合并,只有新创建一个VMA,VMA地址空间是【addr,addr+len】
 vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
 if (!vma) {
  vm_unacct_memory(len >> PAGE_SHIFT);
  return -ENOMEM;
 }

    // 指向匿名域指针
 INIT_LIST_HEAD(&vma->anon_vma_chain);
 vma->vm_mm = mm; // 指向VMA所属于进程struct mm_struct结构
 vma->vm_start = addr;
 vma->vm_end = addr + len;
 vma->vm_pgoff = pgoff;
 vma->vm_flags = flags;
 vma->vm_page_prot = vm_get_page_prot(flags);
 vma_link(mm, vma, prev, rb_link, rb_parent);
out: // 增加进程地址空间长度
 perf_event_mmap(vma);
 mm->total_vm += len >> PAGE_SHIFT;
 if (flags & VM_LOCKED)
  mm->locked_vm += (len >> PAGE_SHIFT);
 vma->vm_flags |= VM_SOFTDIRTY;
 return addr;
}

 

- END -

 


审核编辑 :李倩

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分