8

LiteOS内核源码分析:静态内存Static Memory

 3 years ago
source link: https://my.oschina.net/u/4526289/blog/5039594
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
LiteOS内核源码分析:静态内存Static Memory

摘要:本文带领大家一起剖析了LiteOS静态内存模块的源代码,包含静态内存的结构体、静态内存池初始化、静态内存申请、释放、清除内容等。

本文分享自华为云社区《LiteOS内核源码分析系列十二 静态内存Static Memory》,原文作者:zhushy 。

内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。

在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。

Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。

  • 动态内存:在动态内存池中分配用户指定大小的内存块。
    • 优点:按需分配。
    • 缺点:内存池中可能出现碎片。
  • 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。
    • 优点:分配和释放效率高,静态内存池中无碎片。
    • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

本文主要分析LiteOS静态内存(Memory Box),后续系列会继续分析动态内存。静态内存实质上是一个静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。静态内存池由一个控制块和若干相同大小的内存块构成。控制块位于内存池头部,用于内存块管理。内存块的申请和释放以块大小为粒度。

本文通过分析LiteOS静态内存模块的源码,帮助读者掌握静态内存的使用。LiteOS静态内存模块的源代码, 均可以在LiteOS开源站点https://gitee.com/LiteOS/LiteOS 获取。 静态内存源代码、开发文档,示例程序代码如下:

  • LiteOS内核静态内存源代码

包括静态内存的私有头文件kernel\base\include\los_membox_pri.h、头文件kernel\include\los_membox.h、C源代码文件kernel\base\mem\membox\los_membox.c。

  • 开发指南文档–内存
  • 在线文档https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E5%86%85%E5%AD%98。

接下来,我们看下静态内存的结构体,静态内存初始化,静态内存常用操作的源代码。

1、静态内存结构体定义和常用宏定义

1.1 静态内存结构体定义

在文件kernel\include\los_membox.h中,定义静态内存池信息结构体为LOS_MEMBOX_INFO,静态内存节点LOS_MEMBOX_NODE结构体,源代码如下,结构体成员的解释见注释部分。

typedef struct tagMEMBOX_NODE {
    struct tagMEMBOX_NODE *pstNext; /**< 静态内存池中空闲节点指针,指向下一个空闲节点 */
} LOS_MEMBOX_NODE;

typedef struct {
    UINT32 uwBlkSize;           /**< 静态内存池的内存块大小 */
    UINT32 uwBlkNum;            /**< 静态内存池的内存块总数量 */
    UINT32 uwBlkCnt;            /**< 静态内存池的已分配的内存块总数量 */
#ifdef LOSCFG_KERNEL_MEMBOX_STATIC
    LOS_MEMBOX_NODE stFreeList; /**< 静态内存池的空闲内存块单向链表 */
#endif
} LOS_MEMBOX_INFO;

对静态内存使用如下示意图进行说明,对一块静态内存区域,头部是LOS_MEMBOX_INFO信息,接着是各个内存块,每块内存块大小是uwBlkSize,包含内存块节点LOS_MEMBOX_NODE和内存块数据区。空闲内存块节点指向下一块空闲内存块节点。

v2-35965d00832f6e83e48e9ce25345af88_720w.jpg

1.2 静态内存常用宏定义

静态内存头文件中还提供了一些重要的宏定义。⑴处的LOS_MEMBOX_ALLIGNED(memAddr)用于对齐内存地址,⑵处OS_MEMBOX_NEXT(addr, blkSize)根据当前节点内存地址addr和内存块大小blkSize获取下一个内存块的内存地址。⑶处OS_MEMBOX_NODE_HEAD_SIZE表示内存块中节点头大小,每个内存块包含内存节点LOS_MEMBOX_NODE和存放业务的数据区。⑷处表示静态内存的总大小,包含内存池信息结构体占用的大小,和各个内存块占用的大小。

⑴  #define LOS_MEMBOX_ALLIGNED(memAddr) (((UINTPTR)(memAddr) + sizeof(UINTPTR) - 1) & (~(sizeof(UINTPTR) - 1)))

