鸿蒙轻内核M核源码分析系列十三 消息队列Queue

往期知识点记录:

  • 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
  • 轻内核M核源码分析系列一 数据结构-双向循环链表
  • 轻内核M核源码分析系列二 数据结构-任务就绪队列
  • 鸿蒙轻内核M核源码分析系列三 数据结构-任务排序链表
  • 轻内核M核源码分析系列四 中断Hwi
  • 轻内核M核源码分析系列五 时间管理
  • 轻内核M核源码分析系列六 任务及任务调度(1)任务栈
  • 轻内核M核源码分析系列六 任务及任务调度(2)任务模块
  • 轻内核M核源码分析系列六 任务及任务调度(3)任务调度模块
  • 轻内核M核源码分析系列七 动态内存Dynamic Memory
  • 轻内核M核源码分析系列八 静态内存MemoryBox
  • 轻内核M核源码分析系列九 互斥锁Mutex
  • 轻内核M核源码分析系列十 软件定时器Swtmr
  • 轻内核M核源码分析系列十一 (1)信号量Semaphore
  • 轻内核M核源码分析系列十一 (2)信号量Semaphore
  • 轻内核M核源码分析系列十二 事件Event
  • 轻内核M核源码分析系列十三 消息队列Queue
  • 轻内核M核源码分析系列十四 软件定时器Swtmr
  • 轻内核M核源码分析系列十五 CPU使用率CPUP
  • 轻内核M核源码分析系列十六 MPU内存保护单元
  • 轻内核M核源码分析系列十七(1) 异常钩子函数类型介绍
  • 轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作
  • 轻内核M核源码分析系列十七(3) 异常信息ExcInfo
  • 轻内核M核源码分析系列十八 Fault异常处理
  • 轻内核M核源码分析系列十九 Musl LibC
  • 轻内核M核源码分析系列二十 Newlib C
  • 持续更新中……

队列(Queue)是一种常用于任务间通信的数据结构。任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。

本文通过分析鸿蒙轻内核队列模块的源码,掌握队列使用上的差异。本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。


接下来,我们看下队列的结构体,队列初始化,队列常用操作的源代码。

1、队列结构体定义和常用宏定义

1.1 队列结构体定义

在文件kernel\include\los_queue.h中定义队列控制块结构体为LosQueueCB,结构体源代码如下。队列状态.queueState取值OS_QUEUE_UNUSEDOS_QUEUE_INUSED,其他结构体成员见注释部分。

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;

1.2 队列常用宏定义

系统支持创建多少队列是根据开发板情况使用宏LOSCFG_BASE_IPC_QUEUE_LIMIT定义的,每一个队列queueIDqueueID类型的,取值为[0,LOSCFG_BASE_IPC_QUEUE_LIMIT),表示队列池中各个队列的编号。

⑴处的宏从队列池中获取指定队列编号QueueID对应的队列控制块。⑵处根据双向链表节点readWriteList[OS_QUEUE_WRITE]获取队列控制块内存地址。

⑴    #define GET_QUEUE_HANDLE(QueueID) (((LosQueueCB *)g_allQueue) + (QueueID))⑵    #define GET_QUEUE_LIST(ptr) LOS_DL_LIST_ENTRY(ptr, LosQueueCB, readWriteList[OS_QUEUE_WRITE])

另外,队列中还提供了比较重要的队列读取消息操作相关的枚举和宏。枚举QueueReadWrite区分队列的读和写,枚举QueueHeadTail区分队列的首和尾,枚举QueuePointOrNot区分读写消息时是使用值还是指针。

队列的操作类型使用3比特位的数字来表示,见宏OS_QUEUE_OPERATE_TYPE的定义,其中高1位表示读写数值还是读写指针地址,中1位表示队首还是队尾,低1位表示读取还是写入。枚举和宏的定义如下:

typedef enum {OS_QUEUE_READ,OS_QUEUE_WRITE
} QueueReadWrite;typedef enum {OS_QUEUE_HEAD,OS_QUEUE_TAIL
} QueueHeadTail;typedef enum {OS_QUEUE_NOT_POINT,OS_QUEUE_POINT
} QueuePointOrNot;#define OS_QUEUE_OPERATE_TYPE(ReadOrWrite, HeadOrTail, PointOrNot)  \(((UINT32)(PointOrNot) << 2) | ((UINT32)(HeadOrTail) << 1) | (ReadOrWrite))
#define OS_QUEUE_READ_WRITE_GET(type) ((type) & (0x01))
#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) & (0x03))
#define OS_QUEUE_IS_POINT(type)    ((type) & (0x04))
#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)
#define OS_READWRITE_LEN           2

2、队列初始化

队列在内核中默认开启,用户可以通过宏LOSCFG_BASE_IPC_QUEUE进行关闭。开启队列的情况下,在系统启动时,在kernel\src\los_init.c中调用OsQueueInit()进行队列模块初始化。下面,我们分析下队列初始化的代码。

