评论

收藏

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

移动开发 移动开发 发布于:2022-05-25 11:34 | 阅读数:366 | 评论:0

本篇关键词:池头池体节头节块
DSC0000.png

内存管理相关篇为:
      
  • v31.02 鸿蒙内核源码分析(内存规则) | 内存管理到底在管什么  
  • v32.04 鸿蒙内核源码分析(物理内存) | 真实的可不一定精彩  
  • v33.04 鸿蒙内核源码分析(内存概念) | RAM & ROM & Flash  
  • v34.03 鸿蒙内核源码分析(虚实映射) | 映射是伟大的发明  
  • v35.02 鸿蒙内核源码分析(页表管理) | 映射关系保存在哪  
  • v36.03 鸿蒙内核源码分析(静态分配) | 很简单的一位小朋友  
  • v37.01 鸿蒙内核源码分析(TLFS算法) | 图表解读TLFS原理  
  • v38.01 鸿蒙内核源码分析(内存池管理) | 如何高效切割合并内存块  
  • v39.04 鸿蒙内核源码分析(原子操作) | 谁在守护指令执行的完整性  
  • v40.01 鸿蒙内核源码分析(圆整对齐) | 正在制作中 ...
静态分配
相比动态分配,静态内存池的分配就是个小弟弟,非常的简单,两个结构体 + 一张图 就能说明白。
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;
下图来源于官网 DSC0001.png
解读
      
  • 静态内存池在概念上由 池头池体 两部分组成,池体由众多节块组成,节块由 节头节体 两部分组成  
  • 在数据结构上表现为 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 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。  
  • 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,鸿蒙研究站 | weharmonyos 中回复 百文 可方便阅读。  
  • DSC0002.png
按功能模块:
基础知识    进程管理    任务管理    内存管理              双向链表 内核概念 源码结构 地址空间 计时单位 优雅的宏 钩子框架 位图管理 POSIX main函数    调度故事 进程控制块 进程空间 线性区 红黑树 进程管理 Fork进程 进程回收 Shell编辑 Shell解析    任务控制块 并发并行 就绪队列 调度机制 任务管理 用栈方式 软件定时器 控制台 远程登录 协议栈    内存规则 物理内存 内存概念 虚实映射 页表管理 静态分配 TLFS算法 内存池管理 原子操作 圆整对齐          通讯机制    文件系统    硬件架构    内核汇编          通讯总览 自旋锁 互斥锁 快锁使用 快锁实现 读写锁 信号量 事件机制 信号生产 信号消费 消息队列 消息封装 消息映射 共享内存    文件概念 文件故事 索引节点 VFS 文件句柄 根文件系统 挂载机制 管道文件 文件映射 写时拷贝    芯片模式 ARM架构 指令集 协处理器 工作模式 寄存器 多核管理 中断概念 中断管理    编码方式 汇编基础 汇编传参 链接脚本 开机启动 进程切换 任务切换 中断切换 异常接管 缺页中断          编译运行    调测工具                  编译过程 编译构建 GN语法 忍者无敌 ELF格式 ELF解析 静态链接 重定位 动态链接 进程映像 应用启动 系统调用 VDSO    模块监控 日志跟踪 系统安全 测试用例               百万注源码 | 处处扣细节

      
  • 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
      
  • < gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码,鸿蒙研究站 | weharmonyos 中回复 百万 可方便阅读。


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