存储块的原理和创建

对于RTOS的内存管理,可能会想到malloc和free这种,但是这种不契合RTOS

  • 动态内存分配的时间不确定
  • 内存碎片化
  • 内存管理难度大

所以引入了内存池机制

但是内存池也有缺点,一种是不同类型不共享,例如int的内存池,char不能使用;一种是也会存在内部碎片,但是可以回收

typedef struct _tMemBlock
{
	// 事件控制块
    tEvent event;
    // 存储块的首地址
    void * memStart;
    // 每个存储块的大小
    uint32_t blockSize;
    // 总的存储块的个数
    uint32_t maxCount;
    // 存储块列表
    tList blockList;
}tMemBlock;

void tMemBlockInit (tMemBlock * memBlock, uint8_t * memStart, uint32_t blockSize, uint32_t blockCnt)
{
	uint8_t * memBlockStart = (uint8_t *)memStart;
	uint8_t * memBlockEnd = memBlockStart + blockSize * blockCnt;
	// 每个存储块需要来放置链接指针,所以空间至少要比tNode大
	// 即便如此,实际用户可用的空间并没有少
	if (blockSize < sizeof(tNode))
	{
		return;
	}
	tEventInit(&memBlock->event, tEventTypeMemBlock);
	memBlock->memStart = memStart;
	memBlock->blockSize = blockSize;
	memBlock->maxCount = blockCnt;
	tListInit(&memBlock->blockList);
	while (memBlockStart < memBlockEnd)
	{
		tNodeInit((tNode *)memBlockStart);
		tListAddLast(&memBlock->blockList, (tNode *)memBlockStart);
		memBlockStart += blockSize;
	}
}

新增tMemBlock用于控制存储块,同样里面有一个事件控制块,用于获取内存失败挂起任务

tMemBlockInit中,先确定好我们的大小,当然,由于空间至少要比tNode大,所以还需要判断;之后就是初始化信息,然后每隔blockSize开辟结点

存储块的获取和释放

uint32_t tMemBlockWait (tMemBlock * memBlock, uint8_t ** mem, uint32_t waitTicks)
{
    uint32_t status = tTaskEnterCritical();
    // 首先检查是否有空闲的存储块
    if (tListCount(&memBlock->blockList) > 0)
    {
    	// 如果有的话,取出一个
        *mem = (uint8_t *)tListRemoveFirst(&memBlock->blockList);
	    	tTaskExitCritical(status);
	    	return tErrorNoError;
    }
    else
    {
         // 然后将任务插入事件队列中
        tEventWait(&memBlock->event, currentTask, (void *)0,  tEventTypeMemBlock, waitTicks);
        tTaskExitCritical(status);
        // 最后再执行一次事件调度,以便于切换到其它任务
        tTaskSched();
        // 当切换回来时,从tTask中取出获得的消息
        *mem = currentTask->eventMsg;
        // 取出等待结果
        return currentTask->waitEventResult;
    }
}

uint32_t tMemBlockNoWaitGet (tMemBlock * memBlock, void ** mem)
{
    uint32_t status = tTaskEnterCritical();
    // 首先检查是否有空闲的存储块
    if (tListCount(&memBlock->blockList) > 0)
    {
    	// 如果有的话,取出一个
        *mem = (uint8_t *)tListRemoveFirst(&memBlock->blockList);
    	tTaskExitCritical(status);
    	return tErrorNoError;
    }
    else
    {
     	// 否则,返回资源不可用
        tTaskExitCritical(status);
	    	return tErrorResourceUnavaliable;
    }
}

void tMemBlockNotify (tMemBlock * memBlock, uint8_t * mem)
{
    uint32_t status = tTaskEnterCritical();
    // 检查是否有任务等待
    if (tEventWaitCount(&memBlock->event) > 0)
    {
    	// 如果有的话,则直接唤醒位于队列首部(最先等待)的任务
        tTask * task = tEventWakeUp(&memBlock->event, (void *)mem, tErrorNoError);
        // 如果这个任务的优先级更高,就执行调度,切换过去
        if (task->prio < currentTask->prio)
        {
            tTaskSched();
	    	}
    }
    else
    {
    	// 如果没有任务等待的话,将存储块插入到队列中
    	tListAddLast(&memBlock->blockList, (tNode *)mem);
   	}
	tTaskExitCritical(status);
}

其实邮箱与存储块基本一致,一个是对消息进行处理,一个是对存储块进行处理,这里的存储块肯定是比消息的构造复杂很多,但思路大差不差;但是对于通知来说,存储块的复杂度就低了许多,因为这里并没有区分插入头还是尾

而对于存储块的操作,可以借鉴以下代码

tMemBlockInit(&memBlock1, (uint8_t *)mem1, 100, 20);
for (i = 0; i < 20; i++)
{
		tMemBlockWait(&memBlock1, (uint8_t **)&block[i], 0);
}

存储块的删除和状态查询

typedef struct _tMemBlockInfo
{
	// 当前存储块的计数
    uint32_t count;
    // 允许的最大计数
    uint32_t maxCount;
    // 每个存储块的大小
    uint32_t blockSize;
    // 当前等待的任务计数
    uint32_t taskCount;
}tMemBlockInfo;

void tMemBlockGetInfo (tMemBlock * memBlock, tMemBlockInfo * info)
{
    uint32_t status = tTaskEnterCritical();
    // 拷贝需要的信息
    info->count = tListCount(&memBlock->blockList);
    info->maxCount = memBlock->maxCount;
    info->blockSize = memBlock->blockSize;
    info->taskCount = tEventWaitCount(&memBlock->event);
    tTaskExitCritical(status);
}
uint32_t tMemBlockDestroy (tMemBlock * memBlock)
{
    uint32_t status = tTaskEnterCritical();
    // 清空事件控制块中的任务
    uint32_t count = tEventRemoveAll(&memBlock->event, (void *)0, tErrorDel);
    tTaskExitCritical(status);
    // 清空过程中可能有任务就绪,执行一次调度
    if (count > 0)
    {
        tTaskSched();
    }
    return count;
}

对于删除,我们只需要清空并调度任务即可

在销毁存储控制块时,存储块列表本身已经是固定的,内存块的管理是通过链表或者数组来完成的;当销毁控制块时,通常不需要对这些内存块做进一步处理,它们仍然是有效的内存区域,只不过没有了控制块的统一管理。这种方式允许系统在不破坏存储块数据的前提下,继续使用这些内存,或者在某些情况下可以复用它们。如果需要重新初始化该内存块列表,后续的初始化操作可以重新将这些内存块放入新的存储控制块中

状态查询不多赘述