⑴为队列申请内存,如果申请失败,则返回错误。⑵初始化双向循环链表g_freeQueueList,维护未使用的队列。⑶循环每一个队列进行初始化,为每一个队列节点指定索引queueID,并把队列节点插入未使用队列双向链表g_freeQueueList。代码上可以看出,挂在未使用队列双向链表上的节点是每个队列控制块的写阻塞任务链表节点.readWriteList[OS_QUEUE_WRITE]

LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)
{LosQueueCB *queueNode = NULL;UINT16 index;if (LOSCFG_BASE_IPC_QUEUE_LIMIT == 0) {return LOS_ERRNO_QUEUE_MAXNUM_ZERO;}⑴  g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB));if (g_allQueue == NULL) {return LOS_ERRNO_QUEUE_NO_MEMORY;}(VOID)memset_s(g_allQueue, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB),0, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB));⑵  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]);}return LOS_OK;
}

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;
}

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;
}

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

  • 队首、队尾的读写

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

  • 队列消息数据内容

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

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

读写接口类别接口名称描述
读队列/队尾写队列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);
}

我们进一步分析下函数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;
}

我们再继续看下函数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;}}}
}

3.4 队列写入

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


小结

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

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview

在这里插入图片描述

OpenHarmony 开发环境搭建

图片

《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/878818.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity TMP (TextMeshPro) 更新中文字符集

TMP更新中文字符集 1 字符集缺失说明2 字体的字符表2.1 字符表更新模式&#xff1a;动态2.2 字符表更新模式&#xff1a;静态 3 更新字符集步骤3.1 打开纹理更新面板3.1 导入文本文件3.3 关于警告处理 4 修改TMP默认字体设置 1 字符集缺失说明 使用TMP显示中文需要用到中文字体…

数据预处理与协同过滤推荐算法——从数据清洗到个性化电影推荐

推荐系统在现代应用中占据了重要地位&#xff0c;尤其在电影、音乐等个性化内容推荐中广泛使用。本文将介绍如何使用数据预处理、特征工程以及多种推荐算法&#xff08;包括协同过滤、基于内容的推荐、混合推荐等&#xff09;来实现电影推荐系统。通过Pandas、Scikit-learn、Te…

etcdctl defrag 剔除、添加etcd节点

零、准备工作 find / -name etcdctl cp /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/12/fs/usr/local/bin/etcdctl /usr/local/bin/etcdctlalias ec"etcdctl --endpointshttps://127.0.0.1:2379 --cacert /etc/kubernetes/pki/etcd/ca.crt --…

利用正则表达式从字符串中提取浮点数

在 Python 中&#xff0c;使用正则表达式可以非常方便地从字符串中提取浮点数。Python 的 re 模块提供了正则表达式支持。下面是如何使用正则表达式提取浮点数的示例。 1、问题背景 在开发过程中&#xff0c;有时候我们需要从字符串中提取浮点数&#xff0c;例如从 HTML 代码中…

网关功能介绍

在微服务架构中&#xff0c;网关&#xff08;API Gateway&#xff09;扮演着至关重要的角色&#xff0c;它作为客户端和微服务之间的中介&#xff0c;负责路由、过滤、认证、限流等职责。以下是一些常见的网关实现&#xff1a; Spring Cloud Gateway&#xff1a; Spring Cloud …

linux系统中,计算两个文件的相对路径

realpath --relative-to/home/itheima/smartnic/smartinc/blocks/ruby/seanet_diamond/tb/parser/test_parser_top /home/itheima/smartnic/smartinc/corundum/fpga/lib/eth/lib/axis/rtl/axis_fifo.v 检验方式就是直接在当前路径下&#xff0c;把输出的路径复制一份&#xff0…

5-2 检测内存容量

1 使用的是bios 中断&#xff0c; 每次进行检测都会返回一块 内容。并且标志上&#xff0c;这块内存是否可用。 接下来是代码&#xff1a; 首先是构建 一个文件夹&#xff0c; 两个文件。 types.h 的内容。 #ifndef TYPES_H #define TYPES_H// 基本整数类型&#xff0c;下面的…

全球圆柱锂电池行业领军者!比克电池亮相2024深圳eVTOL展

2024深圳eVTOL产业发展大会暨低空经济展览会将于9月23-25日在深圳坪山燕子湖国际会展中心举办。展会将通过“两天论坛三天展览”的形式展开&#xff0c;专注未来城市空中交通新形态、民用有人驾驶、无人驾驶航空器、城市低空物流&#xff0c;并讨论eVTOL的整机研发、设计、制造…

拍卖新纪元:Spring Boot赋能在线拍卖解决方案

需求分析 1.1技术可行性&#xff1a;技术背景 在线拍卖系统是在Windows操作系统中进行开发运用的&#xff0c;而且目前PC机的各项性能已经可以胜任普通网站的web服务器。系统开发所使用的技术也都是自身所具有的&#xff0c;也是当下广泛应用的技术之一。 系统的开发环境和配置…

