rb_subtree_gap

kernel为了提高对VMA 查找、插入速度,采用了很多技术,例如VMA采用RB tree和 双向链表两种组织形式,另外对VMA结构对cache line对齐从而提高cache命中率等。由于内存在实际使用过程中,不断的申请释放,使得原来成片的连续内存空间变成了一段段不连续空间,每个VMA都存在一定空闲空间即gap

在查找GAP时,一般为了知道GAP大小,需要知道前一个VMA prev的end地址和当前VMA的start地址即GAP=vma->start-prev->end.此时需要知道两个VMA。linux内核中为了加速查找适合的GAP,对此进行了优化 vma结构中增加了 rb_subtree_gap 字段,用于记录当前vma 的前面有多少空闲空间

struct vm_area_struct {.... .../** Largest free memory gap in bytes to the left of this VMA.* Either between this VMA and vma->vm_prev, or between one of the* VMAs below us in the VMA rbtree and its ->vm_prev. This helps* get_unmapped_area find a free area of the right size.*/unsigned long rb_subtree_gap;... ...
} __randomize_layout;

rb_subtree_gap:用于记录该VMA左侧有多少空间,即vma->prev 和 vma之前的空闲空间,以便能够快速查找当前vma前面空闲空间是否符合要求。

vma subtree Gap 计算

vma GAP计算相对比较简单,函数为vma_compute_subtree_gap()

static inline unsigned long vma_compute_gap(struct vm_area_struct *vma)
{unsigned long gap, prev_end;/** Note: in the rare case of a VM_GROWSDOWN above a VM_GROWSUP, we* allow two stack_guard_gaps between them here, and when choosing* an unmapped area; whereas when expanding we only require one.* That's a little inconsistent, but keeps the code here simpler.*/gap = vm_start_gap(vma);if (vma->vm_prev) {prev_end = vm_end_gap(vma->vm_prev);if (gap > prev_end)gap -= prev_end;elsegap = 0;}return gap;
}

计算相对比较简单:

  • vm_start_gap: 获取当前vma 的起始地址;
  • 如果vma->vm_prev 为空,则意味着前面空间都是空闲空间
  • 如果vma->vm_prev为非空,获取vma指向的前一个vma即vma->vm_prev的结束地址
  • 如果prev_end 大于等于 vma的起始地址(严格来说不可能大于 只能等于),则说明没有空闲空间返回为0
  • 如果prev 小于gap,则说明有空闲时间。

vm_start_gap

vm_start_gap()为获取vma的起始地址进行了封装:

static inline unsigned long vm_start_gap(struct vm_area_struct *vma)
{unsigned long vm_start = vma->vm_start;if (vma->vm_flags & VM_GROWSDOWN) {vm_start -= stack_guard_gap;if (vm_start > vma->vm_start)vm_start = 0;}return vm_start;
}

如果当前vma 设置了VM_GROWSDOWN, 则说明该vma设置了保护页,需要将在vma的起始地址vma_start 减去stack_guard_gap即将保护页空间给预留出来

vm_end_gap

vm_end_gap即获取当前vma的 end结束地址:

static inline unsigned long vm_end_gap(struct vm_area_struct *vma)
{unsigned long vm_end = vma->vm_end;if (vma->vm_flags & VM_GROWSUP) {vm_end += stack_guard_gap;if (vm_end < vma->vm_end)vm_end = -PAGE_SIZE;}return vm_end;
}

如果vma 设置VM_GROWSUP,则说明vma需要在其右边即后面设置预留保护页,所以vm_end 要加上stack_guard_gap.

vma gap update

vma gap 一般在创建一个新的vma时,将新的vma插入到rb tree和链表后,需要更新当前vma插入节点时next vma的gap:

void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,struct rb_node **rb_link, struct rb_node *rb_parent)
{/* Update tracking information for the gap following the new vma. */if (vma->vm_next)vma_gap_update(vma->vm_next);elsemm->highest_vm_end = vm_end_gap(vma);/** vma->vm_prev wasn't known when we followed the rbtree to find the* correct insertion point for that vma. As a result, we could not* update the vma vm_rb parents rb_subtree_gap values on the way down.* So, we first insert the vma with a zero rb_subtree_gap value* (to be consistent with what we did on the way down), and then* immediately update the gap to the correct value. Finally we* rebalance the rbtree after all augmented values have been set.*/rb_link_node(&vma->vm_rb, rb_parent, rb_link);vma->rb_subtree_gap = 0;vma_gap_update(vma);vma_rb_insert(vma, &mm->mm_rb);
}

调用 vma_gap_update()函数进行更新:

vma_gap_update

vma_gap_update是更新当前vma gap大小:

/** Update augmented rbtree rb_subtree_gap values after vma->vm_start or* vma->vm_prev->vm_end values changed, without modifying the vma's position* in the rbtree.*/
static void vma_gap_update(struct vm_area_struct *vma)
{/** As it turns out, RB_DECLARE_CALLBACKS_MAX() already created* a callback function that does exactly what we want.*/vma_gap_callbacks_propagate(&vma->vm_rb, NULL);
}

vma_gap_callbacks_propagate()调用的回调使用的是RB_DECLARE_CALLBACKS_MAX 定义:

RB_DECLARE_CALLBACKS_MAX

该宏为一个回调函数定义的宏:

#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD,          \RBTYPE, RBAUGMENTED, RBCOMPUTE)        \
static inline bool RBNAME ## _compute_max(RBSTRUCT *node, bool exit)          \
{                                         \RBSTRUCT *child;                           \RBTYPE max = RBCOMPUTE(node);                         \if (node->RBFIELD.rb_left) {                        \child = rb_entry(node->RBFIELD.rb_left, RBSTRUCT, RBFIELD);   \if (child->RBAUGMENTED > max)                     \max = child->RBAUGMENTED;                  \}                                      \if (node->RBFIELD.rb_right) {                       \child = rb_entry(node->RBFIELD.rb_right, RBSTRUCT, RBFIELD);  \if (child->RBAUGMENTED > max)                     \max = child->RBAUGMENTED;                  \}                                      \if (exit && node->RBAUGMENTED == max)                     \return true;                           \node->RBAUGMENTED = max;                       \return false;                                  \
}                                         \
RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME,                        \RBSTRUCT, RBFIELD, RBAUGMENTED, RBNAME ## _compute_max)

该宏定义为 RBNAME ## _compute_max 函数名,而vma gap回调定义为:

RB_DECLARE_CALLBACKS_MAX(static, vma_gap_callbacks,struct vm_area_struct, vm_rb,unsigned long, rb_subtree_gap, vma_compute_gap)

通过展开可知定义一个vma_gap_callbacks_compute_max函数,而RB_DECLARE_CALLBACKS_MAX中 使用RB_DECLARE_CALLBACK进一步定义回调函数:

#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME,               \RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \
static inline void                          \
RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop)      \
{                                   \while (rb != stop) {                      \RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \if (RBCOMPUTE(node, true))             \break;                     \rb = rb_parent(&node->RBFIELD);                \}                              \
}       

通过RBNAME##_propagate宏定义可知进一步展开为vma_gap_callbacks_propagate函数,该函数定义展开找到。

