鸿蒙轻内核M核源码分析系列十三(续) 消息队列QueueMail接口 原创 精华

zhushangyuan_
发布于 2021-9-16 21:01
浏览
6收藏

鸿蒙轻内核M核源码分析系列十三(续) 消息队列QueueMail接口

之前分析过队列(Queue)的源代码,了解了队列初始化、队列创建、删除、队列读取写入等操作。队列还提供了两个接口OsQueueMailAllocOsQueueMailFree。队列可以和一个静态内存池关联起来,一个任务从静态内存池申请内存块时,如果申请不到,会把该任务插入到队列的内存阻塞链表中,等有其他任务释放内存时,该任务会被分配内存块。


接下来,详细看下这2个接口的源代码。

1、队列结构体定义

1.1 队列结构体定义

我们回忆下队列结构体的定义,在文件kernel\include\los_queue.h中定义队列控制块结构体为LosQueueCB,结构体源代码如下。需要看下成员变量memList,当任务从和队列关联的静态内存池中申请不到空闲内存块时,会把任务插入memList内存阻塞链表,然后调度,进行任务切换。等有其他任务释放空闲内存块到这个静态内存池时,该任务申请到空闲内存块,并把任务从memList内存阻塞链表移除,插入到任务就绪队列,并触发任务调度。

typedef struct {
    UINT8 *queue;      /**< 队列内存空间的指针 */
    UINT16 queueState; /**< 队列的使用状态 */
    UINT16 queueLen;   /**< 队列长度,即消息数量 */
    UINT16 queueSize;  /**< 消息节点大小 */
    UINT16 queueID;    /**< 队列编号  */
    UINT16 queueHead;  /**< 消息头节点位置 */
    UINT16 queueTail;  /**< 消息尾节点位置 */
    UINT16 readWriteableCnt[OS_READWRITE_LEN]; /**< 2维数组,可读、可写的消息数量, 0:可读, 1:可写 */
    LOS_DL_LIST readWriteList[OS_READWRITE_LEN]; /**< 2维双向链表数组,阻塞读、写任务的双向链表, 0:读链表, 1:写链表 */
    LOS_DL_LIST memList; /**< 内存节点双向链表 */
} LosQueueCB;

2、QueueMail接口源码分析

2.1 OsQueueMailAlloc接口

我们可以使用函数VOID *OsQueueMailAlloc(UINT32 queueID, VOID *mailPool, UINT32 timeOut)从和队列关联的静态内存池中申请空闲内存,下面通过分析源码看看如何申请内存。该函数需要3个参数,queueID是一个在使用状态的队列的编号,*mailPool是和队列关联的静态内存池地址,timeOut是超时时间,取值[0,LOS_WAIT_FOREVER]。该接口函数返回申请到的内存地址或者NULL。

⑴处开始对参数进行校验,⑵处根据队列编号获取队列控制结构体queueCB,然后校验该队列是否为使用状态。⑶处调用静态内存分配函数LOS_MemboxAlloc获取空闲内存块,然后获取的内存地址不为NULL,返回该内存块地址,否则执行后续代码。⑷处获取当前运行的任务控制结构体,⑸处把当前任务加入队列的内存阻塞链表queueCB->memList,然后触发任务调度。

等有其他其他任务调用OsQueueMailFree释放内存后,上述阻塞的任务获得内存块,或者因超时退出阻塞列表并调度运行后,会开始执行⑹处语句。⑺处表示因为超时返回,任务没有获取到内存块,跳转到END标签,返回NULL内存地址。⑻处表示获取到内存块,把任务的msg置空,并返回获取到的内存块的地址。

LITE_OS_SEC_TEXT VOID *OsQueueMailAlloc(UINT32 queueID, VOID *mailPool, UINT32 timeOut)
{
    VOID *mem = (VOID *)NULL;
    UINT32 intSave;
    LosQueueCB *queueCB = (LosQueueCB *)NULL;
    LosTaskCB *runTsk = (LosTaskCB *)NULL;

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

    if (mailPool == NULL) {
        return NULL;
    }

    if (timeOut != LOS_NO_WAIT) {
        if (OS_INT_ACTIVE) {
            return NULL;
        }
    }

    intSave = LOS_IntLock();
⑵  queueCB = GET_QUEUE_HANDLE(queueID);
    if (queueCB->queueState == OS_QUEUE_UNUSED) {
        goto END;
    }

⑶  mem = LOS_MemboxAlloc(mailPool);
    if (mem == NULL) {
        if (timeOut == LOS_NO_WAIT) {
            goto END;
        }

⑷      runTsk = (LosTaskCB *)g_losTask.runTask;
⑸      OsSchedTaskWait(&queueCB->memList, timeOut);
        LOS_IntRestore(intSave);
        LOS_Schedule();

⑹      intSave = LOS_IntLock();
        if (runTsk->taskStatus & OS_TASK_STATUS_TIMEOUT) {
⑺          runTsk->taskStatus &= (~OS_TASK_STATUS_TIMEOUT);
            goto END;
        } else {
            /* When enters the current branch, means the current task already got a available membox,
             * so the runTsk->msg can not be NULL.
             */
⑻          mem = runTsk->msg;
            runTsk->msg = NULL;
        }
    }

END:
    LOS_IntRestore(intSave);
    return mem;
}

