浅析linux内核内存管理之bootmem allocator
作者:李万鹏
在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy system,slab等并没有被初始化好,此时就引入了一种内存管理器bootmem allocator在系统初始化的时候进行内存管理与分配,当buddy system等初始化好后,在mem_init()中对bootmem allocator进行释放,内存管理与分配由buddy system,slab等进行接管。bootmem allocator使用一个bitmap来标记页是否被占用,分配的时候按照first fit,从bitmap中进行查找,如果这位为1,表示已经被占用,否则表示未被占用。为什么系统运行的时候不使用bootmem allocator了呢?bootmem allocator每次在bitmap中进行线性搜索,效率非常低,而且在内存的起始端留下许多小的空闲碎片,在需要非配大的内存块的时候,检查位图这一过程就显得代价很高。bootmem allocator是用于在启动阶段分配内存的,对该分配器的需求集中于简单性方面,而不是性能和通用性。
本文档从6个方面来讨论bootmem allocator:
- bootmem allocator 核心数据结构
- bootmem allocator 的初始化
- bootmem allocator 分配内存
- bootmem allocator 保留内存
- bootmem allocator 释放内存
- bootmem allocator的销毁
bootmem allocator 核心数据结构
系统内存的中每一个结点都有一个bootmem_data_t结构,它含有bootmem allocator给结点分配内存时所需的信息。
- node_boot_start是这个结点内存的起始地址
- node_low_pfn是低端内存最后一个page的页帧号
- node_bootmem_map指向内存中bitmap所在的位置
- last_offset是分配的最后一个页内的偏移,如果该页完全使用,则offset为0
- last_pos是分配最后一个页帧号
- last_success是最后一次成功分配的位置
bootmem allocator 的初始化
在setup_memory()函数中调用init_bootmem对bootmem allocator进行初始化:
- max_low_pfn是低端内存结束page的帧号,在物理内存探测的文章中会介绍
-
min_low_pfn是内核镜像后的第一个page的帧号,在setup_memory()中有这么一句:也就是获得符号_end的下一个page的页帧号,传过来就是这里的start了
可以看出这里调用了核心函数init_bootmem_core():
- 将结点添加到pgdat_list链表
- 计算位图大小,使用公式:
,加7是为了使相除后向上取整,除以8获得所需的字节数
- 使mapsize对齐到sizeof(long)的倍数即4个字节的倍数,比如我们有80个页,mapsize为10,(10 + (4 -1 )) & ~(4 - 1)==》0000 1101 & 1111 1100,0000 1100,去掉低2位,为12,即4的倍数
- 这里设置bitmap的位置为内核镜像后的第一个page
- 设置内存块的起始物理地址
- 初始化所有的区域被占用
- 返回bitmap的大小
bootmem allocator 分配内存
alloc_bootmem,alloc_bootmem_low,alloc_bootmem_pages,alloc_bootmem_low_pages都会调用__alloc_bootmem,只是一层封装,实际上是传递不同的参数调用__alloc_bootmem。下面来分析一下__alloc_bootmem的实现:
- 每个结点是链在一个链表头为pgdat_list的链表上的,对于UMA的系统,只有一个结点,结点的描述符存放在contig_page_data变量中,因此这个pgdat_list指向一个只有一个元素的链表
- align的参数是指定对齐
- goal指定了希望分配内存的起始地址,会从这个位置开始查找
这里调用了核心函数__alloc_bootmem_core,下面看其实现:
- 首先检查分配的大小不能为0
-
检查对齐方式,这里应该是4字节的倍数对齐,由于align是unsigned long型的,并且align & (align-1)代表最高bit位为1,其他bit位为0。
- edix获得总共的页帧数
- 如果align不为0,并且bootmem的内存起始地址是4字节倍数对齐,减去已经对齐的部分,剩下的部分通过offset来完成对齐
- 如果goal为真(也就是进行查找的起始地址被指定),并且goal在node_boot_start和node_low_pfn所指向的物理地址之间,则将preffered设置称相对于起始地址的偏移,其实这个起始物理地址为0,所以preffered就是希望开始进行查找的物理地址
- 如果上一次成功分配的地方大于preffered, 就可以从那个地方开始找,提高了效率
- preffered对齐到align
- preffered加上偏移
- 请求大小页对齐
- 根据对齐大小设置步进长度,小于一页为1
- 在preffered~edix之间进行查找,使用 first fit。它会查找后面第一个为0的位
- 然后以这个为0位开始查找areasize大小的返回个page
- 如果失败重新找bitmap为0的bit
- 如果找到记下起始位置start
- 找到后设置一下last_success
- 如果找到我们开始常识时候可以merge
- 能构merge需要几个条件:1)align < PAGE_SIZE 2)上一次分配最后一个page没有完全使用 3)找到的页正好是上次分配最后一个page的下一个page
- 如果可以merge,将offset按照align对齐
- 计算这个要被merge的page还剩下多少空间,即remaining_size
- 这时又分两种情况:1)请求的大小小于一个page且比前一个page剩下的大小小 2)反之
- 如果恰好请求的大小小于一个page且比前一个page剩下的大小小,则分配这个page的剩下部分给请求
- 如果请求大于等于剩下的大小则减一下,请求的一部分分在前一个page中,另一部分计算还需要多少个page
- 然后更新相应的last_pos,last_offset字段
-
如果不满足merge的条件,就从start开始分配,更新last_pos,last_offset字段
- 调用test_and_set_bit,对于没有设置bitmap的设置相应的位,像那种分配小块内存不足一个page的并且与前一个page merge的,当然test后就不用再设置了
我觉得读bootmem allocator我们应该思考几个问题:
- 怎样实现first fit
- 怎样分配小于一个页的内存的
- 找到内存后怎样merge的
bootmem allocator 保留内存
有的时候需要对部分内存进行保留,这些保留的内存在bootmem allocator存在的时候不会被释放,而且buddy system也并没有接管到这些page。
reserve_bootmem只是进行了一次封装,看reserve_bootmem_core的实现过程:
- sidx 是起始页的索引
- edix是终止页的索引
- 调用test_and_set_bit函数将bitmap中相应位置位
bootmem allocator 释放内存
这里的释放是用bootmem allocator释放内存,而不是bootmem allocator本身,其调用了free_bootmem_core函数:
分析free_bootmem_core函数实现:
- sidx 是起始页的索引
- edix是终止页的索引
- 调用test_and_clear_bit函数将bitmap中相应位清除,可以看出在bootmem allocator时代,内存的释放还是很容易的,清除相应bitmap就行。这时你发现相应page并没有清零,但是在__alloc_bootmem_core函数中,每次分配页后都调用memset进行清零操作
bootmem allocator 的销毁
在mem_init函数中会调用bootmem allocator的释放函数free_all_bootmem,将bitmap中为0的page释放到buddy system,由buddy system接管这些页。在setup_memory函数中调用reserve_bootmem保存了kernel镜像,bitmap,page 0所占的页,在free_all_bootmem_core结尾处只对bitmap占用的页进行释放。可见,kernel镜像与page 0占用的页被保留下来,并没有释放给buddy system。注意之前调用reserve_bootmem进行保留,是设置bitmap相应中的位,进行占位,跟SetPageReserved函数不一样,那个是设置page的PG_reserved标志。
释放的时候调用free_all_bootmem,它只是一个前段,里边封装了核心函数free_all_bootmem_core。
- 首先获得第一个页的描述符
- idx为页帧的数量
- 如果结点内存的起始地址是32位对齐,则设置gofast为1
- 由于v是unsigned long型的,所以是得到32位的bitmap取反,如果32位中没有被占用的,则v为0xffffffff
下面来看这个核心的for循环:
gofast为1且v为0xffffffff ==》起始地址32位对齐且没有被占用的
gofast为0且v!=0 && v!=0xffffffff ==》起始地址不是32位对齐但是没有被占用,此时32个page,一个一个检查是否被占用,然后释放
gofast为0且v为0 ==》起始地址不是32位对齐,v为0表示32个page全部被占用,跳过
将bitmap占用的空间释放给buddy system,此时bootmem allocator生命终结。
一个有趣的实验
修改start_kernel部分的代码使系统启动后检查不到我们隐藏的内存。我的系统修改前:
在init/main.c中添加:
全局的:
在start_kernel函数中,记得要在mem_init函数之前添加:
重新编译内核,重启,修改后:
怎么样,是不是少了500MB内存阿,系统都检测不到了。这块内存的起始地址EXPORT出来后,可以在驱动等地方使用。
分享到:
相关推荐
Linux常见驱动源码分析(kernel hacker修炼之道)--李万鹏 李万鹏 IBM Linux Technology Center kernel team 驱动资料清单内容如下: Linux设备模型(中)之上层容器.pdf Linux设备模型(上)之底层模型.pdf Linux...
这本书是“Linux kernel hacker修炼之道”的一部分,通过深入剖析各种常见的驱动源码,帮助读者提升在Linux系统中的驱动开发能力。 在Linux操作系统中,驱动程序是连接硬件与内核的桥梁,它们负责管理和控制硬件...
《常见驱动源码分析(kernel hacker修炼之道)》这本书或课程很可能深入探讨了如何理解和编写这些驱动,旨在帮助开发者提升对Linux内核和驱动编程的理解。在这个过程中,我们将会涉及到几个关键的知识点: 1. **Linux...
如果刚刚对linux的kernel有兴趣,想了解点什么的话,请先看看此书吧,她风趣幽默的介绍了linux的发展趣事,让你开心快乐之余慢慢领会linux的魅力,让你了解学习掌握kernel的方法。其中的很多建议经过我的实践和摸索...
内存管理是Linux内核的重要组成部分,它包括了与体系结构无关的部分和依赖于特定体系结构的部分。内存管理负责分配、回收内存资源,管理虚拟内存和物理内存的映射关系,并实现内存保护机制。 Linux内核支持多种文件...
有关hacker 的文章和资料分享给大家
ProcessHacker是一款强大的系统信息工具,它提供了进程管理、服务管理、硬件监控、内存查看等多种功能,深受系统...通过下载并使用"processhacker-2.39-bin"压缩包,你将能够亲身体验这些功能并提升你的系统管理技能。
Process Hacker是一款开源、免费且功能强大的系统信息工具,它允许用户查看并管理正在运行的进程、服务、线程以及内存等系统资源。 【描述】"Process Hacker 1.1 PH1" 提示我们这是Process Hacker的早期版本,版本...
Process Hacker是一款针对高级用户的安全分析工具,它可以帮助研究人员检测和解决软件或进程在特定操作系统环境下遇到的问题。除此之外,它还可以检测恶意进程,并告知我们这些恶意进程想要实现的功能。 Process ...
渗透测试教程资料
《Scala语言解构HackerRank算法挑战》 在编程领域,算法是不可或缺的一部分,它为计算机程序提供了高效、系统地解决问题的方法。Scala是一种多范式编程语言,融合了面向对象和函数式编程的特点,因其强大的表达能力...
rails-hackernews-reddit-producthunt-clone, 黑客 news/reddit/social 链接分享网站 用 Rails 构建 Rails 上的 Reddit-Hackernews-ProductHunt克隆演示 这是一个 readme.md的Ruby on Rails 应用程序,模仿了 Hacker...
Process Hacker是一款强大的系统进程管理工具,并且还可以显示CPU、GPU、IO、内存等相关使用信息。 官网地址:https://processhacker.sourceforge.io/ git地址:https://github.com/processhacker/processhacker
Hacker 2012 - Final Transfer Readme =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Thank you for downloading Hacker 2012! Registration ------------------------------------ If you like the game, ...
Process Hacker是一款功能丰富的系统程序,比windows自带的任务管理器功能更强大。用户只要借助该程序就可以方便,快捷地查看相关进程的速度,内存,及模块等等,除此,还可以对相关的进程进行管理工作。
Process Hacker是一款强大的系统进程管理工具,开源,并且还可以显示CPU、GPU、IO、内存等相关使用信息。 官网地址:https://processhacker.sourceforge.io/ git地址:...
Process Hacker 2是一种开源工具,用于监视系统进程、服务、网络连接等信息,并且还可以进行系统调试和修改。它可以帮助用户更好地了解系统运行情况,识别系统中的问题和优化系统性能。 使用Process Hacker 2可以...