LiteOS内核源码分析:消息队列Queue
摘要:本文通过分析LiteOS队列模块的源码,掌握队列使用上的差异。
队列(Queue)是一种常用于任务间通信的数据结构。任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。
本文通过分析LiteOS队列模块的源码,掌握队列使用上的差异。LiteOS队列模块的源代码,均可以在LiteOS
开源站点https://gitee.com/LiteOS/LiteOS 获取。队列源代码、开发文档,示例程序代码如下:
- LiteOS内核队列源代码
包括队列的私有头文件kernel\base\include\los_queue_pri.h、头文件kernel\include\los_queue.h、C源代码文件kernel\base\los_queue.c。
- 开发指南文档–队列
在线文档https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E9%98%9F%E5%88%97。
接下来,我们看下队列的结构体,队列初始化,队列常用操作的源代码。
1、队列结构体定义和常用宏定义
1.1 队列结构体定义
在文件kernel\base\include\los_queue_pri.h定义的队列控制块结构体为LosQueueCB,结构体源代码如下。队列状态.queueState取值OS_QUEUE_UNUSED、OS_QUEUE_INUSED,队列内存类型.queueMemType取值OS_QUEUE_ALLOC_STATIC、OS_QUEUE_ALLOC_DYNAMIC,分别表示计用户分配队列内存空间或系统自动分配。
typedef struct {
UINT8 *queueHandle; /**< 队列内存空间的指针 */
UINT8 queueState; /**< 队列的使用状态 */
UINT8 queueMemType; /**< 队列内存类型,用户分配队列内存空间或系统自动分配 */
UINT16 queueLen; /**< 队列长度,消息数量 */
UINT16 queueSize; /**< 消息节点大小 */
UINT32 queueId; /**< 队列编号 */
UINT16 queueHead; /**< 消息头节点位置 */
UINT16 queueTail; /**< 消息尾节点位置 */
UINT16 readWriteableCnt[OS_QUEUE_N_RW]; /**< 2维数组,可读、可写的消息数量, 0:可读, 1:可写 */
LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /**< 2维链表数组,阻塞读、写任务的双向链表, 0:读链表, 1:写链表 */
LOS_DL_LIST memList; /**< 内存节点双向链表,兼容CMSIS时使用 */
} LosQueueCB;
1.2 队列常用宏定义
系统支持创建多少队列是根据开发板情况使用宏LOSCFG_BASE_IPC_QUEUE_LIMIT定义的,每一个队列queueId是UINT32类型的。队列queueId由2部分组成:count和index,分别处于高16位和低16位。创建队列,使用后删除时,队列回收到队列池时,队列queueId的高16位即count值会加1,这样可以用来表示该队列被创建删除的次数。index取值为[0,LOSCFG_BASE_IPC_QUEUE_LIMIT),表示队列池中各个队列的编号。
⑴处的宏用来分割count和index的位数,⑵处的宏在队列被删除时用来更新队列编号queueId,可以看出高16位为count和低16位为index。⑶处获取队列queueId的低16位。⑷根据队列queueId获取对应的队列被创建删除的次数count。⑸处从队列池中获取指定队列queueId对应的队列控制块。
⑴ #define QUEUE_SPLIT_BIT 16 ⑵ #define SET_QUEUE_ID(count, index) (((count) << QUEUE_SPLIT_BIT) | (index)) ⑶ #define GET_QUEUE_INDEX(queueId) ((queueId) & ((1U << QUEUE_SPLIT_BIT) - 1)) ⑷ #define GET_QUEUE_COUNT(queueId) ((queueId) >> QUEUE_SPLIT_BIT) ⑸ #define GET_QUEUE_HANDLE(queueId) (((LosQueueCB *)g_allQueue) + GET_QUEUE_INDEX(queueId))
另外,队列中还提供了比较重要的队列读取消息操作相关的枚举和宏。队列的读取消息操作类型和队首还是队尾写入,读取还是写入有关系,所以操作类型使用2比特的数字来表示,高1位表示队首还是队尾,低1位表示读取还是写入。定义如下:
typedef enum {
OS_QUEUE_READ = 0,
OS_QUEUE_WRITE = 1,
OS_QUEUE_N_RW = 2
} QueueReadWrite; typedef enum {
OS_QUEUE_HEAD = 0,
OS_QUEUE_TAIL = 1
} QueueHeadTail; #define OS_QUEUE_OPERATE_TYPE(ReadOrWrite, HeadOrTail) (((UINT32)(HeadOrTail) << 1) | (ReadOrWrite))
#define OS_QUEUE_READ_WRITE_GET(type) ((type) & 0x01U)
#define OS_QUEUE_READ_HEAD (OS_QUEUE_READ | (OS_QUEUE_HEAD << 1))
#define OS_QUEUE_READ_TAIL (OS_QUEUE_READ | (OS_QUEUE_TAIL << 1))
#define OS_QUEUE_WRITE_HEAD (OS_QUEUE_WRITE | (OS_QUEUE_HEAD << 1))
#define OS_QUEUE_WRITE_TAIL (OS_QUEUE_WRITE | (OS_QUEUE_TAIL << 1))
#define OS_QUEUE_OPERATE_GET(type) ((type) & 0x03U)
#define OS_QUEUE_IS_READ(type) (OS_QUEUE_READ_WRITE_GET(type) == OS_QUEUE_READ)
#define OS_QUEUE_IS_WRITE(type) (OS_QUEUE_READ_WRITE_GET(type) == OS_QUEUE_WRITE)
2、队列初始化
队列在内核中默认开启,用户可以通过宏LOSCFG_BASE_IPC_QUEUE进行关闭。开启队列的情况下,在系统启动时,在kernel\init\los_init.c中调用OsQueueInit()进行队列模块初始化。下面,我们分析下队列初始化的代码。
⑴为队列申请内存,如果申请失败,则返回错误。⑵初始化双向循环链表g_freeQueueList,维护未使用的队列。⑶循环每一个队列进行初始化,为每一个队列节点指定索引queueId,并把队列节点插入未使用队列双向链表g_freeQueueList。代码上可以看出,挂在未使用队列双向链表上的节点是每个队列控制块的写阻塞任务链表点.readWriteList[OS_QUEUE_WRITE]。⑷如果开启了队列调测开关,则调用函数OsQueueDbgInitHook()进行初始化。
LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)
{
LosQueueCB *queueNode = NULL;
UINT32 index;
UINT32 size; size = LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB);
⑴ g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, size);
if (g_allQueue == NULL) {
return LOS_ERRNO_QUEUE_NO_MEMORY;
}
(VOID)memset_s(g_allQueue, size, 0, size);
⑵ LOS_ListInit(&g_freeQueueList);
⑶ for (index = 0; index < LOSCFG_BASE_IPC_QUEUE_LIMIT; index++) {
queueNode = ((LosQueueCB *)g_allQueue) + index;
queueNode->queueId = index;
LOS_ListTailInsert(&g_freeQueueList, &queueNode->readWriteList[OS_QUEUE_WRITE]);
} ⑷ if (OsQueueDbgInitHook() != LOS_OK) {
return LOS_ERRNO_QUEUE_NO_MEMORY;
}
return LOS_OK;
}
3、队列常用操作
3.1 队列创建
创建队列的函数有2个:LOS_QueueCreateStatic()和LOS_QueueCreate(),二者的区别是存放队列消息的内存空间是用户创建还是系统自动创建。前者需要开启宏LOSCFG_QUEUE_STATIC_ALLOCATION时才可用,使用用户分配队列内存空间,后者在创建队列时动态创建队列内存空间。我们看看2个函数的传参:queueName是队列名称,实际上并没有使用。len是队列消息的长度,queueId是队列编号,flags保留未使用maxMsgSize是队列中每条消息的最大大小。对于静态创建队列,还有2个参数来表示用户创建的队列内存空间的地址指针queueMem和内存大小memSize。
我们分析下创建队列的代码。先看静态创建,调用⑴处的函数OsQueueCreateParameterCheck()对参数进行校验,⑵对传入的内存空间进行验证,确保足够大放得下队列的消息,其中maxMsgSize + sizeof(UINT32)表示消息最大大小,另外再加4个字节,在消息的最后4个字节用来保存消息的实际长度。然后调用⑶处函数OsQueueCreateInternal()完成队列创建。再看下动态创建队列的代码,⑷处对队列动态申请内存,然后调用函数OsQueueCreateInternal()完成队列创建。2个函数调用同样的内部创建队列函数OsQueueCreateInternal(),下文继续分析。
#ifdef LOSCFG_QUEUE_STATIC_ALLOCATION
LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreateStatic(CHAR *queueName,
UINT16 len,
UINT32 *queueId,
UINT32 flags,
UINT16 maxMsgSize,
VOID *queueMem,
UINT16 memSize)
{
UINT32 ret;
UINT16 msgSize;
(VOID)queueName;
(VOID)flags; ⑴ ret = OsQueueCreateParameterCheck(len, queueId, maxMsgSize);
if (ret != LOS_OK) {
return ret;
} if (queueMem == NULL) {
return LOS_ERRNO_QUEUE_CREAT_PTR_NULL;
} ⑵ msgSize = maxMsgSize + sizeof(UINT32);
if (memSize < ((UINT32)msgSize * len)) {
return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;
} ⑶ return OsQueueCreateInternal(len, queueId, msgSize, queueMem, OS_QUEUE_ALLOC_STATIC);
}
#endif LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName, UINT16 len, UINT32 *queueId,
UINT32 flags, UINT16 maxMsgSize)
{
UINT32 ret;
UINT8 *queueMem = NULL;
UINT16 msgSize;
(VOID)queueName;
(VOID)flags; ret = OsQueueCreateParameterCheck(len, queueId, maxMsgSize);
if (ret != LOS_OK) {
return ret;
} msgSize = maxMsgSize + sizeof(UINT32);
⑷ queueMem = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, (UINT32)len * msgSize);
if (queueMem == NULL) {
return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;
} ret = OsQueueCreateInternal(len, queueId, msgSize, queueMem, OS_QUEUE_ALLOC_DYNAMIC);
if (ret != LOS_OK) {
(VOID)LOS_MemFree(m_aucSysMem1, queueMem);
return ret;
} return LOS_OK;
}
我们看看创建队列的内部函数OsQueueCreateInternal()。⑴判断g_freeQueueList是否为空,如果没有可以使用的队列,调用函数OsQueueCheckHook()做些调测相关的检测,这个函数需要开启调测开关,后续系列专门分析。⑵处如果g_freeQueueList不为空,则获取第一个可用的队列节点,接着从双向链表g_freeQueueList中删除,然后调用宏GET_QUEUE_LIST获取LosQueueCB *queueCB,初始化创建的队列信息,包含队列的长度.queueLen、消息大小.queueSize,队列内存空间.queueHandle,消息状态.queueState,内存类型.queueMemType,可读的数量.readWriteableCnt[OS_QUEUE_READ]为0,可写的数量readWriteableCnt[OS_QUEUE_WRITE]为队列消息长度len,队列头位置.queueHead和尾位置.queueTail为0。
⑶初始化双向链表.readWriteList[OS_QUEUE_READ],阻塞在这个队列上的读消息任务会挂在这个链表上。初始化双向链表.readWriteList[OS_QUEUE_WRITE],阻塞在这个队列上的写消息任务会挂在这个链表上。初始化双向链表.memList,这个开启兼容CMSIS宏LOSCFG_COMPAT_CMSIS时,才会使用到。⑷赋值给输出参数*queueId,后续程序使用这个队列编号对队列进行其他操作。
LITE_OS_SEC_TEXT_INIT STATIC UINT32 OsQueueCreateInternal(UINT16 len, UINT32 *queueId, UINT16 msgSize,
UINT8 *queue, UINT8 queueMemType)
{
LosQueueCB *queueCB = NULL;
LOS_DL_LIST *unusedQueue = NULL;
UINT32 intSave; SCHEDULER_LOCK(intSave);
⑴ if (LOS_ListEmpty(&g_freeQueueList)) {
SCHEDULER_UNLOCK(intSave);
OsQueueCheckHook();
return LOS_ERRNO_QUEUE_CB_UNAVAILABLE;
} ⑵ unusedQueue = LOS_DL_LIST_FIRST(&g_freeQueueList);
LOS_ListDelete(unusedQueue);
queueCB = GET_QUEUE_LIST(unusedQueue);
queueCB->queueLen = len;
queueCB->queueSize = msgSize;
queueCB->queueHandle = queue;
queueCB->queueState = OS_QUEUE_INUSED;
queueCB->queueMemType = queueMemType;
queueCB->readWriteableCnt[OS_QUEUE_READ] = 0;
queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len;
queueCB->queueHead = 0;
queueCB->queueTail = 0;
⑶ LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_READ]);
LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_WRITE]);
LOS_ListInit(&queueCB->memList); OsQueueDbgUpdateHook(queueCB->queueId, OsCurrTaskGet()->taskEntry);
SCHEDULER_UNLOCK(intSave); ⑷ *queueId = queueCB->queueId; LOS_TRACE(QUEUE_CREATE, *queueId, len, msgSize - sizeof(UINT32), (UINTPTR)queue, queueMemType);
return LOS_OK;
}
3.2 队列删除
我们可以使用函数LOS_QueueDelete(UINT32 queueId)来删除队列,下面通过分析源码看看如何删除队列的。
⑴处判断队列queueId是否超过LOSCFG_BASE_IPC_queue_LIMIT,如果超过则返回错误码。如果队列编号没有问题,获取队列控制块LosQueueCB *queueCB。⑵处判断要删除的队列queueId是否匹配,或者要删除的队列处于未使用状态,则跳转到错误标签QUEUE_END`进行处理。⑶如果队列的阻塞读、阻塞写任务列表不为空,或内存节点链表不为空,则不允许删除,跳转到错误标签进行处理。⑷处检验队列的可读、可写数量是否出错。
⑸处获取队列的内存空间,如果是动态创建的内存,接下来会需要调用⑺处函数LOS_MemFree()释放。⑹处把.queueState设置为未使用OS_QUEUE_UNUSED,设置队列编号.queueId,并把队列节点插入未使用队列双向链表g_freeQueueList。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueDelete(UINT32 queueId)
{
LosQueueCB *queueCB = NULL;
UINT8 *queue = NULL;
UINT32 intSave;
UINT32 ret = LOS_OK; ⑴ if (GET_QUEUE_INDEX(queueId) >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {
return LOS_ERRNO_QUEUE_NOT_FOUND;
} queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueId); LOS_TRACE(QUEUE_DELETE, queueId, queueCB->queueState, queueCB->readWriteableCnt[OS_QUEUE_READ]); SCHEDULER_LOCK(intSave);
⑵ if ((queueCB->queueId != queueId) || (queueCB->queueState == OS_QUEUE_UNUSED)) {
ret = LOS_ERRNO_QUEUE_NOT_CREATE;
goto QUEUE_END;
} ⑶ if (!LOS_ListEmpty(&queueCB->readWriteList[OS_QUEUE_READ])) {
ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
goto QUEUE_END;
} if (!LOS_ListEmpty(&queueCB->readWriteList[OS_QUEUE_WRITE])) {
ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
goto QUEUE_END;
} if (!LOS_ListEmpty(&queueCB->memList)) {
ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
goto QUEUE_END;
} ⑷ if ((queueCB->readWriteableCnt[OS_QUEUE_WRITE] + queueCB->readWriteableCnt[OS_QUEUE_READ]) !=
queueCB->queueLen) {
ret = LOS_ERRNO_QUEUE_IN_TSKWRITE;
goto QUEUE_END;
} ⑸ queue = queueCB->queueHandle;
⑹ queueCB->queueHandle = NULL;
queueCB->queueState = OS_QUEUE_UNUSED;
queueCB->queueId = SET_QUEUE_ID(GET_QUEUE_COUNT(queueCB->queueId) + 1, GET_QUEUE_INDEX(queueCB->queueId));
OsQueueDbgUpdateHook(queueCB->queueId, NULL); LOS_ListTailInsert(&g_freeQueueList, &queueCB->readWriteList[OS_QUEUE_WRITE]);
SCHEDULER_UNLOCK(intSave);
if (queueCB->queueMemType == OS_QUEUE_ALLOC_DYNAMIC) {
⑺ ret = LOS_MemFree(m_aucSysMem1, (VOID *)queue);
}
return ret; QUEUE_END:
SCHEDULER_UNLOCK(intSave);
return ret;
}
下面就来看看队列的读写,有2点需要注意:
- (1)队首、队尾的读写
只支持队首读取,否则就不算队列了。除了正常的队尾写消息外,还提供插队机制,支持从队首写入。
- (2)队列消息数据内容
往队列中写入的消息的类型有2中,即支持按地址写入和按值写入(带拷贝)。按哪种类型写入,就需要配对的按相应的类型去读取。
队列读取接口的类别,归纳如下:
3.3 队列读取
我们知道有2个队列读取方法,按引用读取的函数LOS_QueueRead()把消息地址值bufferAddr作为数值,进一步调用按值读取的函数LOS_QueueReadCopy()。
LITE_OS_SEC_TEXT UINT32 LOS_QueueRead(UINT32 queueId, VOID *bufferAddr, UINT32 bufferSize, UINT32 timeout)
{
return LOS_QueueReadCopy(queueId, bufferAddr, &bufferSize, timeout);
}
再来看看函数LOS_QueueReadCopy()。⑴处校验传入参数,队列编号不能超出限制,传入的指针不能为空。如果timeout不为零会阻塞时,不能在中断中读取队列。⑵处操作类型表示队首读取,然后调用函数OsQueueOperate()进一步操作队列。
LITE_OS_SEC_TEXT UINT32 LOS_QueueReadCopy(UINT32 queueId,
VOID *bufferAddr,
UINT32 *bufferSize,
UINT32 timeout)
{
UINT32 ret;
UINT32 operateType; ⑴ ret = OsQueueReadParameterCheck(queueId, bufferAddr, bufferSize, timeout);
if (ret != LOS_OK) {
return ret;
} ⑵ operateType = OS_QUEUE_OPERATE_TYPE(OS_QUEUE_READ, OS_QUEUE_HEAD);
return OsQueueOperate(queueId, operateType, bufferAddr, bufferSize, timeout);
}
我们进一步分析下函数OsQueueOperate(),这是是比较通用的封装,读取,写入都会调用这个函数,我们以读取队列为例分析这个函数。⑴处获取队列的操作类型,为读取操作。⑵处先调用函数OsQueueOperateParamCheck()进行参数校验,校验队列是使用中的队列,并对读写消息大小进行校验。⑶处如果可读数量为0,无法读取时,如果是零等待则返回错误码。如果当前锁任务调度,跳出函数执行。否则,执行⑷把当前任务放入队列的读取消息阻塞队列,然后触发任务调度,后续的代码暂时不再执行。如果可读的数量不为0,可以继续读取时,执行⑹处代码把可读数量减1,然后继续执行⑺处代码读取队列。
等读取队列阻塞超时,或者队列可以读取后,继续执行⑸处的代码。如果是发生超时,队列还不能读取,更改任务状态,跳出函数执行。如果队列可以读取了,继续执行⑺处代码读取队列。⑻处在成功读取队列后,如果有任务阻塞在写入队列,则获取阻塞链表中的第一个任务resumedTask,然后调用唤醒函数OsTaskWake()把待恢复的任务放入就绪队列,触发一次任务调度。如果无阻塞任务,则把可写入的数量加1。
UINT32 OsQueueOperate(UINT32 queueId, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeout)
{
LosQueueCB *queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueId);
LosTaskCB *resumedTask = NULL;
UINT32 ret;
⑴ UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);
UINT32 intSave; LOS_TRACE(QUEUE_RW, queueId, queueCB->queueSize, *bufferSize, operateType,
queueCB->readWriteableCnt[OS_QUEUE_READ], queueCB->readWriteableCnt[OS_QUEUE_WRITE], timeout); SCHEDULER_LOCK(intSave);
⑵ ret = OsQueueOperateParamCheck(queueCB, queueId, operateType, bufferSize);
if (ret != LOS_OK) {
goto QUEUE_END;
} ⑶ if (queueCB->readWriteableCnt[readWrite] == 0) {
if (timeout == LOS_NO_WAIT) {
ret = OS_QUEUE_IS_READ(operateType) ? LOS_ERRNO_QUEUE_ISEMPTY : LOS_ERRNO_QUEUE_ISFULL;
goto QUEUE_END;
} if (!OsPreemptableInSched()) {
ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK;
goto QUEUE_END;
} ⑷ OsTaskWait(&queueCB->readWriteList[readWrite], OS_TASK_STATUS_PEND, timeout); OsSchedResched();
SCHEDULER_UNLOCK(intSave);
SCHEDULER_LOCK(intSave); ⑸ if (OsCurrTaskGet()->taskStatus & OS_TASK_STATUS_TIMEOUT) {
OsCurrTaskGet()->taskStatus &= ~OS_TASK_STATUS_TIMEOUT;
ret = LOS_ERRNO_QUEUE_TIMEOUT;
goto QUEUE_END;
}
} else {
⑹ queueCB->readWriteableCnt[readWrite]--;
} ⑺ OsQueueBufferOperate(queueCB, operateType, bufferAddr, bufferSize); ⑻ if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite])) {
resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[!readWrite]));
OsTaskWake(resumedTask, OS_TASK_STATUS_PEND);
SCHEDULER_UNLOCK(intSave);
LOS_MpSchedule(OS_MP_CPU_ALL);
LOS_Schedule();
return LOS_OK;
} else {
⑼ queueCB->readWriteableCnt[!readWrite]++;
} QUEUE_END:
SCHEDULER_UNLOCK(intSave);
return ret;
}
我们再继续看下函数OsQueueBufferOperate()是具体如何读取队列的。⑴处switch-case语句获取操作位置。对于⑵头部读取的情况,先获取读取位置queuePosition。然后,如果当前头节点位置.queueHead加1等于队列消息长度,头节点位置.queueHead设置为0,否则加1。对于⑶头部写入的情况,如果当前头节点位置.queueHead加1等于队列消息长度,头节点位置.queueHead设置为队列消息长度减1,否则头节点位置.queueHead减1即可。然后,获取要写入的位置queuePosition。对于⑷尾部写入的情况,先获取写入位置queuePosition。然后,如果当前尾节点位置.queueTail加1等于队列消息长度,尾节点位置.queueTail设置为0,否则加1。
⑸处基于获取的队列读取位置获取队列消息节点queueNode。我们看下⑹处如何读消息,每个消息节点的后4个字节保存的是消息的长度,首先获取消息的长度msgDataSize,然后执行⑺处代码把消息内容读取到bufferAddr。再看看⑻处如何写入队列消息,首先把消息内容写入到queueNode,然后执行⑼再把消息长度的内容写入到queueNode + queueCB->queueSize - sizeof(UINT32),就是每个消息节点的后4字节。
STATIC VOID OsQueueBufferOperate(LosQueueCB *queueCB, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize)
{
UINT8 *queueNode = NULL;
UINT32 msgDataSize;
UINT16 queuePosition; /* get the queue position */
⑴ switch (OS_QUEUE_OPERATE_GET(operateType)) {
case OS_QUEUE_READ_HEAD:
queuePosition = queueCB->queueHead;
⑵ ((queueCB->queueHead + 1) == queueCB->queueLen) ? (queueCB->queueHead = 0) : (queueCB->queueHead++);
break;
case OS_QUEUE_WRITE_HEAD:
⑶ (queueCB->queueHead == 0) ? (queueCB->queueHead = queueCB->queueLen - 1) : (--queueCB->queueHead);
queuePosition = queueCB->queueHead;
break;
case OS_QUEUE_WRITE_TAIL:
⑷ queuePosition = queueCB->queueTail;
((queueCB->queueTail + 1) == queueCB->queueLen) ? (queueCB->queueTail = 0) : (queueCB->queueTail++);
break;
default: /* read tail, reserved. */
PRINT_ERR("invalid queue operate type!\n");
return;
} ⑸ queueNode = &(queueCB->queueHandle[(queuePosition * (queueCB->queueSize))]); if (OS_QUEUE_IS_READ(operateType)) {
⑹ if (memcpy_s(&msgDataSize, sizeof(UINT32), queueNode + queueCB->queueSize - sizeof(UINT32),
sizeof(UINT32)) != EOK) {
PRINT_ERR("get msgdatasize failed\n");
return;
}
⑺ if (memcpy_s(bufferAddr, *bufferSize, queueNode, msgDataSize) != EOK) {
PRINT_ERR("copy message to buffer failed\n");
return;
} *bufferSize = msgDataSize;
} else {
⑻ if (memcpy_s(queueNode, queueCB->queueSize, bufferAddr, *bufferSize) != EOK) {
PRINT_ERR("store message failed\n");
return;
}
⑼ if (memcpy_s(queueNode + queueCB->queueSize - sizeof(UINT32), sizeof(UINT32), bufferSize,
sizeof(UINT32)) != EOK) {
PRINT_ERR("store message size failed\n");
return;
}
}
}
3.4 队列写入
我们知道,有4个队列写入方法,2个队尾写入,2个队首写入,分别包含按引用地址写入消息和按数值写入消息。LOS_QueueWrite()会调用LOS_QueueWriteCopy(),LOS_QueueWriteHead()会调用LOS_QueueWriteHeadCopy(),然后指定不同的操作类型后,会进一步调用前文已经分析过的函数OsQueueOperate()。
小结
本文带领大家一起剖析了LiteOS
队列模块的源代码,包含队列的结构体、队列池初始化、队列创建删除、读写消息等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到LiteOS
代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注Watch
、点赞Star
、并Fork
到自己账户下,谢谢。
本文分享自华为云社区《LiteOS内核源码分析系列十 消息队列Queue》,原文作者:zhushy 。
LiteOS内核源码分析:消息队列Queue的更多相关文章
- 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 百篇博客分析OpenHarmony源码 | v33.02
百篇博客系列篇.本篇为: v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁 ...
- v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威
百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...
- v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...
- [源码分析] 消息队列 Kombu 之 启动过程
[源码分析] 消息队列 Kombu 之 启动过程 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 是 ...
- [源码分析] 消息队列 Kombu 之 Consumer
[源码分析] 消息队列 Kombu 之 Consumer 目录 [源码分析] 消息队列 Kombu 之 Consumer 0x00 摘要 0x01 综述功能 0x02 示例代码 0x03 定义 3.1 ...
- [源码分析] 消息队列 Kombu 之 Producer
[源码分析] 消息队列 Kombu 之 Producer 目录 [源码分析] 消息队列 Kombu 之 Producer 0x00 摘要 0x01 示例代码 0x02 来由 0x03 建立 3.1 定 ...
- [源码分析] 消息队列 Kombu 之 Hub
[源码分析] 消息队列 Kombu 之 Hub 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 中的 ...
- [源码分析] 消息队列 Kombu 之 mailbox
[源码分析] 消息队列 Kombu 之 mailbox 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Komb ...
- LiteOS内核源码分析:任务LOS_Schedule
摘要:调度,Schedule也称为Dispatch,是操作系统的一个重要模块,它负责选择系统要处理的下一个任务.调度模块需要协调处于就绪状态的任务对资源的竞争,按优先级策略从就绪队列中获取高优先级的任 ...
- Android源码分析-消息队列和Looper
转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17361775 前言 上周对Android中的事件派发机制进行了分析,这次博主 ...
随机推荐
- kubernetes集群部署redis5.0.6单机版
1.首先,通过Config Map来对容器中redis应用的配置进行管理,如自定义配置文件.密码.日志路径等 redis-standalone-conf.yml apiVersion: v1 kind ...
- undefined reference to vtable for问题解决(QT)
主要在运行时出现 原因是在自定义类使用信号与槽,在创建文件时,未继承QObject类并且没有添加Q_OBJECT: 解决: 在需要的类中,添加Q_OBJECT,继承QObject类. 然后使用QTCr ...
- 知识图谱与大模型相结合的3种方法,1+1>2
本文分享自华为云社区<知识图谱与大模型结合方法概述>,作者: DevAI . <Unifying Large Language Models and Knowledge Graphs ...
- QT(1)- QString
QT(1)- QString 1 简介 在Qt中表示字符串的类是QString类,它存储字符串是采用的Unicode码,编码方式是使用UTF-16来进行编码的,也就是一个字符(两个字节),一个中文汉字 ...
- .NET周刊【11月第1期 2023-11-09】
国内文章 C#/.NET/.NET Core优秀项目和框架2023年10月简报 https://www.cnblogs.com/Can-daydayup/p/17804085.html 本文主要介绍了 ...
- easyEZbaby_app
for循环,这里给它化简255-i+2-98-未知数x需要等于'0'对应的ASCII值48,那么求x的值,x=111-i,而i的值就是从0到14,这样便可以计算出15位的密码 所以写出来的脚本
- Python利用pandas进行数据合并
当使用Python中的pandas库时,merge函数是用于合并(或连接)两个数据框(DataFrame)的重要工具.它类似于SQL中的JOIN操作,允许你根据一个或多个键(key)将两个数据框连接起 ...
- typeid关键词
typeid是c++的关键字,typeid操作符的返回结果是名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义) ISO C++标准并没有确切定义type_info,它的确 ...
- .NET8.0 AOT 经验分享 - 专项测试各大 ORM 是否支持
AOT 特点 发布和部署本机 AOT 应用具有以下优势: 最大程度减少磁盘占用空间:使用本机 AOT 发布时,将生成一个可执行文件,其中仅包含支持程序所需的外部依赖项的代码.减小的可执行文件大小可能会 ...
- Modbus转PROFIBUS DP 通信网关-应用案例
针对西门子S7系列的PLC,通用串口/PROFIBUS-DP网关(PM-160)为建立西门子PLC与现场RS232/485设备的连接提供了理想解决方案