⑵  #define OS_MEMBOX_NEXT(addr, blkSize) (LOS_MEMBOX_NODE *)(VOID *)((UINT8 *)(addr) + (blkSize))

⑶  #define OS_MEMBOX_NODE_HEAD_SIZE sizeof(LOS_MEMBOX_NODE)

⑷  #define LOS_MEMBOX_SIZE(blkSize, blkNum) \
        (sizeof(LOS_MEMBOX_INFO) + (LOS_MEMBOX_ALLIGNED((blkSize) + OS_MEMBOX_NODE_HEAD_SIZE) * (blkNum)))

在文件kernel\base\mem\membox\los_membox.c中也定义了一些宏。OS_MEMBOX_MAGIC定义魔术字,⑴处宏在内存块节点从静态内存池中分配出来后,节点指针.pstNext不再指向下一个空闲内存块节点,而是设置为魔术字。⑵处的宏用于校验魔术字。⑶处根据内存块的节点地址获取内存块的数据区地址,⑷处根据内存块的数据区地址获取内存块的节点地址。

    #define OS_MEMBOX_MAGIC 0xa55a5aa5

⑴  #define OS_MEMBOX_SET_MAGIC(addr) \
        ((LOS_MEMBOX_NODE *)(addr))->pstNext = (LOS_MEMBOX_NODE *)OS_MEMBOX_MAGIC

⑵  #define OS_MEMBOX_CHECK_MAGIC(addr) \
        ((((LOS_MEMBOX_NODE *)(addr))->pstNext == (LOS_MEMBOX_NODE *)OS_MEMBOX_MAGIC) ? LOS_OK : LOS_NOK)

⑶  #define OS_MEMBOX_USER_ADDR(addr) \
        ((VOID *)((UINT8 *)(addr) + OS_MEMBOX_NODE_HEAD_SIZE))

⑷  #define OS_MEMBOX_NODE_ADDR(addr) \
        ((LOS_MEMBOX_NODE *)(VOID *)((UINT8 *)(addr) - OS_MEMBOX_NODE_HEAD_SIZE))

2、静态内存常用操作

当用户需要使用固定长度的内存时,可以通过静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。

2.1 初始化静态内存池

我们分析下初始化静态内存池函数UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize)的代码。我们先看看函数参数,VOID *pool是静态内存池的起始地址,UINT32 poolSize是初始化的静态内存池的总大小,poolSize需要小于等于*pool开始的内存区域的大小,否则会影响后面的内存区域。还需要大于静态内存的头部大小sizeof(LOS_MEMBOX_INFO)。长度UINT32 blkSize是静态内存池中的每个内存块的块大小。

我们看下代码,⑴处对传入参数进行校验。⑵处设置静态内存池中每个内存块的实际大小,已内存对齐,也算上内存块中节点信息。⑶处计算内存池中内存块的总数量,然后设置已用内存块数量.uwBlkCnt为0。⑷处如果可用的内存块为0,返回初始化失败。⑸处获取内存池中的第一个空闲内存块节点。⑹处把空闲内存块挂载在静态内存池信息结构体空闲内存块链表stFreeList.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;

⑴  if (pool == NULL) {
        return LOS_NOK;
    }

    if (blkSize == 0) {
        return LOS_NOK;
    }

    if (poolSize < sizeof(LOS_MEMBOX_INFO)) {
        return LOS_NOK;
    }

    MEMBOX_LOCK(intSave);
⑵  boxInfo->uwBlkSize = LOS_MEMBOX_ALLIGNED(blkSize + OS_MEMBOX_NODE_HEAD_SIZE);
⑶  boxInfo->uwBlkNum = (poolSize - sizeof(LOS_MEMBOX_INFO)) / boxInfo->uwBlkSize;
    boxInfo->uwBlkCnt = 0;
⑷  if ((boxInfo->uwBlkNum == 0) || (boxInfo->uwBlkSize < blkSize)) {
        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);
        node = node->pstNext;
    }

    node->pstNext = NULL;

    MEMBOX_UNLOCK(intSave);

    return LOS_OK;
}

