class="hide-preCode-box">

3、队列常用操作

3.1 队列创建

创建队列函数是LOS_QueueCreate(),先看看该函数的参数:queueName是队列名称,实际上并没有使用。len是队列中消息的数量,queueID是队列编号,flags保留未使用。maxMsgSize是队列中每条消息的最大大小。

我们分析下创建队列的代码。⑴处对参数进行校验,队列编码不能为空,队列消息长度不能太大,队列消息数量和队列消息大小不能为0。⑵处计算消息的实际最大大小msgSize,即maxMsgSize + sizeof(UINT32)消息最大大小再加4个字节,在消息的最后4个字节用来保存消息的实际长度。然后调用⑶处函数LOS_MemAlloc()为对队列动态申请内存,如果内存申请失败,则返回错误码。

⑷处判断g_freeQueueList是否为空,如果没有可以使用的队列,释放前文申请的内存。⑸处如果g_freeQueueList不为空,则获取第一个可用的队列节点,接着从双向链表g_freeQueueList中删除,然后调用宏GET_QUEUE_LIST获取LosQueueCB *queueCB,初始化创建的队列信息,包含队列的长度.queueLen、消息大小.queueSize,队列内存空间.queue,消息状态.queueState,可读的数量.readWriteableCnt[OS_QUEUE_READ]为0,可写的数量readWriteableCnt[OS_QUEUE_WRITE]为队列消息长度len,队列头位置.queueHead和尾位置.queueTail为0。

⑹初始化双向链表.readWriteList[OS_QUEUE_READ],阻塞在这个队列上的读消息任务会挂在这个链表上。初始化双向链表.readWriteList[OS_QUEUE_WRITE],阻塞在这个队列上的写消息任务会挂在这个链表上。初始化双向链表.memList。⑺赋值给输出参数*queueID,后续程序使用这个队列编号对队列进行其他操作。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName,
                                             UINT16 len,
                                             UINT32 *queueID,
                                             UINT32 flags,
                                             UINT16 maxMsgSize)
{
    LosQueueCB *queueCB = NULL;
    UINT32 intSave;
    LOS_DL_LIST *unusedQueue = NULL;
    UINT8 *queue = NULL;
    UINT16 msgSize;

    (VOID)queueName;
    (VOID)flags;

⑴  if (queueID == NULL) {
        return LOS_ERRNO_QUEUE_CREAT_PTR_NULL;
    }

    if (maxMsgSize > (OS_NULL_SHORT - sizeof(UINT32))) {
        return LOS_ERRNO_QUEUE_SIZE_TOO_BIG;
    }

    if ((len == 0) || (maxMsgSize == 0)) {
        return LOS_ERRNO_QUEUE_PARA_ISZERO;
    }
⑵  msgSize = maxMsgSize + sizeof(UINT32);

    /* Memory allocation is time-consuming, to shorten the time of disable interrupt,
       move the memory allocation to here. */
⑶  queue = (UINT8 *)LOS_MemAlloc(m_aucSysMem0, len * msgSize);
    if (queue == NULL) {
        return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;
    }

    intSave = LOS_IntLock();
⑷  if (LOS_ListEmpty(&g_freeQueueList)) {
        LOS_IntRestore(intSave);
        (VOID)LOS_MemFree(m_aucSysMem0, queue);
        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->queue = queue;
    queueCB->queueState = OS_QUEUE_INUSED;
    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);
    LOS_IntRestore(intSave);

⑺  *queueID = queueCB->queueID;

    OsHookCall(LOS_HOOK_TYPE_QUEUE_CREATE, queueCB);

    return LOS_OK;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

3.2 队列删除

我们可以使用函数LOS_QueueDelete(UINT32 queueID)来删除队列,下面通过分析源码看看如何删除队列的。

⑴处判断队列queueID是否超过LOSCFG_BASE_IPC_QUEUE_LIMIT,如果超过则返回错误码。如果队列编号没有问题,获取队列控制块LosQueueCB *queueCB。⑵处判断要删除的队列处于未使用状态,则跳转到错误标签QUEUE_END进行处理。⑶如果队列的阻塞读、阻塞写任务列表不为空,或内存节点链表不为空,则不允许删除,跳转到错误标签进行处理。⑷处检验队列的可读、可写数量是否出错。

⑸处使用指针UINT8 *queue保存队列的内存空间,⑹处把.queue置空,把.queueState设置为未使用OS_QUEUE_UNUSED,并把队列节点插入未使用队列双向链表g_freeQueueList。接下来会需要调用⑺处函数LOS_MemFree()释放队列内存空间。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueDelete(UINT32 queueID)
{
    LosQueueCB *queueCB = NULL;
    UINT8 *queue = NULL;
    UINT32 intSave;
    UINT32 ret;

⑴  if (queueID >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {
        return LOS_ERRNO_QUEUE_NOT_FOUND;
    }

    intSave = LOS_IntLock();
    queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);
⑵  if (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->queue;
⑹  queueCB->queue = (UINT8 *)NULL;
    queueCB->queueState = OS_QUEUE_UNUSED;
    LOS_ListAdd(&g_freeQueueList, &queueCB->readWriteList[OS_QUEUE_WRITE]);
    LOS_IntRestore(intSave);

    OsHookCall(LOS_HOOK_TYPE_QUEUE_DELETE, queueCB);

⑺  ret = LOS_MemFree(m_aucSysMem0, (VOID *)queue);
    return ret;

QUEUE_END:
    LOS_IntRestore(intSave);
    return ret;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

下面就来看看队列的读写,有2点需要注意:

只支持队首读取,不能队尾读取,否则就不算队列了。除了正常的队尾写消息外,还提供插队机制,支持从队首写入。

往队列中写入的消息的类型有2种,即支持按地址写入和按值写入(带拷贝)。按哪种类型写入,就需要配对的按相应的类型去读取。

队列读取接口的类别,归纳如下:

class="table-box">
读写接口类别接口名称描述
读队列/队尾写队列LOS_QueueRead、 LOS_QueueWrite从指定队列头节点读、往队列尾节点写入。队列消息数据为内存地址,传引用
读队列/队尾写队列,带拷贝LOS_QueueReadCopy、LOS_QueueWriteCopy从指定队列头节点读、往队列尾节点写入。队列消息数据为数据值,传数值
读队列/队首写队列LOS_QueueRead、LOS_QueueWriteHead从指定队列头节点读、往队列头节点写入。队列消息数据为内存地址,传引用
读队列/队首写队列,带拷贝LOS_QueueReadCopy、LOS_QueueWriteHeadCopy从指定队列头节点读、往队列头节点写入。队列消息数据为数据值,传数值

3.3 队列读取

我们知道有2个队列读取方法,按指针地址读取的函数LOS_QueueRead()和按消息数值读取的函数LOS_QueueReadCopy()。我们先看下函数LOS_QueueRead(),该函数的参数有4个,队列编号queueID,存放读取到的消息的缓冲区地址*bufferAddr,存放读取到的消息的缓冲区大小bufferSize,读队列消息的等待超时时间timeOut。代码如下,我们分析下代码。

⑴处校验传入参数,队列编号不能超出限制,传入的指针不能为空,缓冲大小不能为0。如果timeout不为零,不能在中断中读取队列。⑵处操作类型表示队首读取消息指针,然后调用函数OsQueueOperate()进一步操作队列。

LITE_OS_SEC_TEXT UINT32 LOS_QueueRead(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, OS_QUEUE_POINT);

    OsHookCall(LOS_HOOK_TYPE_QUEUE_READ, (LosQueueCB *)GET_QUEUE_HANDLE(queueID));

    return OsQueueOperate(queueID, operateType, bufferAddr, &bufferSize, timeOut);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

我们进一步分析下函数OsQueueOperate(),这是是比较通用的封装,读取,写入都会调用这个函数,我们以读取队列为例分析这个函数。⑴处获取队列的操作类型,为读取操作。⑵处先调用函数OsQueueOperateParamCheck()进行参数校验,校验队列是使用中的队列,并对读写消息大小进行校验。⑶处如果可读数量为0,无法读取时,如果是零等待则返回错误码。如果当前锁任务调度,跳出函数执行。否则,执行⑷把当前任务放入队列的读取消息阻塞队列,然后触发任务调度,后续的代码暂时不再执行。如果可读的数量不为0,可以继续读取时,执行⑹处代码把可读数量减1,然后继续执行⑺处代码读取队列。

等读取队列阻塞超时,或者队列可以读取后,继续执行⑸处的代码。如果是发生超时,队列还不能读取,更改任务状态,跳出函数执行。如果队列可以读取了,继续执行⑺处代码读取队列。⑻处在成功读取队列后,如果有任务阻塞在写入队列,则获取阻塞链表中的第一个任务resumedTask,然后调用唤醒函数OsSchedTaskWake()把待恢复的任务放入就绪队列,触发一次任务调度。如果无阻塞任务,则把可写入的数量加1。

UINT32 OsQueueOperate(UINT32 queueID, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeOut)
{
    LosQueueCB *queueCB = NULL;
    LosTaskCB *resumedTask = NULL;
    UINT32 ret;
⑴  UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);
    UINT32 readWriteTmp = !readWrite;

    UINT32 intSave = LOS_IntLock();

    queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);
⑵  ret = OsQueueOperateParamCheck(queueCB, 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 (g_losTaskLock) {
            ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK;
            goto QUEUE_END;
        }

        LosTaskCB *runTsk = (LosTaskCB *)g_losTask.runTask;
⑷      OsSchedTaskWait(&queueCB->readWriteList[readWrite], timeOut);
        LOS_IntRestore(intSave);
        LOS_Schedule();

        intSave = LOS_IntLock();
⑸      if (runTsk->taskStatus & OS_TASK_STATUS_TIMEOUT) {
            runTsk->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[readWriteTmp])) {
        resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[readWriteTmp]));
        OsSchedTaskWake(resumedTask);
        LOS_IntRestore(intSave);
        LOS_Schedule();
        return LOS_OK;
    } else {
⑼      queueCB->readWriteableCnt[readWriteTmp]++;
    }

QUEUE_END:
    LOS_IntRestore(intSave);
    return ret;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

我们再继续看下函数OsQueueBufferOperate()是具体如何读取队列的。⑴处switch-case语句根据操作类型获取操作位置。对于⑵头部读取的情况,先获取读取位置queuePosition。然后,如果当前头节点位置.queueHead加1等于队列消息长度,头节点位置.queueHead设置为0,否则加1。对于⑶头部写入的情况,如果当前头节点位置.queueHead等于0,头节点位置.queueHead设置为队列消息长度减1即queueCB->queueLen - 1,否则头节点位置.queueHead减1即可。然后,获取要写入的位置queuePosition。对于⑷尾部写入的情况,先获取写入位置queuePosition。然后,如果当前尾节点位置.queueTail加1等于队列消息长度,尾节点位置.queueTail设置为0,否则加1。

⑸处基于获取的队列读取位置获取队列消息节点queueNode。⑹处判断操作类型如果是按指针读写消息,直接读取消息节点的数据写入指针对应的缓冲区*(UINT32 *)bufferAddr,或直接把指针对应的缓冲区*(UINT32 *)bufferAddr数据写入消息节点即可。我们接着看如何按数数据读写消息,⑺处代码用于读取数据消息。每个消息节点的后4个字节保存的是消息的长度,首先获取消息的长度msgDataSize,然后把消息内容读取到bufferAddr。再看看⑻处如何写入队列消息,首先把消息内容写入到queueNode,然后再把消息长度的内容写入到queueNode + queueCB->queueSize - sizeof(UINT32),就是每个消息节点的后4字节。

static INLINE VOID OsQueueBufferOperate(LosQueueCB *queueCB, UINT32 operateType,
                                                                VOID *bufferAddr, UINT32 *bufferSize)
{
    UINT8 *queueNode = NULL;
    UINT32 msgDataSize;
    UINT16 queuePosion;
    errno_t rc;

    /* get the queue position */
⑴  switch (OS_QUEUE_OPERATE_GET(operateType)) {
        case OS_QUEUE_READ_HEAD:
⑵          queuePosion = 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);
            queuePosion = queueCB->queueHead;
            break;

        case OS_QUEUE_WRITE_TAIL:
⑷          queuePosion = queueCB->queueTail;
            ((queueCB->queueTail + 1) == queueCB->queueLen) ? (queueCB->queueTail = 0) : (queueCB->queueTail++);
            break;

        default:
            PRINT_ERR("invalid queue operate type!\n");
            return;
    }

⑸  queueNode = &(queueCB->queue[(queuePosion * (queueCB->queueSize))]);

⑹  if (OS_QUEUE_IS_POINT(operateType)) {
      if (OS_QUEUE_IS_READ(operateType)) {
            *(UINT32 *)bufferAddr = *(UINT32 *)(VOID *)queueNode;
        } else {
            *(UINT32 *)(VOID *)queueNode = *(UINT32 *)bufferAddr;  // change to pp when calling OsQueueOperate
        }
    } else {
⑺      if (OS_QUEUE_IS_READ(operateType)) {
            msgDataSize = *((UINT32 *)(UINTPTR)((queueNode + queueCB->queueSize) - sizeof(UINT32)));
            rc = memcpy_s((VOID *)bufferAddr, *bufferSize, (VOID *)queueNode, msgDataSize);
            if (rc != EOK) {
                PRINT_ERR("%s[%d] memcpy failed, error type = %u\n", __FUNCTION__, __LINE__, rc);
                return;
            }

            *bufferSize = msgDataSize;
        } else {
⑻          *((UINT32 *)(UINTPTR)((queueNode + queueCB->queueSize) - sizeof(UINT32))) = *bufferSize;
            rc = memcpy_s((VOID *)queueNode, queueCB->queueSize, (VOID *)bufferAddr, *bufferSize);
            if (rc != EOK) {
                PRINT_ERR("%s[%d] memcpy failed, error type = %u\n", __FUNCTION__, __LINE__, rc);
                return;
            }
        }
    }
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

3.4 队列写入

我们知道,有4个队列写入方法,2个队尾写入,2个队首写入,分别包含按指针地址写入消息和按数值写入消息。LOS_QueueWrite()会调用LOS_QueueWriteCopy()LOS_QueueWriteHead()会调用LOS_QueueWriteHeadCopy(),然后指定不同的操作类型后,会进一步调用前文已经分析过的函数OsQueueOperate()


小结

本文带领大家一起剖析了鸿蒙轻内核的队列模块的源代码,包含队列的结构体、队列池初始化、队列创建删除、读写消息等。

如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

系统架构分析:https://qr18.cn/CgxrRy

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/maniuT/article/details/139510043","extend1":"pc","ab":"new"}">> id="blogExtensionBox" style="width:400px;margin:auto;margin-top:12px" class="blog-extension-box"> class="blog_extension blog_extension_type2" id="blog_extension"> class="extension_official" data-report-click="{"spm":"1001.2101.3001.6471"}" data-report-view="{"spm":"1001.2101.3001.6471"}"> class="blog_extension_card_left"> class="blog_extension_card_cont"> 鸿蒙开发学习资料领取!!! class="blog_extension_card_cont_r"> 微信名片
注:本文转载自blog.csdn.net的沧海一笑-dj的文章"https://blog.csdn.net/dengjin20104042056/article/details/98251770"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接

评论记录:

未查询到任何数据!