写点什么

解析鸿蒙内核消息队列 QueueMail 接口的哼哈二将

发布于: 56 分钟前

摘要:本文带领大家一起剖析了鸿蒙轻内核的队列模块的 QueueMail 两个接口的源代码。


本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十三(续) 消息队列QueueMail接口》,作者:zhushy 。

 

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


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

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


我们可以使用函数 UINT32OsQueueMailFree(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 两个接口的源代码。感谢阅读,如有任何问题、建议,都可以留言给我,谢谢。


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

发布于: 56 分钟前阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
解析鸿蒙内核消息队列QueueMail接口的哼哈二将