LiteOS内核源码分析系列十三 动态内存Bestfit分配算法(2)

举报
zhushy 发表于 2021/04/21 09:50:35 2021/04/21
【摘要】 2.4 按指定字节对齐申请动态内存我们还可以使用函数VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary),从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存。该函数需要3个参数,VOID *pool为内存池起始地址,UINT32 size为需要申请的内存大小,UINT32 boundary...

2.4 按指定字节对齐申请动态内存

我们还可以使用函数VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary),从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存。该函数需要3个参数,VOID *pool为内存池起始地址,UINT32 size为需要申请的内存大小,UINT32 boundary内存对齐数值。当申请内存后得到的内存地址VOID *ptr,对齐后的内存地址为VOID *alignedPtr,二者的偏移值使用UINT32 gapSize保存。因为已经按OS_MEM_ALIGN_SIZE内存对齐了,最大偏移值为boundary - OS_MEM_ALIGN_SIZE。下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,申请的内存大小不能为0,对齐字节boundary不能为0,还需要是2的幂。⑵处校验下对齐内存后是否会数据溢出。⑶处计算对齐后需要申请的内存大小,然后判断内存大小数值没有已使用或已对齐标记。⑷处调用函数申请到内存VOID *ptr,然后计算出对齐的内存地址VOID *alignedPtr,如果二者相等则返回。⑸处计算出对齐内存的偏移值,⑹处获取申请到的内存的控制节点,设置已对齐标记。⑺对偏移值设置对齐标记,然后把偏移值保存在内存VOID *alignedPtr的前4个字节里。⑻处重新定向要返回的指针,完成申请对齐的内存。

LITE_OS_SEC_TEXT VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary)
{
    UINT32 useSize;
    UINT32 gapSize;
    VOID *ptr = NULL;
    VOID *alignedPtr = NULL;
    LosMemDynNode *allocNode = NULL;
    UINT32 intSave;if ((pool == NULL) || (size == 0) || (boundary == 0) || !IS_POW_TWO(boundary) ||
        !IS_ALIGNED(boundary, sizeof(VOID *))) {
        return NULL;
    }

    MEM_LOCK(intSave);
    do {if ((boundary - sizeof(gapSize)) > ((UINT32)(-1) - size)) {
            break;
        }

⑶      useSize = (size + boundary) - sizeof(gapSize);
        if (OS_MEM_NODE_GET_USED_FLAG(useSize) || OS_MEM_NODE_GET_ALIGNED_FLAG(useSize)) {
            break;
        }

⑷      ptr = OsMemAllocWithCheck(pool, useSize);

        alignedPtr = (VOID *)OS_MEM_ALIGN(ptr, boundary);
        if (ptr == alignedPtr) {
            break;
        }

⑸      gapSize = (UINT32)((UINTPTR)alignedPtr - (UINTPTR)ptr);
⑹      allocNode = (LosMemDynNode *)ptr - 1;
        OS_MEM_NODE_SET_ALIGNED_FLAG(allocNode->selfNode.sizeAndFlag);
#ifdef LOSCFG_MEM_RECORDINFO
        allocNode->selfNode.originSize = size;
#endif
#ifdef LOSCFG_MEM_HEAD_BACKUP
        OsMemNodeSaveWithGapSize(allocNode, gapSize);
#endifOS_MEM_NODE_SET_ALIGNED_FLAG(gapSize);
        *(UINT32 *)((UINTPTR)alignedPtr - sizeof(gapSize)) = gapSize;

⑻      ptr = alignedPtr;
    } while (0);
#ifdef LOSCFG_MEM_RECORDINFO
    OsMemRecordMalloc(ptr, size);
#endif
    OsLmsSetAfterMalloc(ptr);

    MEM_UNLOCK(intSave);

    LOS_TRACE(MEM_ALLOC_ALIGN, pool, (UINTPTR)ptr, size, boundary);
    return ptr;
}

2.5 释放动态内存

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

⑴处对传入的参数先进行校验。⑵如果内存是从SLAB中申请的内存,需要释放到SLAB内存区。⑶处调用函数OsMemFree(pool, ptr)完成内存的释放。