Python+selenium web测试入门基础!

自动化测试框架 from selenium import webdriver 获取浏览器对象 我这里是edge浏览器&#xff0c;用的是edge的webdriver # webdriver获取浏览器对象  driver webdriver.Edge() 尝试打开网站并关闭​​​​​​​ # 准备一个网址  url "https://www.baidu.com/"…

包拯断案 | 数据库从库GTID在变化 为何没有数据写入@还故障一个真相

提问&#xff1a;作为DBA运维的你是否遇到过这些烦恼 1、数据库从库复制链路如何正确配置表过滤信息&#xff1f; 2、数据库从库的GTID在变化&#xff0c;实际却没有数据写入&#xff0c;究竟是什么原因&#xff1f; 心中有章&#xff0c;遇事不慌 作为DBA的你&#xff0c;…

如何构建短视频矩阵?云微客开启多账号协同作战

你有没有疑惑过&#xff0c;为什么有些账号每一次发布视频&#xff0c;都要艾特一下其他账号呢&#xff1f;那些被艾特的账号&#xff0c;你有点进去关注过吗&#xff1f;其实做过运营的都或多或少的接触过矩阵&#xff0c;短视频矩阵的玩法现在也逐步成为了趋势。企业通过多账…

pyautogui进行点击失效,pyautogui.click()失效

背景&#xff1a;在Pycharm里&#xff0c;使用pythonpyautogui调用 .exe程序文件时候&#xff0c;当程序界面出来之后&#xff0c;鼠标失去反应&#xff0c;用pyautogui进行点击。后面尝试使用图片相似也无法实行点击。 解决方法&#xff1a;运行Pycharm或者其他ide的时候选择…

黑马点评2——商户查询缓存(P37店铺类型查询业务添加缓存练习题答案)redis缓存、更新、穿透、雪崩、击穿、工具封装

文章目录 什么是缓存&#xff1f;添加Redis缓存店铺类型查询业务添加缓存练习题 缓存更新策略给查询商铺的缓存添加超时剔除和主动更新的策略 缓存穿透缓存空对象布隆过滤 缓存雪崩解决方案 缓存击穿解决方案基于互斥锁方式解决缓存击穿问题基于逻辑过期的方式解决缓存击穿问题…

极市开发平台yolov8训练无人机数据集样例数据流程

先进入vscode&#xff0c;进入src_repo文件夹。 第一步&#xff0c;克隆一个比较好的博主的库&#xff1a; GitHub - Incalos/YOLO-Datasets-And-Training-Methods: This project involves making custom datasets for the YOLO series and model training methods for YOLO.…

关于蓝屏查看日志分析原因

一、前提 虽然电脑经常蓝屏&#xff0c;或者发生了蓝屏现象&#xff0c;但是仍然可以进入系统&#xff0c;并且可以进行桌面操作。 二、查看蓝屏日志 1.按下win键&#xff0c;搜索计算机管理。 2.依次点击&#xff1a;系统工具->事件查看器->Windows日志->系统 3.在…

【项目二】C++高性能服务器开发——日志系统(日志器,日志级别,日志事件)

知识点备忘录 其实也没啥 操作记录 在乌邦图上写的&#xff0c;先是模仿sylar创建了目录 进入sylar文件夹&#xff0c;有今天写的log.h头文件 其中log_test.cpp是为了测试log.h能否正常运行建的&#xff0c;只是个测试文件 log.h写了三个类&#xff0c;日志级别&#xff0…

PHP一站式解决方案高级房产系统小程序源码

一站式解决方案&#xff0c;高级房产系统让房产管理更轻松 &#x1f3e0;【开篇&#xff1a;告别繁琐&#xff0c;迎接高效房产管理新时代】&#x1f3e0; 你是否还在为房产管理的繁琐流程而头疼&#xff1f;从房源录入、客户咨询到合同签订、售后服务&#xff0c;每一个环节…

【2024数模国赛赛题思路公开】国赛C题第二套思路丨附可运行代码丨无偿自提

2024年国赛C题第二套解题思路 第一问&#xff1a;2024~2030年农作物的最优种植方案 【问题分析】 题目要求为某乡村在2024~2030年制定农作物的最优种植方案&#xff0c;目的是最大化收益&#xff0c;并需考虑两种销售情况&#xff1a; 1. 超过预期销售量的部分滞销&#xff0…

【LeetCode】05.最长回文子串

题目要求 解题思路 这一类型&#xff08;回文子串&#xff09;主要有两种解决方法&#xff0c;一种是动态规划&#xff0c;另一种是中心拓展算法。 动态规划&#xff1a; 本质问题就是在i-j区间是不是回文的。这样的话我们在 i 和 j 位置的值相等时&#xff0c;判断如下三种情…