linux内核那些事之 VMA Gap相关推荐

  1. linux内核那些事之VMA常用操作

    内核对vm_area_struct 数据结构相关操作进行了一系列封装,方面进行后续操作,vma的操作实现大部分位于mm\mmap.c文件中 find_vma() find_vma()是内核中经常需要查 ...

  2. linux内核那些事之mmap_region流程梳理

    承接<linux内核那些事之mmap>,mmap_region()是申请一个用户进程虚拟空间 并根据匿名映射或者文件映射做出相应动作,是实现mmap关键函数,趁这几天有空闲时间 整理下mm ...

  3. linux内核那些事之buddy(慢速申请内存__alloc_pages_slowpath)(5)

    内核提供__alloc_pages_nodemask接口申请物理内存主要分为两个部分:快速申请物理内存get_page_from_freelist(linux内核那些事之buddy(快速分配get_p ...

  4. linux内核那些事之buddy(anti-fragment机制)(4)

    程序运行过程中,有些内存是短暂的驻留 用完一段时间之后就可以将内存释放以供后面再次使用,但是有些内存一旦申请之后,会长期使用而得不到释放.长久运行有可能造成碎片.以<professional l ...

  5. linux内核那些事之buddy

    buddy算法是内核中比较古老的一个模块,很好的解决了相邻物理内存碎片的问题即"内碎片问题",同时有兼顾内存申请和释放效率问题,内核从引入该算法之后一直都能够在各种设备上完好运行, ...

  6. linux内核那些事之pg_data_t、zone结构初始化

    free_area_init 继续接着<linux内核那些事之ZONE>,分析内核物理内存初始化过程,zone_sizes_init()在开始阶段主要负责对各个类型zone 大小进行计算, ...

  7. linux内核那些事之Sparse vmemmap

    <inux内核那些事之物理内存模型之SPARCE(3)>中指出在传统的sparse 内存模型中,每个mem_section都有一个属于自己的section_mem_map,如下图所示: 而 ...

  8. linux内核那些事之buddy(anti-fragment机制-steal page)(5)

    继<linux内核那些事之buddy(anti-fragment机制)(4)>,在同一个zone内指定的migrate type中没有足够内存,会启动fallback机制,从fallbac ...

  9. linux内核那些事之用户空间管理

    内核主要数据结构 linux内核将用户空间抽象成struct vm_area_struct进行管理,每申请以个用户空间在内核中都会抽象成对应的vm_are_struct进行管理,同时为了区别不同进程的 ...

最新文章

  1. 解决nginx 502 bad gateway--团队的力量
  2. 5G会用什么样的语音通信方案?
  3. Appium环境搭建简介
  4. STM32 基础系列教程 14 - IIC
  5. 竞价账户整改技巧-小脑袋竞价软件
  6. 并查集(图论) LA 3644 X-Plosives
  7. 集群(二)——LVS-DR-Keepalived
  8. xvhfeng的工作回忆总结(第二年)阅读手记
  9. jsp在mysql中删除数据_如何在jsp页面中删除数据库中的数据
  10. 网页速度很慢优化方案:如何提高网页加载速度,提升网站加载速度
  11. Entity Framework Core 7.0 未来规划
  12. 网页脚本基本java语法_JSP学习(一)JSP基础语法
  13. 迅雷精简版 4.0.0 Mac中文版
  14. Visual C++ 2010创建Ribbon界面
  15. 【方差分析】之matlab求解
  16. Docker镜像分层原理-联合文件系统(UnionFS)
  17. 如何设置excel表格表头冻结_excel怎么冻结窗口固定表头_excel冻结窗口固定表头详细教程 - 系统家园...
  18. 纵向时间线html,51个css时间轴
  19. 稀土配合物Ln(DBM)3(Cz-PBM)|Tb(DBM)3(Cz-PBM)|Gd(DBM)3(Cz-PBM)|Ir(L)2(DBM-Ox)Ir(L)2(DBM-Cz)qiyue
  20. oracle用户常见job权限不足,JOB调用的权限问题

热门文章

  1. PowerDesigner反向工程 mysql
  2. Java中主线程如何捕获子线程抛出 ...
  3. 再不懂ZooKeeper,就安安心心把这篇文章看完
  4. 学习 python logging(1): 基本用法
  5. jsondataobjects
  6. Python(pycharm)在windows下路径 ( ' / ' 与' \ ' )的问题
  7. 在HermesJMS中创建ActiveMQ Session
  8. Mysql学习笔记(六)增删改查
  9. 12款最佳的 WordPress 语法高亮插件推荐
  10. cisco初级随堂笔记2