LITE_OS_SEC_TEXT UINT32 LOS_MemFree(VOID *pool, VOID *ptr)
{
    UINT32 ret;
    UINT32 intSave;if ((pool == NULL) || (ptr == NULL) ||
        !IS_ALIGNED(pool, sizeof(VOID *)) || !IS_ALIGNED(ptr, sizeof(VOID *))) {
        return LOS_NOK;
    }

    MEM_LOCK(intSave);if (OsSlabMemFree(pool, ptr)) {
        ret = LOS_OK;
        goto OUT;
    }

⑶  ret = OsMemFree(pool, ptr);
OUT:
    OsLmsSetAfterFree(ptr);
    MEM_UNLOCK(intSave);

    LOS_TRACE(MEM_FREE, pool, (UINTPTR)ptr);
    return ret;
}

我们继续分析下函数OsMemFree(pool, ptr)。⑴处获取gapSize,对于函数LOS_MemAlloc()申请的内存,gapSize对应控制节点LosMemCtlNode的成员变量sizeAndFlag;对于函数LOS_MemAllocAlign()申请的内存,gapSize对应内存对齐偏移值。对于第一种情况,只标记已使用,第二种情况只标记已对齐。⑵处表示如果既标记已使用,又标记已对齐,则返回错误。⑶处获取内存控制节点,对于第二种情况这样的获取是错误的。⑷处代码校准第二种情况,如果gapSize标记为已对齐,去除gapSize中的对齐标记,获取偏移值。⑸处对偏移值进行校验,然后执行⑹获取获取内存控制节点。⑺处进一步调用函数完成内存的释放。

UINT32 OsMemFree(VOID *pool, VOID *ptr)
{
    UINT32 ret = LOS_NOK;
    UINT32 gapSize;
    LosMemDynNode *node = NULL;

    do {
⑴      gapSize = *(UINT32 *)((UINTPTR)ptr - sizeof(UINT32));if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize) && OS_MEM_NODE_GET_USED_FLAG(gapSize)) {
            PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
            goto OUT;
        }

⑶      node = (LosMemDynNode *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize)) {
            gapSize = OS_MEM_NODE_GET_ALIGNED_GAPSIZE(gapSize);if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) || (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE))) {
                PRINT_ERR("illegal gapSize: 0x%x\n", gapSize);
                break;
            }
⑹          node = (LosMemDynNode *)((UINTPTR)ptr - gapSize - OS_MEM_NODE_HEAD_SIZE);
        }
#ifndef LOSCFG_MEM_HEAD_BACKUP
⑺      ret = OsDoMemFree(pool, ptr, node);
#endif
    } while (0);
#ifdef LOSCFG_MEM_HEAD_BACKUP
    ret = OsMemBackupCheckAndRetore(pool, ptr, node);
    if (!ret) {
        ret = OsDoMemFree(pool, ptr, node);
    }
#endif

OUT:
#ifdef LOSCFG_MEM_RECORDINFO
    if (ret == LOS_NOK) {
        OsMemRecordFree(ptr, 0);
    }
#endif
    return ret;
}

我们继续看下函数OsDoMemFree(),该函数进一步调用函数OsMemFreeNode(node, pool)完成内存释放。

LITE_OS_SEC_TEXT STATIC INLINE UINT32 OsDoMemFree(VOID *pool, const VOID *ptr, LosMemDynNode *node)
{
    UINT32 ret = OsMemCheckUsedNode(pool, node);
    if (ret == LOS_OK) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(ptr, node->selfNode.originSize);
#endif
        OsMemFreeNode(node, pool);
    }
    return ret;
}

函数OsMemFreeNode(node, pool)如下,继续分析。⑴处获取动态内存池的第一个内存控制节点,⑵处去除已使用标记。⑶处处理前一个节点不为空,且没有使用的情况。⑷处执行内存节点合并,然后获取下一个节点nextNode,如果下一个节点也是未使用节点,则把下一个节点从链表中删除,并把空闲节点进行合并。⑸处把前一个节点从链表中删除,基于合并后的内存节点大小重新挂载到链表上。