2.2 OsQueueMailFree

我们可以使用函数UINT32 OsQueueMailFree(UINT32 queueID, VOID *mailPool, VOID *mailMem)释放空闲内存到和队列关联的静态内存池中,下面通过分析源码看看如何释放内存。该函数需要3个参数,queueID是一个在使用状态的队列的编号,*mailPool是和队列关联的静态内存池地址,*mailMem表示要释放的内存块地址。该接口返回值类型为无符号整数,表示是否成功或者错误码。

⑴处开始对参数进行校验。⑵处调用静态内存释放函数LOS_MemboxFree释放空闲内存块,如果释放失败,返回错误码。⑶处根据队列编号获取队列控制结构体queueCB,然后校验该队列是否为使用状态。成功释放内存后,如果队列的内存阻塞列表不为空,有阻塞任务,则执行⑷。⑸处从阻塞列表中获取第一个任务控制结构体,然后调用接口OsSchedTaskWake把任务从阻塞列表移除,并添加到任务就绪队列。⑹处从静态内存池申请一个内存块,如果申请失败返回错误码,否则执行⑺,把申请到的内存赋值到任务控制结构体的msg成员变量,然后触发调度。

LITE_OS_SEC_TEXT UINT32 OsQueueMailFree(UINT32 queueID, VOID *mailPool, VOID *mailMem)
{
    VOID *mem = (VOID *)NULL;
    UINT32 intSave;
    LosQueueCB *queueCB = (LosQueueCB *)NULL;
    LosTaskCB *resumedTask = (LosTaskCB *)NULL;

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

    if (mailPool == NULL) {
        return LOS_ERRNO_QUEUE_MAIL_PTR_INVALID;
    }

    intSave = LOS_IntLock();

⑵  if (LOS_MemboxFree(mailPool, mailMem)) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_QUEUE_MAIL_FREE_ERROR;
    }

⑶  queueCB = GET_QUEUE_HANDLE(queueID);
    if (queueCB->queueState == OS_QUEUE_UNUSED) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_QUEUE_NOT_CREATE;
    }

⑷  if (!LOS_ListEmpty(&queueCB->memList)) {
⑸      resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->memList));
        OsSchedTaskWake(resumedTask);
⑹      mem = LOS_MemboxAlloc(mailPool);
        if (mem == NULL) {
            LOS_IntRestore(intSave);
            return LOS_ERRNO_QUEUE_NO_MEMORY;
        }
⑺      resumedTask->msg = mem;
        LOS_IntRestore(intSave);
        LOS_Schedule();
    } else {
        LOS_IntRestore(intSave);
    }
    return LOS_OK;
}

小结

本文带领大家一起剖析了鸿蒙轻内核的队列模块的QueueMail两个接口的源代码。感谢阅读,如有任何问题、建议,都可以留言给我,谢谢。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-9-17 08:59:32修改
9
收藏 6
回复
举报
4条回复
按时间正序
/
按时间倒序
mb609898e2cfb86
mb609898e2cfb86

是解读源码大佬,许久不见。

1
回复
2021-9-17 10:07:38
Anzia
Anzia

注释好评

回复
2021-9-17 22:21:00
爱吃土豆丝的打工人
爱吃土豆丝的打工人

抱拳了

回复
2021-9-18 11:25:10
zhushangyuan_
zhushangyuan_

https://www.keil.com/pack/doc/cmsis/RTOS/html/group__CMSIS__RTOS__Mail.html

 

了解下邮寄队列可以做什么?A mail queue resembles a Message Queue, but the data that is being transferred consists of memory blocks that need to be allocated (before putting data in) and freed (after taking data out). The mail queue uses a Memory Pool to create formatted memory blocks and passes pointers to these blocks in a message queue. This allows the data to stay in an allocated memory block while only a pointer is moved between the separate threads. This is an advantage over messages that can transfer only a 32-bit value or a pointer. Using the mail queue functions, you can control, send, receive, or wait for mail.

回复
2021-12-24 17:10:35
回复
    相关推荐