LiteOS内核源码分析:消息队列Queue

本文分享自华为云社区《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/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到自己账户下,谢谢。

 

点击关注,第一时间了解华为云新鲜技术~

相关推荐

鸿蒙案例上新啦 | 购物、听歌、健身:15个模板带你一键激活社交娱乐

页面层级如何组织、跨页共享状态如何同步、列表与详情及个人中心如何形成稳定闭环。鸿蒙开发实践中心将这些高频消费与互动场景沉淀为可参考的标准模板,提供从业务架构到界面实现的快捷实践入口。更多实战案例欢迎前往鸿蒙开发实践中心案例中心查看并开启云端体验。

华为云官方博客 338

鸿蒙案例上新啦 | 出门打车、到家上课:25个模板带你一屏打通日常生活

如何合理拆分页面组件、如何建立单一可信的状态源、如何让用户在每一步操作中都获得明确反馈。鸿蒙开发实践中心以标准化的应用与元服务模板为入口,将清晰的业务架构与可复用的代码结构直观呈现。不妨立即挑选一个感兴趣的方向进入云端环境,从页面布局、组件封装、状态管理到路由组织,亲手跑通你的鸿蒙实战项目。

华为云官方博客 324

LakeAgent:DWS数据仓库的第二大脑,告别 “翻译式运维”,迈入智能自治时代

和普通 SQL 改写工具不同,LakeAgent 自动做多方案代价对比,主动规避负优化陷阱:补充分区键过滤实现剪枝,扫描分区由 37 收敛至 1,执行代价从 758.28 降至 113.74;未来的数据仓库,将不止能算、能存,更能看懂自己、诊断自己、理解自己。长久以来,数仓的智能都依附于人:信号需要人解读,瓶颈需要人研判,经验困在个体里,无法随底座永续生长。CPU 高、CPU 告警、CPU 诊断、CPU 使用率过高、CPU 满。语法、错误码、计费、性能调优、最佳实践、故障处置等全场景内容。

华为云官方博客 555

如何使用RDS Supabase后端BaaS能力:文件存储(File Storage)

• product-images(公开桶,允许公共读取):策略名称"Public read for product-images",允许SELECT,目标角色anon,策略定义bucket_id = 'product-images'。上传/删除需另配INSERT/DELETE策略,详见SQL。• product-specs(私有桶,仅认证用户访问自己的文件夹):建议拆为上传、读取、删除三条策略,策略定义均为 bucket_id = 'product-specs' 且仅限本人文件夹(详见下文 SQL)。

华为云官方博客 476

如何使用RDS Supabase后端BaaS能力:实时订阅与推送(Realtime)

Realtime 把数据库的每次变更同步到客户端:表里增、删、改一行,订阅者无需刷新页面或主动请求即可秒级收到。三大能力各司其职:• Postgres Changes — 监听表的 INSERT/UPDATE/DELETE,变化即通知• Broadcast — 客户端之间直接互发,适合瞬时通知、协同编辑• Presence — 跟踪频道里“谁在线”典型场景有实时数据展示(订单、库存、监控指标即时刷新)、即时通讯(消息收发、在线名单、“对方正在输入”)、协同编辑与实时通知。

华为云官方博客 464

AI Agent如何通过MCP连接到华为云RDS Supabase应用平台

Schema元数据列出所有可访问的表(按schema分组)。Schema元数据列出表的列及元数据(类型/默认值/约束)。Schema元数据列出所有约束(PK/FK/UNIQUE/CHECK/EXCLUDE)。可按schema/表/类型过滤。Schema元数据列出所有外键关系。可按schema/表过滤。Schema元数据列出所有索引及定义和大小。可按schema/表过滤。Schema元数据列出已应用的数据库迁移记录。Schema元数据列出已安装的PostgreSQL扩展。

华为云官方博客 473

华为大数据Flink让每一 CU 算力,跑出开源 5.74 倍的性能

1. 开源Apache Flink 已成为业界流计算事实标准,但在极端重负载场景下存在性能瓶颈。2. 华为大数据Flink通过多项自研深度优化,在执行计划、算子执行、状态存储三个层面构建协同的优化体系。3. Nexmark Benchmark 测试结果:整体性能提升 5.74 倍,极端重负载场景性能领先最高 18.3 倍。4. 100% 兼容 Apache Flink 1.20,业务代码零改造,无缝迁移。