如果上一个节点已使用,无法和上一个节点合并,则执行⑹获取下一个节点。如果下一个节点也是未使用节点,则把下一个节点从链表中删除,并把空闲节点进行合并。⑺根据内存节点大小获取链表节点,然后把释放的内存节点挂载到链表上,完成内存节点的释放。

STATIC INLINE VOID OsMemFreeNode(LosMemDynNode *node, LosMemPoolInfo *pool)
{
    LosMemDynNode *nextNode = NULL;
    LOS_DL_LIST *listNodeHead = NULL;const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE);

    OS_MEM_REDUCE_USED(&pool->stat, OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag), OS_MEM_TASKID_GET(node));
⑵  node->selfNode.sizeAndFlag = OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag);
#ifdef LOSCFG_MEM_HEAD_BACKUP
    OsMemNodeSave(node);
#endif
#ifdef LOSCFG_MEM_LEAKCHECK
    OsMemLinkRegisterRecord(node);
#endifif ((node->selfNode.preNode != NULL) &&
        !OS_MEM_NODE_GET_USED_FLAG(node->selfNode.preNode->selfNode.sizeAndFlag)) {
        LosMemDynNode *preNode = node->selfNode.preNode;OsMemMergeNode(node);
        nextNode = OS_MEM_NEXT_NODE(preNode);
        if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) {
            OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode);
            OsMemMergeNode(nextNode);
        }OsMemListDelete(&(preNode->selfNode.freeNodeInfo), firstNode);
        listNodeHead = OS_MEM_HEAD(pool, preNode->selfNode.sizeAndFlag);
        OS_CHECK_NULL_RETURN(listNodeHead);

        OsMemListAdd(listNodeHead, &preNode->selfNode.freeNodeInfo, firstNode);
    } else {
⑹      nextNode = OS_MEM_NEXT_NODE(node);
        if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) {
            OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode);
            OsMemMergeNode(nextNode);
        }

⑺      listNodeHead = OS_MEM_HEAD(pool, node->selfNode.sizeAndFlag);
        OS_CHECK_NULL_RETURN(listNodeHead);

        OsMemListAdd(listNodeHead, &node->selfNode.freeNodeInfo, firstNode);
    }
}

2.6 重新申请动态内存

我们还可以使用函数VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size),按size大小重新分配内存块,并将原内存块内容拷贝到新内存块。如果新内存块申请成功,则释放原内存块。该函数需要3个参数,VOID *pool为内存池起始地址,VOID *ptr为之前申请的内存地址,UINT32 size为重新申请的内存大小。下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,内存大小不能含有已使用、已对齐标记。⑵处如果传入的内存地址为空,则等价于LOS_MemAlloc()函数。⑶如果传入size为0,等价于函数LOS_MemFree()。⑷如果开启支持SLAB,需要调用OsMemReallocSlab()函数重新申请SLAB内存。⑸处调用函数OsMemRealloc()进行重新申请内存。

LITE_OS_SEC_TEXT_MINOR VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
    UINT32 intSave;
    VOID *newPtr = NULL;
    BOOL isSlabMem = FALSE;if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size) || (pool == NULL)) {
        return NULL;
    }if (ptr == NULL) {
        newPtr = LOS_MemAlloc(pool, size);
        goto OUT;
    }if (size == 0) {
        (VOID)LOS_MemFree(pool, ptr);
        goto OUT;
    }

    MEM_LOCK(intSave);

⑷  newPtr = OsMemReallocSlab(pool, ptr, &isSlabMem, size);
    if (isSlabMem == TRUE) {
        goto OUT_UNLOCK;
    }

⑸  newPtr = OsMemRealloc(pool, ptr, size);

OUT_UNLOCK:
    MEM_UNLOCK(intSave);
OUT:

    LOS_TRACE(MEM_REALLOC, pool, (UINTPTR)ptr, size);
    return newPtr;
}

进一步看下函数OsMemRealloc()。⑴处获取内存对齐后的大小allocSize,⑵处获取内存对齐之前的地址,稍后会分析该函数OsGetRealPtr()。⑶获取内存控制节点node,然后获取节点的大小nodeSize。⑷处理重新申请的内存小于等于现有的内存的情况,需要调用函数OsMemReAllocSmaller()进行分割,分割完毕返回(VOID *)ptr即可。如果重新申请更大的内存,则执行⑸获取下一个节点,然后⑹处理下一个节点可用且两个节点大小之和大于等于重新申请内存的大小allocSize。执行⑺处的函数,合并节点重新分配内存。