2.2 清除静态内存块内容

我们可以使用函数VOID LOS_MemboxClr(VOID *pool, VOID *box)来清除静态内存块内容,需要2个参数,VOID *pool是初始化过的静态内存池地址。VOID *box是需要清除内容的静态内存块的数据区的起始地址,注意这个不是内存块的节点地址,每个内存块的节点区不能清除。下面分析下源码。

⑴处对参数进行校验,⑵处调用memset_s()函数把内存块的数据区写入0。写入的开始地址是内存块的数据区的起始地址VOID *box,写入长度是数据区的长度boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE。

LITE_OS_SEC_TEXT_MINOR VOID LOS_MemboxClr(VOID *pool, VOID *box)
{
    LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;
    UINT32 intSave;

⑴  if ((pool == NULL) || (box == NULL)) {
        return;
    }

    MEMBOX_LOCK(intSave);
⑵  (VOID)memset_s(box, (boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE), 0,
        (boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE));
    MEMBOX_UNLOCK(intSave);
}

2.3 申请、释放静态内存

初始化静态内存池后,我们可以使用函数VOID *LOS_MemboxAlloc(VOID *pool)来申请静态内存,下面分析下源码。

⑴处获取静态内存池空闲内存块链表头结点,如果链表不为空,执行⑵,把下一个可用节点赋值给nodeTmp。⑶处把链表头结点执行下一个的下一个链表节点,然后执行⑷把分配出来的内存块设置魔术字,接着把内存池已用内存块数量加1。⑸处返回时调用宏OS_MEMBOX_USER_ADDR()计算出内存块的数据区域地质。

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);
}

对申请的内存块使用完毕,我们可以使用函数UINT32 LOS_MemboxFree(VOID *pool, VOID *box)来释放静态内存,需要2个参数,VOID *pool是初始化过的静态内存池地址。VOID *box是需要释放的静态内存块的数据区的起始地址,注意这个不是内存块的节点地址。下面分析下源码。

⑴处根据待释放的内存块的数据区域地址获取节点地址node,⑵对要释放的内存块先进行校验。⑶处把要释放的内存块挂在内存池空闲内存块链表上,然后执行⑷把已用数量减1。

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;
}

接下来,我们再看看校验函数OsCheckBoxMem()。⑴如果内存池的块大小为0,返回校验失败。⑵处计算出要释放的内存快节点相对第一个内存块节点的偏移量offset。⑶如果偏移量除以内存块数量余数不为0,返回校验失败。⑷如果偏移量除以内存块数量的商大于等于内存块的数量,返回校验失败。⑸调用宏OS_MEMBOX_CHECK_MAGIC校验魔术字。

STATIC INLINE UINT32 OsCheckBoxMem(const LOS_MEMBOX_INFO *boxInfo, const VOID *node)
{
    UINT32 offset;

⑴  if (boxInfo->uwBlkSize == 0) {
        return LOS_NOK;
    }

⑵  offset = (UINT32)((UINTPTR)node - (UINTPTR)(boxInfo + 1));
⑶  if ((offset % boxInfo->uwBlkSize) != 0) {
        return LOS_NOK;
    }

⑷  if ((offset / boxInfo->uwBlkSize) >= boxInfo->uwBlkNum) {
        return LOS_NOK;
    }

⑸   return OS_MEMBOX_CHECK_MAGIC(node);
}

3、Membox管理算法

Membox内存管理,支持静态内存和动态内存,二选一,分别由宏开关LOSCFG_KERNEL_MEMBOX_STATIC和LOSCFG_KERNEL_MEMBOX_DYNAMIC进行控制。
Membox静态内存是默认算法,需要开发者提供一个栈中的静态内存区域;Membox动态内存支持从堆里动态分配内存,相应的内存代码在kernel\base\mem\membox\los_membox_dyn.c,代码比较简单,读者们自行阅读即可。

点击关注,第一时间了解华为云新鲜技术~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK