存储块
存储块的原理和创建


对于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;
}
对于删除,我们只需要清空并调度任务即可
在销毁存储控制块时,存储块列表本身已经是固定的,内存块的管理是通过链表或者数组来完成的;当销毁控制块时,通常不需要对这些内存块做进一步处理,它们仍然是有效的内存区域,只不过没有了控制块的统一管理。这种方式允许系统在不破坏存储块数据的前提下,继续使用这些内存,或者在某些情况下可以复用它们。如果需要重新初始化该内存块列表,后续的初始化操作可以重新将这些内存块放入新的存储控制块中
状态查询不多赘述