本篇关键词:池头池体节头节块

v86.01 鸿蒙内核源码分析(静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码_链表

静态分配

相比动态分配,静态内存池的分配就是个小弟弟,非常的简单,两个结构体 + 一张图 就能说明白。

typedef struct {//静态内存池信息结构体
UINT32 uwBlkSize; /**< Block size | 块大小*/
UINT32 uwBlkNum; /**< Block number | 块数量*/
UINT32 uwBlkCnt; /**< The number of allocated blocks | 已经被分配的块数量*/
LOS_MEMBOX_NODE stFreeList; /**< Free list | 空闲链表*/
} LOS_MEMBOX_INFO;

typedef struct tagMEMBOX_NODE { //内存池中空闲节点的结构,是个单向的链表
struct tagMEMBOX_NODE *pstNext; /**< Free node's pointer to the next node in a memory pool | 指向内存池中下一个空闲节点的指针*/
} LOS_MEMBOX_NODE;

下图来源于官网

v86.01 鸿蒙内核源码分析(静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码_harmonyos_02

解读

  • 静态内存池在概念上由池头池体两部分组成,池体由众多节块组成,节块由节头节体两部分组成
  • 在数据结构上表现为​​LOS_MEMBOX_INFO​​​(池头) + [​​LOS_MEMBOX_NODE​​​(节头) +​​data​​​(节体)] + ... + [​​LOS_MEMBOX_NODE​​​(节头) +​​data​​(节体)] ,在虚拟地址上它们是连在一起的。
  • 池头记录总信息,包括 节块大小,总节块数量,已分配节块数量,空闲节块链表表头,​​stFreeList​​​将所有空闲节块链接到一起,分配内存根本不需要遍历,​​stFreeList​​​指向的下一个不为​​null​​代表还有空闲节块。
  • 节头只有一个指向下一个空闲链表的​​pstNext​​指针,简单但足以。
  • 静态分配的优缺点是很明显的,总结下:
  • 负责管理的结构体简单,会占用很少的空间,这点优于动态分配。
  • 分配速度最快,一步到位。
  • 缺点是浪费严重,僵硬不灵活,很计划经济,给每一个家庭每月口粮就这么多,高矮胖瘦都不会管。

因代码量不大,但很精彩,看这种代码是种享受,本篇详细列出静态内存代码层面的实现,关键处已添加注释。

初始化

///初始化一个静态内存池,根据入参设定其起始地址、总大小及每个内存块大小
LITE_OS_SEC_TEXT_INIT UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize)
{
LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;//在内存起始处放置控制头
LOS_MEMBOX_NODE *node = NULL;
//...
UINT32 index;
UINT32 intSave;
MEMBOX_LOCK(intSave);
boxInfo->uwBlkSize = LOS_MEMBOX_ALIGNED(blkSize + OS_MEMBOX_NODE_HEAD_SIZE); //节块总大小(节头+节体)
boxInfo->uwBlkNum = (poolSize - sizeof(LOS_MEMBOX_INFO)) / boxInfo->uwBlkSize;//总节块数量
boxInfo->uwBlkCnt = 0; //已分配的数量
if (boxInfo->uwBlkNum == 0) {//只有0块的情况
MEMBOX_UNLOCK(intSave);
return LOS_NOK;
}
node = (LOS_MEMBOX_NODE *)(boxInfo + 1);//去除池头,找到第一个节块位置
boxInfo->stFreeList.pstNext = node;//池头空闲链表指向第一个节块
for (index = 0; index < boxInfo->uwBlkNum - 1; ++index) {//切割节块,挂入空闲链表
node->pstNext = OS_MEMBOX_NEXT(node, boxInfo->uwBlkSize);//按块大小切割好,统一由pstNext指向
node = node->pstNext;//node存储了下一个节点的地址信息
}
node->pstNext = NULL;//最后一个为null
MEMBOX_UNLOCK(intSave);
return LOS_OK;
}

申请

///从指定的静态内存池中申请一块静态内存块,整个内核源码只有 OsSwtmrScan中用到了静态内存.
LITE_OS_SEC_TEXT VOID *LOS_MemboxAlloc(VOID *pool)
{
LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;
LOS_MEMBOX_NODE *node = NULL;
LOS_MEMBOX_NODE *nodeTmp = NULL;
UINT32 intSave;
if (pool == NULL) {
return NULL;
}
MEMBOX_LOCK(intSave);
node = &(boxInfo->stFreeList);//拿到空闲单链表
if (node->pstNext != NULL) {//不需要遍历链表,因为这是空闲链表
nodeTmp = node->pstNext;//先记录要使用的节点
node->pstNext = nodeTmp->pstNext;//不再空闲了,把节点摘出去了.
OS_MEMBOX_SET_MAGIC(nodeTmp);//为已使用的节块设置魔法数字
boxInfo->uwBlkCnt++;//已使用块数增加
}
MEMBOX_UNLOCK(intSave);
return (nodeTmp == NULL) ? NULL : OS_MEMBOX_USER_ADDR(nodeTmp);//返回可用的虚拟地址
}

释放

/// 释放指定的一块静态内存块
LITE_OS_SEC_TEXT UINT32 LOS_MemboxFree(VOID *pool, VOID *box)
{
LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;
UINT32 ret = LOS_NOK;
UINT32 intSave;
if ((pool == NULL) || (box == NULL)) {
return LOS_NOK;
}
MEMBOX_LOCK(intSave);
do {
LOS_MEMBOX_NODE *node = OS_MEMBOX_NODE_ADDR(box);//通过节体获取节块首地址
if (OsCheckBoxMem(boxInfo, node) != LOS_OK) {
break;
}
node->pstNext = boxInfo->stFreeList.pstNext;//节块指向空闲链表表头
boxInfo->stFreeList.pstNext = node;//空闲链表表头反指向它,意味节块排到第一,下次申请将首个分配它
boxInfo->uwBlkCnt--;//已经使用的内存块减一
ret = LOS_OK;
} while (0);//将被编译时优化
MEMBOX_UNLOCK(intSave);
return ret;
}

使用

鸿蒙内核目前只有软时钟处理使用了静态内存池,直接上代码

///软时钟初始化 ,注意函数在多CPU情况下会执行多次
STATIC UINT32 SwtmrBaseInit(VOID)
{
UINT32 ret;
UINT32 size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT;
SWTMR_CTRL_S *swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size); /* system resident resource */
if (swtmr == NULL) {
return LOS_ERRNO_SWTMR_NO_MEMORY;
}
(VOID)memset_s(swtmr, size, 0, size);//清0
g_swtmrCBArray = swtmr;//软时钟
LOS_ListInit(&g_swtmrFreeList);//初始化空闲链表
for (UINT16 index = 0; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) {
swtmr->usTimerID = index;//按顺序赋值
LOS_ListTailInsert(&g_swtmrFreeList, &swtmr->stSortList.sortLinkNode);//通过sortLinkNode将节点挂到空闲链表
}
//想要用静态内存池管理,就必须要使用LOS_MEMBOX_SIZE来计算申请的内存大小,因为需要点前缀内存承载头部信息.
size = LOS_MEMBOX_SIZE(sizeof(SwtmrHandlerItem), OS_SWTMR_HANDLE_QUEUE_SIZE);//规划一片内存区域作为软时钟处理函数的静态内存池。
g_swtmrHandlerPool = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, size); /* system resident resource */
if (g_swtmrHandlerPool == NULL) {
return LOS_ERRNO_SWTMR_NO_MEMORY;
}
ret = LOS_MemboxInit(g_swtmrHandlerPool, size, sizeof(SwtmrHandlerItem));
if (ret != LOS_OK) {
return LOS_ERRNO_SWTMR_HANDLER_POOL_NO_MEM;
}
for (UINT16 index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) {
SwtmrRunQue *srq = &g_swtmrRunQue[index];
/* The linked list of all cores must be initialized at core 0 startup for load balancing */
OsSortLinkInit(&srq->swtmrSortLink);
LOS_ListInit(&srq->swtmrHandlerQueue);
srq->swtmrTask = NULL;
}
SwtmrDebugDataInit();
return LOS_OK;
}
typedef VOID (*SWTMR_PROC_FUNC)(UINTPTR arg);    //函数指针, 赋值给 SWTMR_CTRL_S->pfnHandler,回调处理
typedef struct {//处理软件定时器超时的回调函数的结构体
SWTMR_PROC_FUNC handler; /**< Callback function that handles software timer timeout */ //处理软件定时器超时的回调函数
UINTPTR arg; /**< Parameter passed in when the callback function
that handles software timer timeout is called */ //调用处理软件计时器超时的回调函数时传入的参数
LOS_DL_LIST node;
#ifdef LOSCFG_SWTMR_DEBUG
UINT32 swtmrID;
#endif
} SwtmrHandlerItem;

