关键词:内存池、哨兵节点、动态扩展、吃水线 内存管理相关篇为: v31.02 鸿蒙内核源码分析(内存规则) | 内存管理到底在管什么 v32.04 鸿蒙内核源码分析(物理内存) | 真实的可不一定精彩 …
本篇关键词:内存池、哨兵节点、动态扩展、吃水线
内存管理相关篇为: v31.02 鸿蒙内核源码分析(内存规则) | 内存管理到底在管什么 v32.04 鸿蒙内核源码分析(物理内存) | 真实的可不一定精彩 v33.04 鸿蒙内核源码分析(虚拟内存) | 虚拟的也是真实的 v34.03 鸿蒙内核源码分析(虚实映射) | 映射是伟大的发明 v35.02 鸿蒙内核源码分析(页表管理) | 映射关系保存在哪 v36.03 鸿蒙内核源码分析(静态分配) | 很简单的一位小朋友 v37.01 鸿蒙内核源码分析(TLFS算法) | 用图解读TLFS原理 v38.01 鸿蒙内核源码分析(内存池管理) | 如何高效切割合并内存块 v39.04 鸿蒙内核源码分析(原子操作) | 谁在守护指令执行的完整性 v40.01 鸿蒙内核源码分析(圆整对齐) | 正在制作中 … ##### 动态分配 系列篇将动态分配分成上下两篇,本篇为下篇,阅读之前需翻看上篇偏于快速理解。 鸿蒙内核源码分析(TLFS算法) 结合图表从理论视角说清楚 TLFS 算法 鸿蒙内核源码分析(内存池管理) 结合内核源码说清楚实现过程,个人认为这部分代码很精彩,简洁高效,尤其对空闲节点和已使用节点的实现令人称奇。 为了便于理解源码,站长画了以下图,图中列出主要结构体,位图,分配和释放信息,逐一说明。 请将内存池想成一条画好了网格虚线的大白纸,会有两种角色往白纸上画东西,一个是内核画管理数据,一个外部程序画业务数据,内核先画,外部程序想画需申请大小,申请成功内核会提供个地址给外部使用,例如申请 个格子,成功后内核返回一个 坐标,表示从第五行第八列开始往后的连续 个格子你可以使用。用完了释放只需要告诉内核一个坐标 而不需要大小,内核就知道回收多少格子。但内核凭什么知道要释放多少个格子呢 ? 一定有个格子给记录下来了对不对,实际中存大小的格子坐标就是 。其值是在申请的时候或更早的时候填进去的。而且不一定是 ,但一定不小于 。如果您能完全理解以上这段话,那可能已经理解了内存池的管理的方式,不用往下看了。 ##### 内存池 | OsMemPoolHead1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct OsMemPoolHead { struct OsMemPoolInfo info; UINT32 freeListBitmap[OS_MEM_BITMAP_WORDS]; struct OsMemFreeNodeHead *freeList[OS_MEM_FREE_LIST_COUNT]; SPIN_LOCK_S spinlock; #ifdef LOSCFG_MEM_MUL_POOL VOID *nextPool; #endif }; struct OsMemPoolInfo { VOID *pool; UINT32 totalSize; UINT32 attr; #ifdef LOSCFG_MEM_WATERLINE UINT32 waterLine; UINT32 curUsedSize; #endif };
解读 是整个内存池的第一个格子,里面放的是一个内存池起始虚拟地址。 表示这张纸有多少个格子。 表示池子还能不能再变大。 池子水位警戒线,跟咱三峡大坝发洪水时的警戒线 175米 类似,告知上限,水一旦漫过此线就有重大风险, 一词很形象,内核很多思想真来源于生活。1 OsMemPoolInfo.curUsedSize
所有已分配内存大小的叠加。 空闲位图,这是 算法的一二级表示,是个长度为 的整型数组1 2 3 4 5 6 #define OS_MEM_BITMAP_WORDS ((OS_MEM_FREE_LIST_COUNT >> 5 ) + 1 ) #define OS_MEM_FREE_LIST_COUNT (OS_MEM_SMALL_BUCKET_COUNT + (OS_MEM_LARGE_BUCKET_COUNT << OS_MEM_SLI) ) #define OS_MEM_LARGE_START_BUCKET 7 #define OS_MEM_SMALL_BUCKET_COUNT 31 #define OS_MEM_SLI 3
这一坨坨的宏看着有点绕,简单说就是鸿蒙对申请大小分成两种情况 第一种:小桶申请** 当小于128个字节大小的需求平均分成了1 ([0 -4 ],[4 -8 ],...,[124 -128 ])
共 个等级,而 为一个 ,共 位刚好表示这 个等级是否有空闲块。例如: 当1 freeListBitmap[0 ] = 0b...101
时,如果此时 到来,因 对应的是 , , 等级,而且 , 位图位为 ,说明在 的等级上有空闲内存块可以满足 ,需要注意的是虽然 但因为 等级上只有一种单位 所以 最后实际得到的是 ,而如果 到来时,正常需要 等级来满足,但 等级位图位为 表示没有空闲内存块,就需要向上找位图为 的 等级来申请,于是 将被分成 , 两块, 提供给 ,剩下的 挂入等级为 的空闲链表上。 第二种:大桶申请** 将占用 的剩余 个 整型变量,共可以表示 位 ,同时 ,鸿蒙将大于
个字节的申请按
次幂分成
大等级,每个等级又分成
个小等级 即 TLFS 算法
级对应的范围为
1 ([2 ^7 -2 ^8 -1 ],[2 ^8 -2 ^9 -1 ],...,[2 ^30 -2 ^31 -1 ])
而每大级被平均分成
小级, 例如最小的
将被分成每份递增
大小的八份
1 ([2 ^7 -2 ^7 +2 ^4 ],[2 ^7 +2 ^4 -2 ^7 +2 ^4 *2 ],...,[2 ^7 +2 ^4 *7 -2 ^8 -1 ])
而最大的
将被分成每份递增
大小的八份,请记住
这个数,后面还会说它。
1 ([2 ^30 -2 ^30 +2 ^27 ],[2 ^30 +2 ^4 -2 ^30 +2 ^27 *2 ],...,[2 ^30 +2 ^4 *7 -2 ^31 -1 ])
1 OsMemFreeNodeHead freeList[..]
是空闲链表数组,大小
个,即每个
等级都对应了一个链表
1 2 3 4 5 6 7 struct OsMemFreeNodeHead { struct OsMemNodeHead header; struct OsMemFreeNodeHead *prev; struct OsMemFreeNodeHead *next; };
,
,指向同级前后节点, 节点的内容在
中,这是一个关键结构体,需单独讲。
内存池节点 | OsMemNodeHead 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct OsMemNodeHead { UINT32 magic; union { struct OsMemNodeHead *prev; struct OsMemNodeHead *next; } ptr; #ifdef LOSCFG_MEM_LEAKCHECK UINTPTR linkReg[LOS_RECORD_LR_CNT]; #endif UINT32 sizeAndFlag; }; struct OsMemUsedNodeHead { struct OsMemNodeHead header; #if OS_MEM_FREE_BY_TASKID UINT32 taskID; #endif };
解读
魔法数字多次提高,内核很多模块都用到了它,比如 栈顶 ,存在的意义是防止越界,栈溢出栈顶元素就一定会被修改。同理使用了大于申请的内存会导致紧挨着的内存块魔法数字被修改,从而判定为内存溢出。 出现一个联合体,其中的
,是指向前节点的 虚拟地址 或者叫 线性地址 也可以叫 逻辑地址, 这些地址是 连续 的,注意 连续性 很重要,它是内存块合并和分割的前提,回到图中的
,
,
来看,三个内存块节点的地址是逻辑地址相连的,内存块节点由头体两部分组成,头部放的是该节点的信息,体是 malloc(..) 的返回地址,所以当释放 free(0xXXX) 某块内存时很容易知道本节点的起始地址是多少,但向前合并就得知道前节点
的地址,而后节点
的地址可通过
1 0xXXX + sizeAndFlag - 头部 = next
计算得到。既然不需要
那联合体出现在的
有什么意思呢? 这个
是指该块内存的尾节点的意思,当内存池允许扩展大小时,新旧两块内存之间就会产生一个连接处,它们的线性地址是不可能连续的,所以不存在合并的问题,
于它而言没有意义,需要记录下一个内存块的地址,这个工作就交给了联合体中的
。
一个内存池可以��多个内存块组成,每个内存块都有独立的尾节点,指向下一块内存的开始地址,最后一个内存块的尾节点也称为哨兵节点,它像个哨兵一样为整个内存池站岗,风餐露宿,固守边疆。当扩大版图之后它又跑到下一站,一个内存池只有一个哨兵,它是最可爱的人,此处应有掌声。
用于检测内存泄漏,这部分内容在 鸿蒙内核源码分析(模块监控) 已有详细说明,此处不再赘述。
,表示总大小 包括(头部和体部)和 标签 ,上面已经让大家记住
这个数,这是动态内存能分配的最大的尺寸。
中留
位给它足以,剩下的高
位就留给
。每位又分别表示以下含义
1 2 3 4 5 #define OS_MEM_NODE_USED_FLAG 0x80000000U #define OS_MEM_NODE_ALIGNED_FLAG 0x40000000U #define OS_MEM_NODE_LAST_FLAG 0x20000000U #define OS_MEM_NODE_ALIGNED_AND_USED_FLAG (OS_MEM_NODE_USED_FLAG | OS_MEM_NODE_ALIGNED_FLAG | OS_MEM_NODE_LAST_FLAG)
从联合体和
可以看出鸿蒙的设计思想,充分利用空间,准确区分概念,一张卫生纸擦完嘴还要接着擦地,节俭之家必有余粮啊,这是非常有必要的,因为内存资源太稀缺了。在实际运行过程中,分配节点常数以万计,每个能省一个
,就是一万个
,约等于
,非常可观。 这也是为什么站长始终觉得鸿蒙是个大宝藏的原因。
1 OsMemUsedNodeHead.taskID
已使用节点比空闲节点头部多了一个使用该节点任务的标记,由开关宏
控制,默认是关闭的。
代码实现 有了这么长的铺垫,再来看鸿蒙内核动态内存管理的代码简直就是易如反掌,此处拆解 节点切割 ,节点合并 ,内存池扩展 三段代码。都已添加详细的注解 ,所有注解代码请前往 百万汉字注解鸿蒙内核 | kernel_liteos_a_note 仓库查看
节点切割 | OsMemSplitNode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 STATIC INLINE VOID OsMemSplitNode (VOID *pool, struct OsMemNodeHead *allocNode, UINT32 allocSize) { struct OsMemFreeNodeHead *newFreeNode = NULL; struct OsMemNodeHead *nextNode = NULL; newFreeNode = (struct OsMemFreeNodeHead *)(VOID *)((UINT8 *)allocNode + allocSize); newFreeNode->header.ptr.prev = allocNode; newFreeNode->header.sizeAndFlag = allocNode->sizeAndFlag - allocSize; allocNode->sizeAndFlag = allocSize; nextNode = OS_MEM_NEXT_NODE(&newFreeNode->header); if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) { nextNode->ptr.prev = &newFreeNode->header; if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag)) { OsMemFreeNodeDelete(pool, (struct OsMemFreeNodeHead *)nextNode); OsMemMergeNode(nextNode); } } OsMemFreeNodeAdd(pool, newFreeNode); }
节点合并 | OsMemMergeNode 1 2 3 4 5 6 7 8 9 10 11 STATIC INLINE VOID OsMemMergeNode (struct OsMemNodeHead *node) { struct OsMemNodeHead *nextNode = NULL; node->ptr.prev->sizeAndFlag += node->sizeAndFlag; nextNode = (struct OsMemNodeHead *)((UINTPTR)node + node->sizeAndFlag); if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) { nextNode->ptr.prev = node->ptr.prev; } }
内存池扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 STATIC INLINE INT32 OsMemPoolExpandSub (VOID *pool, UINT32 size, UINT32 intSave) { UINT32 tryCount = MAX_SHRINK_PAGECACHE_TRY; struct OsMemPoolHead *poolInfo = (struct OsMemPoolHead *)pool; struct OsMemNodeHead *newNode = NULL; struct OsMemNodeHead *endNode = NULL; size = ROUNDUP(size + OS_MEM_NODE_HEAD_SIZE, PAGE_SIZE); endNode = OS_MEM_END_NODE(pool, poolInfo->info.totalSize); RETRY: newNode = (struct OsMemNodeHead *)LOS_PhysPagesAllocContiguous(size >> PAGE_SHIFT); if (newNode == NULL) return -1 ; newNode->sizeAndFlag = (size - OS_MEM_NODE_HEAD_SIZE); newNode->ptr.prev = OS_MEM_END_NODE(newNode, size); OsMemSentinelNodeSet(endNode, newNode, size); OsMemFreeNodeAdd(pool, (struct OsMemFreeNodeHead *)newNode); endNode = OS_MEM_END_NODE(newNode, size); (VOID)memset(endNode, 0 , sizeof(*endNode)); endNode->ptr.next = NULL; endNode->magic = OS_MEM_NODE_MAGIC; OsMemSentinelNodeSet(endNode, NULL, 0 ); OsMemWaterUsedRecord(poolInfo, OS_MEM_NODE_HEAD_SIZE); return 0 ; }
百文说内核 | 抓住主脉络 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。 与代码需不断
一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,
代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,鸿蒙研究站 | weharmonyos 中回复 百文 可方便阅读。
按功能模块:
基础知识 >> 双向链表 | 内核概念 | 源码结构 | 地址空间 | 计时单位 | 宏的使用 | 钩子框架 | 位图管理 | POSIX | main函数 | 进程管理 >> 调度故事 | 进程控制块 | 进程空间 | 线性区 | 红黑树 | 进程管理 | Fork进程 | 进程回收 | Shell编辑 | Shell解析 | 任务管理 >> 任务控制块 | 并发并行 | 就绪队列 | 调度机制 | 任务管理 | 用栈方式 | 软件定时器 | 控制台 | 远程登录 | 协议栈 | 内存管理 >> 内存规则 | 物理内存 | 虚拟内存 | 虚实映射 | 页表管理 | 静态分配 | TLFS算法 | 内存池管理 | 原子操作 | 圆整对齐 | 通讯机制 >> 通讯总览 | 自旋锁 | 互斥锁 | 快锁使用 | 快锁实现 | 读写锁 | 信号量 | 事件机制 | 信号生产 | 信号消费 | 消息队列 | 消息封装 | 消息映射 | 共享内存 | 文件系统 >> 文件概念 | 文件故事 | 索引节点 | VFS | 文件句柄 | 根文件系统 | 挂载机制 | 管道文件 | 文件映射 | 写时拷贝 | 硬件架构 >> 芯片模式 | ARM架构 | 指令集 | 协处理器 | 工作模式 | 寄存器 | 多核管理 | 中断概念 | 中断管理 | 内核汇编 >> 编码方式 | 汇编基础 | 汇编传参 | 可变参数 | 开机启动 | 进程切换 | 任务切换 | 中断切换 | 异常接管 | 缺页中断 | 编译运行 >> 编译过程 | 编译构建 | GN语法 | 忍者无敌 | ELF格式 | ELF解析 | 静态链接 | 重定位 | 动态链接 | 进程映像 | 应用启动 | 系统调用 | VDSO | 调测工具 >> 模块监控 | 日志跟踪 | 系统安全 | 测试用例 | 前因后果 >> 总目录 | 源码注释 | 静态站点 | 参考手册 |
百万注源码 | 处处扣细节 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。 < gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码,鸿蒙研究站 | weharmonyos 中回复 百万 可方便阅读。
据说喜欢点赞分享的,后来都成了大神。:)
本文标题: 推荐系列-v85.01 鸿蒙内核源码分析(内存池管理) - 如何高效切割合并内存块 - 百篇博客分析OpenHarmony源码
本文作者: OSChina
发布时间: 2022年05月11日 05:14
最后更新: 2023年06月29日 07:10
原始链接: https://haoxiang.eu.org/c0f9fba3/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!