如果连续的节点的大小不满足重新申请内存的大小,则执行⑻处函数重新申请内存。然后执行⑼把之前内存的数据复制到新申请的内存区域,复制失败的话,则把新申请的内存释放掉,并返回退出函数。如果复制成功,继续执行⑽释放掉之前的节点。

STATIC VOID *OsMemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
    LosMemDynNode *node = NULL;
    LosMemDynNode *nextNode = NULL;
    VOID *tmpPtr = NULL;
    VOID *realPtr = NULL;
    UINT32 nodeSize;
⑴  UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
#ifdef LOSCFG_MEM_RECORDINFO
    const VOID *originPtr = ptr;
#endif

⑵  realPtr = OsGetRealPtr(pool, ptr);
    if (realPtr == NULL) {
        return NULL;
    }

⑶   node = (LosMemDynNode *)((UINTPTR)realPtr - OS_MEM_NODE_HEAD_SIZE);
    if (OsMemCheckUsedNode(pool, node) != LOS_OK) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, 0);
#endif
        return NULL;
    }

    nodeSize = OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag);if (nodeSize >= allocSize) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, node->selfNode.originSize);
#endif
        OsMemReAllocSmaller(pool, allocSize, node, nodeSize);
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemReallocNodeRecord(node, size, ptr);
#endif
        return (VOID *)ptr;
    }

⑸  nextNode = OS_MEM_NEXT_NODE(node);if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag) &&
        ((nextNode->selfNode.sizeAndFlag + nodeSize) >= allocSize)) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, node->selfNode.originSize);
#endifOsMemMergeNodeForReAllocBigger(pool, allocSize, node, nodeSize, nextNode);
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemReallocNodeRecord(node, size, ptr);
#endif
        return (VOID *)ptr;
    }

⑻  tmpPtr = OsMemAllocWithCheck(pool, size);
    if (tmpPtr != NULL) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordMalloc(tmpPtr, size);
#endif
        UINT32 gapSize = (UINT32)((UINTPTR)ptr - (UINTPTR)realPtr);if (memcpy_s(tmpPtr, size, ptr, (nodeSize - OS_MEM_NODE_HEAD_SIZE - gapSize)) != EOK) {
            (VOID)OsMemFree((VOID *)pool, (VOID *)tmpPtr);
            return NULL;
        }
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, node->selfNode.originSize);
#endifOsMemFreeNode(node, pool);
    }
    return tmpPtr;
}

我们回过头来,继续看下函数OsGetRealPtr()。⑴获取内存对齐的偏移值,⑵如果偏移值同时标记为已使用和已对齐,则返回错误。⑶如果偏移值标记为已对齐,则执行⑷去除对齐标记,获取单纯的偏移值。然后执行⑸,获取内存对齐之前的数据区内存地址。

STATIC VOID *OsGetRealPtr(const VOID *pool, VOID *ptr)
{
    VOID *realPtr = ptr;
⑴  UINT32 gapSize = *((UINT32 *)((UINTPTR)ptr - sizeof(UINT32)));if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize) && OS_MEM_NODE_GET_USED_FLAG(gapSize)) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(ptr, 0);
#endif
        PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
        return NULL;
    }if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize)) {
⑷      gapSize = OS_MEM_NODE_GET_ALIGNED_GAPSIZE(gapSize);
        if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) ||
            (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE - (UINTPTR)pool))) {
            PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
#ifdef LOSCFG_MEM_RECORDINFO
            OsMemRecordFree(ptr, 0);
#endif
            return NULL;
        }
⑸      realPtr = (VOID *)((UINTPTR)ptr - (UINTPTR)gapSize);
    }
    return realPtr;
}

小结

本文带领大家一起剖析了LiteOS动态内存模块bestfit算法的源代码,包含动态内存的结构体、动态内存池初始化、动态内存申请、释放等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到LiteOS代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。