摘要: 本文通过剖析 LiteOS 队列模块的源码,把握队列应用上的差别。
本文分享自华为云社区《LiteOS 内核源码剖析系列十 音讯队列 Queue》,原文作者:zhushy。
队列(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/Lite…
接下来,咱们看下队列的构造体,队列初始化,队列罕用操作的源代码。
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/Lite…。为了更容易找到 LiteOS 代码仓,倡议拜访 https://gitee.com/LiteOS/LiteOS,关注 Watch、点赞 Star、并 Fork 到本人账户下,谢谢。
点击关注,第一工夫理解华为云陈腐技术~