华为云官方博客 380

LakeAgent实战 | 业务分析篇从一句提问到业务分析

本文以“订单金额变化”分析为例,展示LakeAgent如何通过自然语言提问,结合DataArtsStudio的数据资产与专业技能,帮助业务人员快速定位数据、生成SQL、追踪血缘、生成报告。从问题出发,逐步实现数据发现、变化洞察、原因追溯与团队协作,最终形成可复用的分析流程与企业规则,显著提升数据分析效率与一致性。

华为云官方博客 389

华为云RDS Supabase AI应用已开放公测 :30分钟搭建AI应用后端

认证、存储、实时推送、数据库API——4个后端模块,传统开发需要数周。今天,你只需要30分钟。AI创业公司的全栈开发工程师。上周老板说"下周要上线的AI对话应用,后端你来搞"——认证鉴权、数据库接口、文件存储、实时推送,每一个模块都需要数周搭建,而市场不会等你。小张打开华为云RDS Supabase,30分钟搭好认证、数据库API、对象存储,剩下的时间全用来打磨AI交互体验。基于开源Supabase+华为云RDS PostgreSQL全托管能力,为开发者提供。

华为云官方博客 396

如何使用RDS Supabase后端BaaS能力:数据库函数(Database Function)

上一篇介绍了 Database Data API——建表即自动生成增删改查接口,解决单表读写,但聚合统计、跨表事务、字段自动维护等逻辑已超出其能力边界。本篇推进到数据库函数(Database Function):把业务逻辑封装进数据库,前端通过 .rpc() 调用,由触发器自动响应数据变更。上一篇中,小张基于 Data API 完成了产品目录的建表与基础读写。但新需求随之而来:按分类统计产品数与价格区间、上架新品同时写产品表与定价表、产品修改后自动刷新更新时间。

华为云官方博客 464

如何使用RDS Supabase后端BaaS能力:创表即生成API(Database Data API)

认证、存储、实时推送、数据库API——4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。

华为云官方博客 478

从一句话到一个能出去的链接,AI开发者空间做了什么

自行搭建的 Agent、正在使用的开源项目、团队既有的工具,都可以接入,可以通过AI创建或者手动搭建环境;华为云 AI开发者空间——面向开发者的云端一站式开发平台,做的就是把这段路接上:环境免安装,浏览器打开即为完整的 Linux 运行环境,预置的 AI 智能体开箱即用。第一个任务建议选能出结果的:做一个游戏、生成一个页面、创建一台实例,从需求走到可访问的链接,一次就能看清这个环境在做什么。沙箱是一个完整的 Linux 环境,依赖、服务、端口都是真实的,不是模拟。环境释放后,权限随之失效。

华为云官方博客 425

华为云码道配置自定义大模型,四种方式完整实操

案例介绍。

华为云官方博客 624

图数据库:实现穿透式监管的数据基座

穿透式监管标志着国企监管从“管企业”向“管资本”转型过程中的关键一跃。而图数据库正是这一跃的技术支点。从股权穿透到资金链穿透,从供应链穿透到数据穿透,华为GES图数据库正在将“穿透式监管”从理念变为可落地、可操作的实践。无论是天眼查式的全量链路查询,还是某集团财经的税务规则判断,抑或是芯片供应链式的部件风险分析,乃至通过Palantir构建决策知识图谱,其底层逻辑都是一致的——用图的力量,穿透一切形式的“隔离层”,直抵风险本质与责任核心。

华为云官方博客 664

《公共云三十问》正式收官,奔赴企业AI百场景新征程

今年6月底,公共云产业联盟重点推出《公共云三十问》专栏,以每周三问的节奏,连载十周,解读《先进公共云》白皮书,分享国计民生系列案例,旨在凝聚共识、推动落地实践。同时,专栏分析公共云在产业升级、民生福祉、科技创新、全球发展等方面的作用,梳理中国特色公共云发展路径:以普惠云拓展用云广度,以智算云提升用云深度,以自主云夯实用云厚度,以全球云打开用云跨度。这十问从专属云、行业云切入,深入政务、金融、交通、电力、钢铁、化工、油气、制造等国计民生场景,回答公共云“怎么用”。

华为云官方博客 473

开发者小技巧|IAM配置,让码道代码智能体连续30天免登录