关于软定时器可以查看系列相关篇,请想想为何软件定时器会使用静态内存。

百文说内核 | 抓住主脉络

  • 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。
  • 与代码需不断​​debug​​​一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,​​v**.xx​​ 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。

  • v86.01 鸿蒙内核源码分析(静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码_gitee_03

按功能模块:

基础知识

进程管理

任务管理

内存管理

​双向链表​​​ ​​内核概念​​​ ​​源码结构​​​ ​​地址空间​​​ ​​计时单位​​​ ​​优雅的宏​​​ ​​钩子框架​​​ ​​位图管理​​​ ​​POSIX​​​ ​​main函数​

​调度故事​​​ ​​进程控制块​​​ ​​进程空间​​​ ​​线性区​​​ ​​红黑树​​​ ​​进程管理​​​ ​​Fork进程​​​ ​​进程回收​​​ ​​Shell编辑​​​ ​​Shell解析​

​任务控制块​​​ ​​并发并行​​​ ​​就绪队列​​​ ​​调度机制​​​ ​​任务管理​​​ ​​用栈方式​​​ ​​软件定时器​​​ ​​控制台​​​ ​​远程登录​​​ ​​协议栈​

​内存规则​​​ ​​物理内存​​​ ​​内存概念​​​ ​​虚实映射​​​ ​​页表管理​​​ ​​静态分配​​​ ​​TLFS算法​​​ ​​内存池管理​​​ ​​原子操作​​​ ​​圆整对齐​

通讯机制

文件系统

硬件架构

内核汇编

​通讯总览​​​ ​​自旋锁​​​ ​​互斥锁​​​ ​​快锁使用​​​ ​​快锁实现​​​ ​​读写锁​​​ ​​信号量​​​ ​​事件机制​​​ ​​信号生产​​​ ​​信号消费​​​ ​​消息队列​​​ ​​消息封装​​​ ​​消息映射​​​ ​​共享内存​

​文件概念​​​ ​​文件故事​​​ ​​索引节点​​​ ​​VFS​​​ ​​文件句柄​​​ ​​根文件系统​​​ ​​挂载机制​​​ ​​管道文件​​​ ​​文件映射​​​ ​​写时拷贝​

​芯片模式​​​ ​​ARM架构​​​ ​​指令集​​​ ​​协处理器​​​ ​​工作模式​​​ ​​寄存器​​​ ​​多核管理​​​ ​​中断概念​​​ ​​中断管理​

​编码方式​​​ ​​汇编基础​​​ ​​汇编传参​​​ ​​链接脚本​​​ ​​开机启动​​​ ​​进程切换​​​ ​​任务切换​​​ ​​中断切换​​​ ​​异常接管​​​ ​​缺页中断​

编译运行

调测工具

​编译过程​​​ ​​编译构建​​​ ​​GN语法​​​ ​​忍者无敌​​​ ​​ELF格式​​​ ​​ELF解析​​​ ​​静态链接​​​ ​​重定位​​​ ​​动态链接​​​ ​​进程映像​​​ ​​应用启动​​​ ​​系统调用​​​ ​​VDSO​

​模块监控​​​ ​​日志跟踪​​​ ​​系统安全​​​ ​​测试用例​

据说喜欢点赞分享的,后来都成了大神。:)