Linux brk(),mmap()系统调用源码分析 brk()的内存申请流程

荣涛 2021年4月30日

  • 内核版本:linux-5.10.13
  • 注释版代码:https://github.com/Rtoax/linux-5.10.13

1. 基础部分

在之前文章中已经介绍了基础部分 《Linux内存管理 brk(),mmap()系统调用源码分析1:基础部分》,本文介绍brk的释放部分。

2. brk内存释放

在之前文章中已经介绍了brk内存释放过程《Linux内存管理 brk(),mmap()系统调用源码分析2:brk()的内存释放流程》

3. brk内存申请

本文介绍申请流程。如果新的 brk 位置高于 旧的 brk 位置,首先会查找旧brk所在的vma的下一个vma结构:

 next = find_vma(mm, oldbrk);if (next && newbrk + PAGE_SIZE > vm_start_gap(next))goto out;

如果下一个vma结构存在,并且新的brk+pagesize落在vma上,那么说明现在的brk满足要求,直接返回就行了,如果不是,就迎来了do_brk_flags

4. do_brk_flags

函数原型为:

static int do_brk_flags(unsigned long addr, unsigned long len, unsigned long flags, struct list_head *uf)

入参分别为:起始地址,长度,标志。

函数是这么调用的do_brk_flags(oldbrk, newbrk-oldbrk, 0, &uf),该函数基本上是释放流程的逆向操作,这里只就几个核心的函数进行讲解,第一个get_unmapped_area

4.1. get_unmapped_area

brk系统调用肯定不是文件,所以file=NULL,

get_unmapped_area(NULL, addr, len, 0, MAP_FIXED);

MAP_FIXED准确解释地址,如果addr和len指定的内存区域与任何现有映射的页面重叠,则现有映射的重叠部分将被丢弃.

首先调用arch_mmap_check,在x86下为0。接下来获取未映射区域,这区分了mmap类型:

 get_area = current->mm->get_unmapped_area;if (file) { /* 如果是文件映射 */if (file->f_op->get_unmapped_area)get_area = file->f_op->get_unmapped_area;} else if (flags & MAP_SHARED) {    /* 如果是共享的映射 *//** mmap_region() will call shmem_zero_setup() to create a file,* so use shmem's get_unmapped_area in case it can be huge.* do_mmap() will clear pgoff, so match alignment.*/pgoff = 0;get_area = shmem_get_unmapped_area; /* 共享 */}

首先从mm结构中获取了get_unmapped_area函数指针,这个指针牛的一批,在arch\x86\kernel\sys_x86_64.c里,通过函数指针调用addr = get_area(file, addr, len, pgoff, flags);

4.2. arch_get_unmapped_area

该结构是在arch_pick_mmap_layout函数中被赋予get_unmapped_area指针的,如下:

void arch_pick_mmap_layout(struct mm_struct *mm, struct rlimit *rlim_stack)
{if (mmap_is_legacy())mm->get_unmapped_area = arch_get_unmapped_area;elsemm->get_unmapped_area = arch_get_unmapped_area_topdown;...

https://www.kernel.org/doc/gorman/html/understand/understand021.html#func:%20arch_get_unmapped_area

函数不长,但是操作很骚。先看参数:

do_brk_flags(oldbrk, newbrk-oldbrk, 0, &uf)get_unmapped_area(NULL, addr, len, 0, MAP_FIXED);get_area(file, addr, len, pgoff, flags); -> arch_get_unmapped_areaarch_get_unmapped_areafind_start_endget_mmap_basevm_unmapped_areaunmapped_area
  • file=NULL;
  • addr=oldbrk;
  • len=newbrk-oldbrk;
  • pgoff=0;
  • flags=MAP_FIXED;(准确解释地址,如果addr和len指定的内存区域与任何现有映射的页面重叠,则现有映射的重叠部分将被丢弃.)

使用find_start_end获取begin和end:

static void find_start_end(unsigned long addr, unsigned long flags,unsigned long *begin, unsigned long *end)
{if (!in_32bit_syscall() && (flags & MAP_32BIT)) {   /* 32 位 *//* This is usually used needed to map code in smallmodel, so it needs to be in the first 31bit. Limitit to that.  This means we need to move theunmapped base down for this case. This can giveconflicts with the heap, but we assume that glibcmalloc knows how to fall back to mmap. Give it 1GBof playground for now. -AK */*begin = 0x40000000;*end = 0x80000000;if (current->flags & PF_RANDOMIZE) {*begin = randomize_page(*begin, 0x02000000);}return;}*begin   = get_mmap_base(1); /*  */if (in_32bit_syscall())*end = task_size_32bit();else*end = task_size_64bit(addr > DEFAULT_MAP_WINDOW);
}

首先判断如果不是32bit系统调用!in_32bit_syscall()并且设置了标记位(flags & MAP_32BIT),之类不成立,因为flags值为MAP_FIXED,那么接下来会执行*begin = get_mmap_base(1);。这个函数get_mmap_base直接返回is_legacy ? mm->mmap_legacy_base : mm->mmap_base;也就是mm->mmap_legacy_base,这个值等于几?他是在arch_pick_mmap_base设置的,在文章mmap随机化中有解释,也就是是否将mmap随机化,这是在一个漏洞的解决方法,此处不所解释,感兴趣可以参考一篇论文《Meltdown(熔断漏洞)- Reading Kernel Memory from User Space/KASLR | 原文+中文翻译》。
接着,调用task_size_64bit获取end地址。

然后判断长度:

 if (len > end)return -ENOMEM;

如果已存在,直接返回:

 if (addr) {addr = PAGE_ALIGN(addr);    /* 对齐 */vma = find_vma(mm, addr);   /* 查找对应 vma */if (end - len >= addr &&(!vma || addr + len <= vm_start_gap(vma)))return addr;}

接着是对数据结构vm_unmapped_area_info的填充

struct vm_unmapped_area_info {  /*  */
#define VM_UNMAPPED_AREA_TOPDOWN 1unsigned long flags;unsigned long length;unsigned long low_limit;unsigned long high_limit;unsigned long align_mask;unsigned long align_offset;
};

它是这么填充的:

 info.flags = 0;info.length = len;info.low_limit = begin;info.high_limit = end;info.align_mask = 0;info.align_offset = pgoff << PAGE_SHIFT;if (filp) {info.align_mask = get_align_mask();info.align_offset += get_align_bits();}

接着调用vm_unmapped_area,其调用unmapped_area(flags=0)

4.3. unmapped_area

这里的入参为:

  • file=NULL;
  • addr=oldbrk;
  • len=newbrk-oldbrk;
  • pgoff=0;
  • flags=MAP_FIXED;

他的操作在函数注释中给出:

/** We implement the search by looking for an rbtree node that* immediately follows a suitable gap. That is,* - gap_start = vma->vm_prev->vm_end <= info->high_limit - length;* - gap_end   = vma->vm_start        >= info->low_limit  + length;* - gap_end - gap_start >= length*/

接着get_unmapped_area返回,并进行合法性判断:

 mapped_addr = get_unmapped_area(NULL, addr, len, 0, MAP_FIXED);if (IS_ERR_VALUE(mapped_addr))  /* unlikely */return mapped_addr;

4.4. munmap_vma_range

该函数的注释为

munmap VMAs that overlap a range.
/* Clear old maps, set up prev, rb_link, rb_parent, and uf */

在这,我发现一个问题,find_vma_links函数永远不会返回真值,那么此处的while的作用是什么呢?

static inline int
munmap_vma_range(struct mm_struct *mm, unsigned long start, unsigned long len,struct vm_area_struct **pprev, struct rb_node ***link,struct rb_node **parent, struct list_head *uf)
{/*  */while (find_vma_links(mm, start, start + len, pprev, link, parent))if (do_munmap(mm, start, len, uf))return -ENOMEM;return 0;
}

这里具体关于mm的操作可以参考函数copy_mmdup_mmvm_area_dup

4.5. may_expand_vm

/** Return true if the calling process may expand its vm space by the passed* number of pages*/
bool may_expand_vm(struct mm_struct *mm, vm_flags_t flags, unsigned long npages)
{/* 检查映射的页数有没有超限 */if (mm->total_vm + npages > rlimit(RLIMIT_AS) >> PAGE_SHIFT)return false;/* 数据 mapping 1.在 brk系统调用传入的是0,此代码不执行*/if (is_data_mapping(flags) &&mm->data_vm + npages > rlimit(RLIMIT_DATA) >> PAGE_SHIFT) {/* Workaround for Valgrind */if (rlimit(RLIMIT_DATA) == 0 &&mm->data_vm + npages <= rlimit_max(RLIMIT_DATA) >> PAGE_SHIFT)return true;pr_warn_once("%s (%d): VmData %lu exceed data ulimit %lu. Update limits%s.\n",current->comm, current->pid,(mm->data_vm + npages) << PAGE_SHIFT,rlimit(RLIMIT_DATA),ignore_rlimit_data ? "" : " or use boot option ignore_rlimit_data");if (!ignore_rlimit_data)return false;}return true;
}

接下来检查系统配置,是否映射数量超限:

    /* 检查sysctl */if (mm->map_count > sysctl_max_map_count)return -ENOMEM;

4.6. vma_merge

brk 此处不对其进行讲解,将在mprotect系统调用中讲解。

4.7. vma_link

接下来,分配新的vma结构,并且填充响应的数据,并将vma添加至mm结构的链表和红黑树中:

 /** create a vma struct for an anonymous mapping*/vma = vm_area_alloc(mm);    /* 分配这个结构 */if (!vma) {vm_unacct_memory(len >> PAGE_SHIFT);return -ENOMEM;}vma_set_anonymous(vma);     /* 匿名vma */vma->vm_start = addr;       /* start */vma->vm_end = addr + len;   /* end */vma->vm_pgoff = pgoff;      /* 页内偏移 */vma->vm_flags = flags;      /* 标志 */vma->vm_page_prot = vm_get_page_prot(flags);    /* VMA 的权限 */vma_link(mm, vma, prev, rb_link, rb_parent);    /* 插入 */

其中vm_link函数:

static void vma_link(struct mm_struct *mm, struct vm_area_struct *vma,struct vm_area_struct *prev, struct rb_node **rb_link,struct rb_node *rb_parent)
{struct address_space *mapping = NULL;if (vma->vm_file) { /* 文件映射 */mapping = vma->vm_file->f_mapping;i_mmap_lock_write(mapping);}__vma_link(mm, vma, prev, rb_link, rb_parent);  /* 添加至链表和红黑树 */__vma_link_file(vma);   /* 文件映射的话,更新缓存 */if (mapping)i_mmap_unlock_write(mapping);mm->map_count++;    /* 映射计数++ */validate_mm(mm);    /*  */
}

这里的validate_mm在本文中不做过多讲解,将在后续文章中详细解说。

4.8. perf_event_mmap

brk 此处不对其进行讲解,将在手续文章中进行讲解。

然后,对mm结构字段进行更新:

 mm->total_vm += len >> PAGE_SHIFT;  /* 共映射的页数计数 */mm->data_vm += len >> PAGE_SHIFT;   /* 数据映射计数 */if (flags & VM_LOCKED)mm->locked_vm += (len >> PAGE_SHIFT);   /* 锁定的页面计数 */vma->vm_flags |= VM_SOFTDIRTY;return 0;

至此,do_brk_flags就返回了。接着,更新brk位置:

mm->brk = brk;

4.9. mm_populate

brk 此处不对其进行讲解,将在手续文章中进行讲解。

至此brk系统调用就返回至用户态程序。

5. 相关链接

  • https://www.cs.unc.edu/~porter/courses/cse506/f12/slides/address-spaces.pdf
  • https://stackoverflow.com/questions/14943990/overlapping-pages-with-mmap-map-fixed
  • 《Linux内存管理 brk(),mmap()系统调用源码分析1:基础部分》
  • 《Linux内存管理 brk(),mmap()系统调用源码分析2:brk()的内存释放流程》
  • 内核实现mmap的关键点-get_unmapped_area
  • mmap随机化
  • Meltdown(熔断漏洞)- Reading Kernel Memory from User Space/KASLR | 原文+中文翻译

Linux brk(),mmap()系统调用源码分析3:brk()的内存申请流程相关推荐

  1. Linux内存管理 brk(),mmap()系统调用源码分析2:brk()的内存释放流程

    Linux brk(),mmap()系统调用源码分析 brk()的内存释放流程 荣涛 2021年4月30日 内核版本:linux-5.10.13 注释版代码:https://github.com/Rt ...

  2. Linux内存管理 brk(),mmap()系统调用源码分析1:基础部分

    Linux内存管理 brk(),mmap(),munmap()系统调用源码分析 基础部分 荣涛 2021年4月30日 内核版本:linux-5.10.13 注释版代码:https://github.c ...

  3. 【Linux 内核 内存管理】mmap 系统调用源码分析 ④ ( do_mmap 函数执行流程 | do_mmap 函数源码 )

    文章目录 一.do_mmap 函数执行流程 二.do_mmap 函数源码 调用 mmap 系统调用 , 先检查 " 偏移 " 是否是 " 内存页大小 " 的 & ...

  4. 【SemiDrive源码分析】【X9芯片启动流程】21 - MailBox 核间通信机制介绍(代码分析篇)之 Mailbox for Linux 篇

    [SemiDrive源码分析][X9芯片启动流程]21 - MailBox 核间通信机制介绍(代码分析篇)之 Mailbox for Linux 篇 一.Mailbox for Linux 驱动框架分 ...

  5. linux显示启动logo源码分析以及修改显示logo

    1.linux显示启动logo整个流程分析 (1)logo图片在内核源码中是以ppm格式的文件保存,在编译内核时会把ppm格式的文件自动转换成.c文件,在c文件中会构造一个struct linux_l ...

  6. 【SemiDrive源码分析】【X9芯片启动流程】30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一)

    [SemiDrive源码分析][X9芯片启动流程]30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一) 一.Android Kernel 启动流程分析 ...

  7. 【SemiDrive源码分析】【X9芯片启动流程】19 - MailBox 核间通信机制介绍(理论篇)

    [SemiDrive源码分析][X9芯片启动流程]19 - MailBox 核间通信机制介绍(理论篇) 一.核间通信 二.核间通信软件架构 三.Mailbox 设备驱动 3.1 Mailbox for ...

  8. 【SemiDrive源码分析】【X9芯片启动流程】23 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC Kernel 篇

    [SemiDrive源码分析][X9芯片启动流程]23 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC Kernel 篇 一.RPMSG 接口 1.1 Linux Kern ...

  9. 【SemiDrive源码分析】【X9芯片启动流程】25 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC RTOS QNX篇

    [SemiDrive源码分析][X9芯片启动流程]25 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC RTOS & QNX篇 一.RPMSG 接口 1.1 Lin ...

最新文章

  1. 安卓学习-界面-ui-Spinner
  2. 0909操作系统初感
  3. 自适应波束形成matlab,自适应波束形成matlab
  4. 大数据到底怎么学:数据科学概论与大数据学习误区
  5. 使用java的方式配置Spring---JavaConfig
  6. 深入理解计算机系统简述
  7. 【排序】快排(霍尔快排)
  8. 为什么二进制按权展开就是十进制?彻底搞懂原理
  9. android联想云服务,联想云服务app下载-联想云服务手机客户端(原乐同步)下载 v5.5.10.99 官方安卓版-IT猫扑网...
  10. 小白前端之路:手写一个简单的vue-router这几年,好像过的好快,怀念我的大学生活。 - 连某人 大三实习生,之前写过简单MVVM框架、简单的vuex、但是看了vue-router的源码(看了
  11. 理解区块链背后的Merkle Tree
  12. 北语期末考试计算机组成原理,基础知识一认识计算机网络_1课件
  13. MIUI小米 卸载金山安全服务
  14. 一文带你全面解析postman工具的使用(高级篇)
  15. 读书 | 设计模式之禅 - 策略模式
  16. 引领趋势拥抱未来!修嗒嗒云脸惊艳亮相上海建博会,大放异彩!
  17. Facebook AI的DETR:一种基于Transformer的目标检测方法
  18. ccf a类会议_信息安全相关学术会议列表
  19. 全球及中国空气电磁阀行业营运模式及销售渠道分析报告2021~2026年
  20. 计算机技术属于全日制工程硕士吗,计算机技术领域全日制工程硕士专业学位研究生培养方案试行为.PDF...

热门文章

  1. STM32F429I-DISCO 和GPS的亲热接触
  2. 每日一个小技巧:音频提取软件免费版有哪些?这3款收好了
  3. markdown的甘特图耶
  4. 今天不抠图,Python实现一键换底片!想换什么换什么(附源码)
  5. qt库文件添加到环境变量linux,QT获得所有系统环境变量(包括Linux和MAC的信息)...
  6. 将iPhone短信迁移到安卓系统
  7. 中级会计师考过还考英语计算机不,中级会计师考试是笔试还是机考 机考怎么操作...
  8. Kinect1代+KinectSDK1.8+OpenNI2.2+NITE2.0+Opencv2.4.10环境配置(2)
  9. Pytorch中 permute / transpose 和 view / reshape, flatten函数
  10. iPhone11和华为P40Pro哪个好