案例介绍。

华为云官方博客 645

码道深度对接CodeArts,让需求自动落地为代码

案例介绍。

华为云官方博客 719

【公共云三十问 之三十】拥抱AI,企业如何建设面向智能体的专属云?

企业可分阶段建设覆盖开发、运行、运营及安全的一体化智能体平台——从构建一站式、高低码协同的智能体开发平台,到打造高性能、极致弹性的运行时环境,再到搭建AgentOps驱动的持续运营与运维体系,同时构建端到端、多层次的全链路安全防护体系。在数据湖建设方面,遵循“存量复用、平滑演进、AI导向、分步迭代”原则,分三阶段推进——打造AI多模数据湖核心能力、完成存算分离与统一元数据管理、实现闭环赋能全面支撑智能体规模化落地,在保护既有投资的前提下将传统数据平台转型为AI数据专属供给平台。

华为云官方博客 823

【公共云三十问 之二十九】国计民生之制造:制造云如何重塑数智时代的新质生产力?

平台打通软件交付的完整路径,覆盖需求管理、代码托管、代码检查、编译构建、制品仓库、部署、测试等关键环节,支持大模型能力融入研发全流程,实现从需求到交付的端到端管理。通过构建“研发一朵云”,方案打破地域与组织壁垒,实现跨团队协同开发,有效缩短软件交付周期,助力工业制造企业从“传统研发”走向“敏捷研发”,加速数智化转型进程。当制造云真正成为数据流通的平台、AI创新的底座,公共云的社会价值将从基础设施层向生产场景层持续延伸,为新型工业化建设和制造强国战略注入源源不断的动力,重塑数智时代的中国制造新质生产力。

华为云官方博客 789

告别重复手工迁仓,码道MCP+Skill实现仓库批量迁移

案例介绍。

华为云官方博客 790

【公共云三十问 之二十八】国计民生之油气:智算云如何叩响数智时代的“地心蓝金”?

西南油气田以“向地球深部要资源”为战略方向,联合以华为云Stack为代表的公共云服务商共同打造统一算力底座,建设西南区域数据湖与通用技术平台,构建“勘探开发工程技术、天然气生产过程管控、天然气产运储销”等三个一体化协同应用场景,支撑油气勘探、开发生产、管输储运等全业务链的数智化转型。当智算云真正成为数据流通的平台、AI创新的底座,油气田得以将算力效能、数据价值与智能技术深度融入勘探开发全链条,将“看不见的算力”转化为“摸得着的储量”,以智能化手段持续降低勘探风险、缩短发现周期、提高采收效率,确保生产安全。

华为云官方博客 613

【公共云三十问 之二十七】国计民生之化工:数智云如何重塑传统化工新范式?

在架构方面,方案构建云边协同的统一体系,涵盖AI协同、数据协同与运营协同三大维度——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现“边用边学”的智能闭环,模型平均精度提升20%以上。此外,方案提供一站式企业级工业物联网平台、数据湖与数据治理平台,覆盖工业数据分析“采、存、算、管、用”全流程,打通生产现场与管理系统间的数据壁垒,有效消除数据孤岛。层面,方案充分发挥软硬协同优势,实现计算、存储、网络与云平台的深度协同,训练线性度超95%,为大模型训练推理提供强劲算力支撑。▲化工“数智”新模式。

华为云官方博客 663

【公共云三十问 之二十六】国计民生之钢铁:数智云如何淬炼中国钢铁新力量?

同时,构建云边端协同的统一架构,支撑集团总部与各生产基地两级协同发展——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现训推一体化的资源调度与高效协同,为钢铁行业的数智化转型构建了坚实的算力底座。层面,方案构建覆盖“工业数采→边缘IT/OT融合→统一数据湖→数据治理平台→数据应用”全链路的一站式数据治理与流通平台,打通生产现场与管理系统间的数据壁垒,让数据“采得到、流得动、用得好”,加速数据价值从生产现场向经营决策层流动释放。湘潭钢铁集团的钢铁数智云,是钢铁工业智能云建设的标杆案例。

华为云官方博客 526

LakeWatch——MRS 智能运维Agent

从资源池、队列、作业等多个维度,对集群CPU与内存资源进行精细化分析与对比,直观呈现不同时间段的空闲、满载等使用状态,识别资源浪费,指导治理优化,为扩缩容决策提供数据支撑。对HDFS存储进行多维度深度分析(目录、用户、用户组等),涵盖使用量、增量、文件大小分布及数据热度等指标,帮助快速识别增长较快的目录、长期未访问的文件,统计目录文件数量与小文件占比,评估HDFS总存储量与扩缩容需求,为存储治理与资源规划提供全面数据支撑。Agent主动巡检,识别异常趋势并提前预警,让运维从"事后处理"转向"事前预防"。

华为云官方博客 515

华为云专题文档发布:丰富的AI案例、选型指导和概念解读

发布 “云服务选型”、“云计算概念”专题,提供云服务选型指导,助您选择匹配业务的云服务,解读云计算相关概念,开启云上学习之路。开发者工具和文档专区上线,提供从学习、实践到反馈的全链路支持。帮助中心新增100余篇AI案例。

华为云官方博客 286

一个人,也能用AI做点“星东西”

华为云星耀创想者大赛-2026华为云OPC创意大赛等你来~

华为云官方博客 290

从需求到可执行工作项:MCP+Skill 让智能体规划真正落地

回顾整个体验,以前让智能体帮我拆需求,它是洋洋洒洒给我列了一大篇 Markdown,看着挺像那么回事,但最后还是得我一条条往 CodeArts Req 里搬,搬完再手动挂父子关系、排日期,拆需求五分钟,搬运两小时。现在好了,MCP 给智能体接上了"手",Skill 给它装上了"项目经理的脑子",一句话下去,34 个工作项连带排期、层级关系全给我建好了,甘特图一拉,齐刷刷都在两周内,节省了多少工作量啊,美滋滋。本次实操在码道代码智能体基础上,构建了一套"需求→可执行工作项"的自动化规划方案。

华为云官方博客 576

华为云码道「码力续航计划」更新!

✅上新GLM‑5.3‑Flash,高速响应,代码生成、工程解析、Agent调试高效输出。新增GLM‑5.3‑Flash福利模型,每日解锁1000万免费Tokens。● 本次活动共发放总量价值100万元Tokens,发完即止!把耗算力的编码重活,交给华为云码道代码智能体🚀🚀。● 每日额度准时刷新,当日清零、次日满血重置。✅复杂算法、大项目解析、批量代码生成随便跑。✅不用抠额度、不用算消耗,放开调试大胆迭代。✅无需额外操作,登录即送,直接切换模型使用。● 把算力交给码道,让撸码不再内耗👏🏻。

华为云官方博客 685

【公共云三十问 之二十四】国计民生之交通:交通云如何铺就数智通衢新坦途?

交通行业作为支撑实体经济的关键基础设施,涵盖公路、铁路、机场、港口、地铁等众多子领域,其中,公路网络作为综合交通运输体系的基础骨架,承担着全国超过70%的客货运量,其数字化、智能化转型的需求尤为迫切。方面,提供Agent开发框架、知识库管理、RAG工程、意图识别、静态应用编排及模型评测等能力,支持客户将行业知识、业务流程与AI能力深度融合,快速构建面向收费稽核、路网调度、应急处置等场景的智能应用。开发并实现落地应用,全面覆盖建设、管理、养护、运营、服务、安全、办公七大业务领域。

华为云官方博客 461

【公共云三十问 之二十五】国计民生之电力:电力云如何点亮数智电力新引擎?

方案构建了覆盖数据获取、质量评估到标准化处理的全链路能力。基于公共云服务商智算底座及气象大模型,通过时空图神经网络重新建模产流机制,中国华电建成超50TB多源融合数据集,突破时空建模与机理融合等五大关键技术,短期径流预测精度较传统机理模型提升5个百分点以上,实现15天中期径流预测精度超85%,推理效率达秒级。基于气象大模型的基础架构与预训练能力,客户可直接在大模型之上开展增量训练与微调,充分利用场站所在区域的高精度历史数据(如3公里、1公里分辨率),针对不同场站和区域的地形地貌与气候特征进行模型调优。

华为云官方博客 381
上一篇: 安全开发Java:日志注入,并没那么简单
下一篇: AUC/ROC:面试中80%都会问的知识点
华为云开发者联盟
华为云开发者联盟 企业官方账号 企业官方账号
博客等级 码龄10年 72万+粉丝 5985原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值