鸿蒙微内核IPC数据结构

鸿蒙内核IPC数据结构

内核为任务之间的通信提供了多种机制,包含队列、事件、互斥锁、信号量等,其中还有Futex(用户态快速锁),rwLock(读写锁),signal(信号)。

队列

队列又称为消息队列,是一种常用于任务间通信的数据结构,可以在任务间传递消息内容或消息的地址。内核使用队列控制块来管理消息队列,同时又使用双向环形链表来管理控制块。

  • 基本概念
    队列又称消息队列,是一种常用于任务间通信的数据结构。队列接收来自任务或中断的
    不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中。

    任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,
    挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,
    挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将
    读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。

    消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。

  • 队列特性
    消息以先进先出的方式排队,支持异步读写。
    读队列和写队列都支持超时机制。
    每读取一条消息,就会将该消息节点设置为空闲。
    发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息。
    一个任务能够从任意一个消息队列接收和发送消息。
    多个任务能够从同一个消息队列接收和发送消息。
    创建队列时所需的队列空间,默认支持接口内系统自行动态申请内存的方式,同时也支持将用户分配的队列空间作为接口入参传入的方式。

  • 队列运作原理
    创建队列时,创建队列成功会返回队列ID。

    在队列控制块中维护着一个消息头节点位置Head和一个消息尾节点位置Tail来,用于表示当前
    队列中消息的存储情况。Head表示队列中被占用的消息节点的起始位置。Tail表示被占用的
    消息节点的结束位置,也是空闲消息节点的起始位置。队列刚创建时,Head和Tail均指向队列起始位置。

    写队列时,根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)
    队列进行写操作。写队列支持两种写入方式:向队列尾节点写入,也可以向队列头节点写入。尾节点写入时,
    根据Tail找到起始空闲消息节点作为数据写入对象,如果Tail已经指向队列尾部则采用回卷方式。头节点写入时,
    将Head的前一个节点作为数据写入对象,如果Head指向队列起始位置则采用回卷方式。

    读队列时,根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)
    队列进行读操作会引起任务挂起。如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。
    如果Head已经指向队列尾部则采用回卷方式。

    删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态。
    如果是通过系统动态申请内存方式创建的队列,还会释放队列所占内存。

  • 使用场景
    队列用于任务间通信,可以实现消息的异步处理。同时消息的发送方和接收方不需要彼此联系,两者间是解耦的。

  • 队列错误码
    对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

双向循环列表

双向循环链表的首结点中的prev指针成员指向链表的尾结点,并且双向循环链表的尾结点里的next指针成员指向链表的首结点,所以双向循环链表也属于环形结构。
双向循环链表
在这里插入图片描述

队列控制块

管理消息队列的数据块,内核初始化的时候调用OsQueueInit()创建,并依次挂载到双向环形链表g_freeQueueList中,此时控制块状态queueState为UNUSED,队列控制块用来保存队列的状态,队列的长度,消息长度,队列ID,队列头尾位置和等待读写的任务列表,内核根据这些信息来管理消息队列和任务完成对消息读写等操作。
kernel\liteos_a\kernel\base\include\los_queue_pri.h

/*** @ingroup los_queue* Queue information block structure*/
typedef struct TagQueueCB {UINT8 *queueHandle; /**< Pointer to a queue handle */UINT16 queueState; /**< Queue state */UINT16 queueLen; /**< Queue length */UINT16 queueSize; /**< Node size */UINT32 queueID; /**< queueID */UINT16 queueHead; /**< Node head */UINT16 queueTail; /**< Node tail */UINT16 readWriteableCnt[OS_QUEUE_N_RW]; /**< Count of readable or writable resources, 0:readable, 1:writable */LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /**< the linked list to be read or written, 0:readlist, 1:writelist *///链表用于存放等待申请内存的任务LOS_DL_LIST memList; /**< Pointer to the memory linked list */
} LosQueueCB;

kernel\liteos_a\kernel\common\los_config.c

///由汇编调用,鸿蒙C语言层级的入口点 
LITE_OS_SEC_TEXT_INIT UINT32 OsMain(VOID)
{UINT32 ret;
#ifdef LOS_INIT_STATISTICSUINT64 startNsec, endNsec, durationUsec;
#endifret = EarliestInit();//鸿蒙初开,天地混沌if (ret != LOS_OK) {return ret;}OsInitCall(LOS_INIT_LEVEL_EARLIEST);......ret = OsIpcInit();//进程间通讯模块初始化if (ret != LOS_OK) {return ret;}ret = OsSystemProcessCreate();//创建系统进程 if (ret != LOS_OK) {return ret;}......return LOS_OK;
}......LITE_OS_SEC_TEXT_INIT STATIC UINT32 OsIpcInit(VOID)
{UINT32 ret;#ifdef LOSCFG_BASE_IPC_SEMret = OsSemInit();if (ret != LOS_OK) {PRINT_ERR("OsSemInit error\n");return ret;}
#endif#ifdef LOSCFG_BASE_IPC_QUEUEret = OsQueueInit();//创建队列控制块if (ret != LOS_OK) {PRINT_ERR("OsQueueInit error\n");return ret;}
#endifreturn LOS_OK;
}

kernel\liteos_a\kernel\base\ipc\los_queue.c

#ifndef LOSCFG_IPC_CONTAINER
LITE_OS_SEC_BSS LosQueueCB *g_allQueue = NULL;
LITE_OS_SEC_BSS STATIC LOS_DL_LIST g_freeQueueList;
#define FREE_QUEUE_LIST g_freeQueueList
#endif...../** Description : queue initial* Return      : LOS_OK on success or error code on failure*/
LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)
{
#ifndef LOSCFG_IPC_CONTAINERg_allQueue = OsAllQueueCBInit(&g_freeQueueList);if (g_allQueue == NULL) {return LOS_ERRNO_QUEUE_NO_MEMORY;}
#endifreturn LOS_OK;
}......LITE_OS_SEC_TEXT_INIT LosQueueCB *OsAllQueueCBInit(LOS_DL_LIST *freeQueueList)
{UINT32 index;if (freeQueueList == NULL) {return NULL;}//LOSCFG_BASE_IPC_QUEUE_LIMIT 支持的最大的队列数为1024UINT32 size = LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB);//系统驻留内存,不要释放LosQueueCB *allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, size);if (allQueue == NULL) {return NULL;}(VOID)memset_s(allQueue, size, 0, size);//初始化双向链表freeQueueListLOS_ListInit(freeQueueList);for (index = 0; index < LOSCFG_BASE_IPC_QUEUE_LIMIT; index++) {LosQueueCB *queueNode = ((LosQueueCB *)allQueue) + index;queueNode->queueID = index;//依次将队列控制块插入到环形列表g_freeQueueList中LOS_ListTailInsert(freeQueueList, &queueNode->readWriteList[OS_QUEUE_WRITE]);}#ifndef LOSCFG_IPC_CONTAINERif (OsQueueDbgInitHook() != LOS_OK) {(VOID)LOS_MemFree(m_aucSysMem0, allQueue);return NULL;}
#endifreturn allQueue;
}

自此队列控制块g_freeQueueList初始化完成,初始化后的队列控制块如下:
队列控制块
在这里插入图片描述

创建队列

队列用于存放具体的消息内容,任务调用LOS_QueueCreate创建队列,此时内核会根据入参指定的队列长度和消息大小申请内存创建队列,并从g_freeQueueList分配一个队列控制块来管理队列,分配的队列控制块状态为OS_QUEUE_INUSED,分配队列总是从头节点开始。
kernel\liteos_a\kernel\base\ipc\los_queue.c

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;}//消息队列大小不能大于 (UINT16)0xFFFF - sizeof(UINT32) = 65535 -4 = 65531// maxMsgSize上限 为啥要减去 sizeof(UINT32) ,因为前面存的是队列的大小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;}//总size = 消息体内容长度 + 消息大小(UINT32) 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_aucSysMem1, (UINT32)len * msgSize);if (queue == NULL) {return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;}SCHEDULER_LOCK(intSave);//没有空余的队列ID的处理,注意软时钟定时器是由 g_swtmrCBArray统一管理的,里面有正在使用和可分配空闲的队列//g_freeQueueList是管理可用于分配的队列链表,申请消息队列的ID需要向它要if (LOS_ListEmpty(&FREE_QUEUE_LIST)) {SCHEDULER_UNLOCK(intSave);//申请LOSCFG_BASE_IPC_QUEUE_LIMIT个QueueDebugCB进行测试,验证系统能够正常的创建队列OsQueueCheckHook();(VOID)LOS_MemFree(m_aucSysMem1, queue);return LOS_ERRNO_QUEUE_CB_UNAVAILABLE;}//获取链表中的第一个节点指针unusedQueue = LOS_DL_LIST_FIRST(&FREE_QUEUE_LIST);//将节点从链表中删除LOS_ListDelete(unusedQueue);queueCB = GET_QUEUE_LIST(unusedQueue);//通过unusedQueue找到整个消息队列(LosQueueCB)queueCB->queueLen = len;	//队列中消息的总个数,注意这个一旦创建是不能变的.queueCB->queueSize = msgSize;//消息节点的大小,注意这个一旦创建也是不能变的.queueCB->queueHandle = queue;	//队列句柄,队列内容存储区. queueCB->queueState = OS_QUEUE_INUSED;	//队列状态使用中queueCB->readWriteableCnt[OS_QUEUE_READ] = 0;//可读资源计数,OS_QUEUE_READ(0):可读.queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len;//可些资源计数 OS_QUEUE_WRITE(1):可写, 默认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;//带走队列IDOsHookCall(LOS_HOOK_TYPE_QUEUE_CREATE, queueCB);return LOS_OK;
}

如下图,队列创建成功时候,控制块0 被分配用于管理新创建的队列
创建队列
在这里插入图片描述

读写队列

内核支持两种写队列的方式:从头部写LOS_QueueWriteHead()和从尾部写;写队列只有一种方式:从队列头部读LOS_QueueRead(),读取之后head指向下个节点
kernel\liteos_a\kernel\base\ipc\los_queue.c

LITE_OS_SEC_TEXT UINT32 LOS_QueueWrite(UINT32 queueID, VOID *bufferAddr, UINT32 bufferSize, UINT32 timeout)
{if (bufferAddr == NULL) {return LOS_ERRNO_QUEUE_WRITE_PTR_NULL;}bufferSize = sizeof(CHAR *);return LOS_QueueWriteCopy(queueID, &bufferAddr, bufferSize, timeout);
}LITE_OS_SEC_TEXT UINT32 LOS_QueueWriteHead(UINT32 queueID,VOID *bufferAddr,UINT32 bufferSize,UINT32 timeout)
{if (bufferAddr == NULL) {return LOS_ERRNO_QUEUE_WRITE_PTR_NULL;}bufferSize = sizeof(CHAR *);return LOS_QueueWriteHeadCopy(queueID, &bufferAddr, bufferSize, timeout);
}//接口函数从队列头开始写
LITE_OS_SEC_TEXT UINT32 LOS_QueueWriteHeadCopy(UINT32 queueID,VOID *bufferAddr,UINT32 bufferSize,UINT32 timeout)
{UINT32 ret;UINT32 operateType;ret = OsQueueWriteParameterCheck(queueID, bufferAddr, &bufferSize, timeout);//参数检查if (ret != LOS_OK) {return ret;}operateType = OS_QUEUE_OPERATE_TYPE(OS_QUEUE_WRITE, OS_QUEUE_HEAD);//从头开始写return OsQueueOperate(queueID, operateType, bufferAddr, &bufferSize, timeout);//执行写操作
}//接口函数 从队列尾部开始写
LITE_OS_SEC_TEXT UINT32 LOS_QueueWriteCopy(UINT32 queueID,VOID *bufferAddr,UINT32 bufferSize,UINT32 timeout)
{UINT32 ret;UINT32 operateType;ret = OsQueueWriteParameterCheck(queueID, bufferAddr, &bufferSize, timeout);//参数检查if (ret != LOS_OK) {return ret;}operateType = OS_QUEUE_OPERATE_TYPE(OS_QUEUE_WRITE, OS_QUEUE_TAIL);//从尾部开始写return OsQueueOperate(queueID, operateType, bufferAddr, &bufferSize, timeout);//执行写操作
}....../*** @brief * @verbatim外部接口 读一个队列数据读队列时,根据Head找到最先写入队列中的消息节点进行读取。如果Head已经指向队列尾则采用回卷方式。根据usReadableCnt判断队列是否有消息读取,对全部空闲(usReadableCnt为0)队列进行读队列操作会引起任务挂起。* @endverbatim*/
LITE_OS_SEC_TEXT UINT32 LOS_QueueRead(UINT32 queueID, VOID *bufferAddr, UINT32 bufferSize, UINT32 timeout)
{return LOS_QueueReadCopy(queueID, bufferAddr, &bufferSize, timeout);
}///接口函数定时读取消息队列
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);//定时执行读操作
}
....../*** @brief 队列操作.是读是写由operateType定本函数是消息队列最重要的一个函数,可以分析出读取消息过程中发生的细节,涉及任务的唤醒和阻塞,阻塞链表任务的相互提醒.* @param queueID * @param operateType * @param bufferAddr * @param bufferSize * @param timeout * @return UINT32 */
UINT32 OsQueueOperate(UINT32 queueID, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeout)
{UINT32 ret;UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);//获取读/写操作标识UINT32 intSave;OsHookCall(LOS_HOOK_TYPE_QUEUE_READ, (LosQueueCB *)GET_QUEUE_HANDLE(queueID), operateType, *bufferSize, timeout);SCHEDULER_LOCK(intSave);LosQueueCB *queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);//获取对应的队列控制块ret = OsQueueOperateParamCheck(queueCB, queueID, operateType, bufferSize);//参数检查if (ret != LOS_OK) {goto QUEUE_END;}if (queueCB->readWriteableCnt[readWrite] == 0) {//根据readWriteableCnt判断队列是否有消息读/写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;}//任务等待,这里很重要啊,将自己从就绪列表摘除,让出了CPU并发起了调度,并挂在readWriteList[readWrite]上,挂的都等待读/写消息的taskLosTaskCB *runTask = OsCurrTaskGet();OsTaskWaitSetPendMask(OS_TASK_WAIT_QUEUE, queueCB->queueID, timeout);ret = runTask->ops->wait(runTask, &queueCB->readWriteList[readWrite], timeout);if (ret == LOS_ERRNO_TSK_TIMEOUT) {//唤醒后如果超时了,返回读/写消息失败ret = LOS_ERRNO_QUEUE_TIMEOUT;goto QUEUE_END;//}} else {queueCB->readWriteableCnt[readWrite]--;//对应队列中计数器--,说明一条消息只能被读/写一次}OsQueueBufferOperate(queueCB, operateType, bufferAddr, bufferSize);//发起读或写队列操作if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite])) {//如果还有任务在排着队等待读/写入消息(当时不能读/写的原因有可能当时队列满了==)LosTaskCB *resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[!readWrite]));//取出要读/写消息的任务OsTaskWakeClearPendMask(resumedTask);resumedTask->ops->wake(resumedTask);SCHEDULER_UNLOCK(intSave);LOS_MpSchedule(OS_MP_CPU_ALL);//让所有CPU发出调度申请,因为很可能那个要读/写消息的队列是由其他CPU执行LOS_Schedule();//申请调度return LOS_OK;} else {queueCB->readWriteableCnt[!readWrite]++;//对应队列读/写中计数器++}QUEUE_END:SCHEDULER_UNLOCK(intSave);return ret;
}......///读队列参数检查
STATIC LITE_OS_SEC_TEXT UINT32 OsQueueReadParameterCheck(UINT32 queueID, const VOID *bufferAddr,const UINT32 *bufferSize, UINT32 timeout)
{if (GET_QUEUE_INDEX(queueID) >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {//队列ID不能超上限return LOS_ERRNO_QUEUE_INVALID;}if ((bufferAddr == NULL) || (bufferSize == NULL)) {//缓存地址和大小参数判断return LOS_ERRNO_QUEUE_READ_PTR_NULL;}if ((*bufferSize == 0) || (*bufferSize > (OS_NULL_SHORT - sizeof(UINT32)))) {//限制了读取数据的上限64K, sizeof(UINT32)代表的是队列的长度return LOS_ERRNO_QUEUE_READSIZE_IS_INVALID;					//所以要减去}OsQueueDbgTimeUpdateHook(queueID);if (timeout != LOS_NO_WAIT) {//等待一定时间再读取if (OS_INT_ACTIVE) {//如果碰上了硬中断return LOS_ERRNO_QUEUE_READ_IN_INTERRUPT;//意思是:硬中断发生时是不能读消息队列的}}return LOS_OK;
}
///写队列参数检查
STATIC LITE_OS_SEC_TEXT UINT32 OsQueueWriteParameterCheck(UINT32 queueID, const VOID *bufferAddr,const UINT32 *bufferSize, UINT32 timeout)
{if (GET_QUEUE_INDEX(queueID) >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {//队列ID不能超上限return LOS_ERRNO_QUEUE_INVALID;}if (bufferAddr == NULL) {//没有数据源return LOS_ERRNO_QUEUE_WRITE_PTR_NULL;}if (*bufferSize == 0) {//这里没有限制写队列的大小,如果写入一个很大buf 会怎样?return LOS_ERRNO_QUEUE_WRITESIZE_ISZERO;}OsQueueDbgTimeUpdateHook(queueID);if (timeout != LOS_NO_WAIT) {if (OS_INT_ACTIVE) {return LOS_ERRNO_QUEUE_WRITE_IN_INTERRUPT;}}return LOS_OK;
}
///队列buf操作,注意队列数据是按顺序来读取的,要不从头,要不从尾部,不会出现从中间读写,所有可由 head 和 tail 来管理队列.
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;}//queueHandle是create队列时,由外界参数申请的一块内存. 用于copy 使用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) {//1.先读出队列大小,由队列头四个字节表示PRINT_ERR("get msgdatasize failed\n");return;}msgDataSize = (*bufferSize < msgDataSize) ? *bufferSize : msgDataSize;if (memcpy_s(bufferAddr, *bufferSize, queueNode, msgDataSize) != EOK) {//2.读表示读走已有数据,所以相当于bufferAddr接着了queueNode的数据PRINT_ERR("copy message to buffer failed\n");return;}*bufferSize = msgDataSize;//通过入参 带走消息的大小} else {//只有读写两种操作,这里就是写队列了.写也分两步走 , @note_thinking 这里建议鸿蒙加上 OS_QUEUE_IS_WRITE 判断 if (memcpy_s(queueNode, queueCB->queueSize, bufferAddr, *bufferSize) != EOK) {//1.写入消息内容PRINT_ERR("store message failed\n");//表示把外面数据写进来,所以相当于queueNode接着了bufferAddr的数据return;}if (memcpy_s(queueNode + queueCB->queueSize - sizeof(UINT32), sizeof(UINT32), bufferSize,sizeof(UINT32)) != EOK) {//2.写入消息数据的长度,sizeof(UINT32)PRINT_ERR("store message size failed\n");return;}}
}
///队列操作参数检查
STATIC UINT32 OsQueueOperateParamCheck(const LosQueueCB *queueCB, UINT32 queueID,UINT32 operateType, const UINT32 *bufferSize)
{if ((queueCB->queueID != queueID) || (queueCB->queueState == OS_QUEUE_UNUSED)) {//队列ID和状态判断return LOS_ERRNO_QUEUE_NOT_CREATE;}if (OS_QUEUE_IS_WRITE(operateType) && (*bufferSize > (queueCB->queueSize - sizeof(UINT32)))) {//写时判断return LOS_ERRNO_QUEUE_WRITE_SIZE_TOO_BIG;//塞进来的数据太大,大于队列节点能承受的范围}return LOS_OK;
}

如下图,为读写队列操作
读队列
在这里插入图片描述

写队列
在这里插入图片描述

删除队列

当队列不再使用了,可以使用LOS_QueueDelete()来删除队列,此时会归还控制块到g_freeQueueList中,并释放消息队列。

/*** @brief * @verbatim外部接口 删除队列,还有任务要读/写消息时不能删除删除队列时,根据传入的队列ID寻找到对应的队列,把队列状态置为未使用,释放原队列所占的空间,对应的队列控制头置为初始状态。* @endverbatim*/
LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueDelete(UINT32 queueID)
{LosQueueCB *queueCB = NULL;UINT8 *queue = NULL;UINT32 intSave;UINT32 ret;//对应的节点index大于1024if (GET_QUEUE_INDEX(queueID) >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {return LOS_ERRNO_QUEUE_NOT_FOUND;}SCHEDULER_LOCK(intSave);queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);//拿到队列实体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;	//队列bufqueueCB->queueHandle = NULL;	//queueCB->queueState = OS_QUEUE_UNUSED;//重置队列状态queueCB->queueID = SET_QUEUE_ID(GET_QUEUE_COUNT(queueCB->queueID) + 1, GET_QUEUE_INDEX(queueCB->queueID));//@note_why 这里需要这样做吗?OsQueueDbgUpdateHook(queueCB->queueID, NULL);LOS_ListTailInsert(&FREE_QUEUE_LIST, &queueCB->readWriteList[OS_QUEUE_WRITE]);//回收,将节点挂入可分配链表,等待重新被分配再利用SCHEDULER_UNLOCK(intSave);									OsHookCall(LOS_HOOK_TYPE_QUEUE_DELETE, queueCB);ret = LOS_MemFree(m_aucSysMem1, (VOID *)queue);//释放队列句柄return ret;QUEUE_END:SCHEDULER_UNLOCK(intSave);return ret;
}

删除队列操作如下图:
删除队列
在这里插入图片描述

事件

事件(Event)是一种任务间通信的机制,可用于任务间的同步。
多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。
一对多同步模型:一个任务等待多个事件的触发。可以是任意一个事件发生时唤醒任务处理事件,也可以是几个事件都发生后才唤醒任务处理事件。
多对多同步模型:多个任务等待多个事件的触发。

  • 事件特点
    任务通过创建事件控制块来触发事件或等待事件。
    事件间相互独立,内部实现为一个32位无符号整型,每一位标识一种事件类型。第25位不可用,因此最多可支持31种事件类型。
    事件仅用于任务间的同步,不提供数据传输功能。
    多次向事件控制块写入同一事件类型,在被清零前等效于只写入一次。
    多个任务可以对同一事件进行读写操作。
    支持事件读写超时机制。

  • 事件读取模式
    在读事件时,可以选择读取模式。读取模式如下:
    所有事件(LOS_WAITMODE_AND):逻辑与,基于接口传入的事件类型掩码eventMask,
    只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。
    任一事件(LOS_WAITMODE_OR):逻辑或,基于接口传入的事件类型掩码eventMask,
    只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。
    清除事件(LOS_WAITMODE_CLR):这是一种附加读取模式,需要与所有事件模式或任一事件模式结合
    使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,
    当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。

  • 运作机制
    任务在调用LOS_EventRead接口读事件时,可以根据入参事件掩码类型eventMask读取事件的单个或者多个事件类型。
    事件读取成功后,如果设置LOS_WAITMODE_CLR会清除已读取到的事件类型,反之不会清除已读到的事件类型,需显式清除。
    可以通过入参选择读取模式,读取事件掩码类型中所有事件还是读取事件掩码类型中任意事件。
    任务在调用LOS_EventWrite接口写事件时,对指定事件控制块写入指定的事件类型,
    可以一次同时写多个事件类型。写事件会触发任务调度。
    任务在调用LOS_EventClear接口清除事件时,根据入参事件和待清除的事件类型,
    对事件对应位进行清0操作。

  • 使用场景
    事件可应用于多种任务同步场景,在某些同步场景下可替代信号量。

  • 注意事项
    在系统初始化之前不能调用读写事件接口。如果调用,系统运行会不正常。
    在中断中,可以对事件对象进行写操作,但不能进行读操作。
    在锁任务调度状态下,禁止任务阻塞于读事件。
    LOS_EventClear 入参值是:要清除的指定事件类型的反码(~events)。
    为了区别LOS_EventRead接口返回的是事件还是错误码,事件掩码的第25位不能使用。

事件控制块

事件控制块用来记录事件和管理等待读取事件的任务。

  • uwEventID:标记任务的事件类型,每个bit可以标识一个事件,最多可以支持31个事件(第25bit保留)
  • stEventList:事件控制块的双向循环链表;当有任务等待事件但事件还没发生时,任务会被挂载到等待链表中,当事件发生时,系统唤醒等待事件的任务,此时任务就会被踢出链表。

kernel\liteos_a\kernel\include\los_event.h

/*** @ingroup los_event* Event control structure*/
typedef struct tagEvent {UINT32 uwEventID;        /**< Event mask in the event control block,indicating the event that has been logically processed. */LOS_DL_LIST stEventList; /**< Event control block linked list */
} EVENT_CB_S, *PEVENT_CB_S;
事件的初始化

事件控制块由任务创建,然后调用LOS_EventInit()进行初始化;
kernel\liteos_a\kernel\base\ipc\los_event.c

LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB)
{UINT32 intSave;if (eventCB == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}intSave = LOS_IntLock();//锁中断eventCB->uwEventID = 0;//事件类型初始化LOS_ListInit(&eventCB->stEventList);//事件链表初始化LOS_IntRestore(intSave);//恢复中断OsHookCall(LOS_HOOK_TYPE_EVENT_INIT, eventCB);return LOS_OK;
}

事件初始化
在这里插入图片描述

读事件

系统提供了两个读事件函数

  • LOS_EventPoll()
    根据任务传入的事件值、掩码及校验模式,返回满足条件的事件,任务可以主动检查事件是否发生而不必被挂起。
  • LOS_EventRead()
    阻塞式读取事件,如果事件没有发生,则可以指定等待事件,挂起当前任务。

kernel\liteos_a\kernel\base\ipc\los_event.c

LITE_OS_SEC_TEXT UINT32 LOS_EventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode)
{UINT32 ret;UINT32 intSave;ret = OsEventParamCheck((VOID *)eventID, eventMask, mode);if (ret != LOS_OK) {return ret;}SCHEDULER_LOCK(intSave);ret = OsEventPoll(eventID, eventMask, mode);SCHEDULER_UNLOCK(intSave);return ret;
}LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout)
{return OsEventRead(eventCB, eventMask, mode, timeout, FALSE);
}......///事件参数检查
LITE_OS_SEC_TEXT STATIC UINT32 OsEventParamCheck(const VOID *ptr, UINT32 eventMask, UINT32 mode)
{if (ptr == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}if (eventMask == 0) {return LOS_ERRNO_EVENT_EVENTMASK_INVALID;}if (eventMask & LOS_ERRTYPE_ERROR) {return LOS_ERRNO_EVENT_SETBIT_INVALID;}//LOS_WAITMODE_OR 只要有一个事件发生则读取成功,返回发生的那个事件if (((mode & LOS_WAITMODE_OR) && (mode & LOS_WAITMODE_AND)) ||(mode & ~(LOS_WAITMODE_OR | LOS_WAITMODE_AND | LOS_WAITMODE_CLR)) ||!(mode & (LOS_WAITMODE_OR | LOS_WAITMODE_AND))) {return LOS_ERRNO_EVENT_FLAGS_INVALID;}return LOS_OK;
}
///根据用户传入的事件值、事件掩码及校验模式,返回用户传入的事件是否符合预期
LITE_OS_SEC_TEXT UINT32 OsEventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode)
{UINT32 ret = 0;LOS_ASSERT(OsIntLocked());//断言不允许中断了LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//任务自旋锁if (mode & LOS_WAITMODE_OR) {//如果模式是读取掩码中任意事件if ((*eventID & eventMask) != 0) {ret = *eventID & eventMask;}} else {//等待全部事件发生if ((eventMask != 0) && (eventMask == (*eventID & eventMask))) {//必须满足全部事件发生ret = *eventID & eventMask;}}if (ret && (mode & LOS_WAITMODE_CLR)) {//读取完成后清除事件*eventID = *eventID & ~ret;}return ret;
}
///检查读事件
LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadCheck(const PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode)
{UINT32 ret;LosTaskCB *runTask = NULL;ret = OsEventParamCheck(eventCB, eventMask, mode);//事件参数检查if (ret != LOS_OK) {return ret;}if (OS_INT_ACTIVE) {//中断正在进行return LOS_ERRNO_EVENT_READ_IN_INTERRUPT;//不能在中断发送时读事件}runTask = OsCurrTaskGet();//获取当前任务if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {//任务属于系统任务OsBackTrace();return LOS_ERRNO_EVENT_READ_IN_SYSTEM_TASK;//不能在系统任务中读取事件}return LOS_OK;
}
/// 读取指定事件类型的实现函数,超时时间为相对时间:单位为Tick
LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadImp(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode,UINT32 timeout, BOOL once)
{UINT32 ret = 0;LosTaskCB *runTask = OsCurrTaskGet();//获取当前任务OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode, timeout);if (once == FALSE) {ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);//检测事件是否符合预期}if (ret == 0) {//不符合预期时if (timeout == 0) {//不等待的情况return ret;}if (!OsPreemptableInSched()) {//不能抢占式调度return LOS_ERRNO_EVENT_READ_IN_LOCK;}runTask->eventMask = eventMask;	//等待事件runTask->eventMode = mode;		//事件模式runTask->taskEvent = eventCB;	//事件控制块OsTaskWaitSetPendMask(OS_TASK_WAIT_EVENT, eventMask, timeout);//任务进入等待状态,等待事件的到来并设置时长和掩码ret = runTask->ops->wait(runTask, &eventCB->stEventList, timeout);if (ret == LOS_ERRNO_TSK_TIMEOUT) {return LOS_ERRNO_EVENT_READ_TIMEOUT;}ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);//检测事件是否符合预期}return ret;
}
///读取指定事件类型,超时时间为相对时间:单位为Tick
LITE_OS_SEC_TEXT STATIC UINT32 OsEventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout,BOOL once)
{UINT32 ret;UINT32 intSave;ret = OsEventReadCheck(eventCB, eventMask, mode);//读取事件检查if (ret != LOS_OK) {return ret;}SCHEDULER_LOCK(intSave);ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once);//读事件实现函数SCHEDULER_UNLOCK(intSave);return ret;
}

当事件没有发生时,读事件操作会引发系统调度,把当前任务挂起并加入到stEventList链表,下图中事件1发生,任务Task1读取事件2,但是事件2没有发生导致Task1被挂起

读事件
在这里插入图片描述

写事件

任务可以通过LOS_EventWrite()来写触发一个或多个事件。
kernel\liteos_a\kernel\base\ipc\los_event.c

///写指定的事件类型
LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{return OsEventWrite(eventCB, events, FALSE);
}......///写入事件
LITE_OS_SEC_TEXT STATIC UINT32 OsEventWrite(PEVENT_CB_S eventCB, UINT32 events, BOOL once)
{UINT32 intSave;UINT8 exitFlag = 0;if (eventCB == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}if (events & LOS_ERRTYPE_ERROR) {return LOS_ERRNO_EVENT_SETBIT_INVALID;}SCHEDULER_LOCK(intSave);	//禁止调度OsEventWriteUnsafe(eventCB, events, once, &exitFlag);//写入事件SCHEDULER_UNLOCK(intSave);	//允许调度if (exitFlag == 1) { //需要发生调度LOS_MpSchedule(OS_MP_CPU_ALL);//通知所有CPU调度LOS_Schedule();//执行调度}return LOS_OK;
}///以不安全的方式写事件
LITE_OS_SEC_TEXT VOID OsEventWriteUnsafe(PEVENT_CB_S eventCB, UINT32 events, BOOL once, UINT8 *exitFlag)
{LosTaskCB *resumedTask = NULL;LosTaskCB *nextTask = NULL;BOOL schedFlag = FALSE;OsHookCall(LOS_HOOK_TYPE_EVENT_WRITE, eventCB, events);eventCB->uwEventID |= events;//对应位贴上标签if (!LOS_ListEmpty(&eventCB->stEventList)) {//等待事件链表判断,处理等待事件的任务//遍历事件链表,唤醒符合条件的任务for (resumedTask = LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext, LosTaskCB, pendList);&resumedTask->pendList != &eventCB->stEventList;) {nextTask = LOS_DL_LIST_ENTRY(resumedTask->pendList.pstNext, LosTaskCB, pendList);//获取任务实体if (OsEventResume(resumedTask, eventCB, events)) {//是否恢复任务schedFlag = TRUE;//任务已加至就绪队列,申请发生一次调度}if (once == TRUE) {//是否只处理一次任务break;//退出循环}resumedTask = nextTask;//检查链表中下一个任务}}if ((exitFlag != NULL) && (schedFlag == TRUE)) {//是否让外面调度*exitFlag = 1;}
}......//事件恢复操作
LITE_OS_SEC_TEXT STATIC UINT8 OsEventResume(LosTaskCB *resumedTask, const PEVENT_CB_S eventCB, UINT32 events)
{UINT8 exitFlag = 0;//是否唤醒if (((resumedTask->eventMode & LOS_WAITMODE_OR) && ((resumedTask->eventMask & events) != 0)) ||((resumedTask->eventMode & LOS_WAITMODE_AND) &&((resumedTask->eventMask & eventCB->uwEventID) == resumedTask->eventMask))) {//逻辑与 和 逻辑或 的处理exitFlag = 1; resumedTask->taskEvent = NULL;OsTaskWakeClearPendMask(resumedTask);resumedTask->ops->wake(resumedTask);}return exitFlag;
}

当事件2发生时,任务Task2把事件2写入uwEventID,此时任务Task1被调度读取事件成功,事件2对应bit位被清0(也可以不清0),Task1从链表stEventList中被摘出。
写事件
在这里插入图片描述

删除事件

事件消费完成之后,通过LOS_EventClear清空事件,通过LOS_EventDestroy清空链表指针

kernel\liteos_a\kernel\base\ipc\los_event.c

///销毁指定的事件控制块
LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB)
{UINT32 intSave;if (eventCB == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}SCHEDULER_LOCK(intSave);if (!LOS_ListEmpty(&eventCB->stEventList)) {SCHEDULER_UNLOCK(intSave);return LOS_ERRNO_EVENT_SHOULD_NOT_DESTROY;}eventCB->uwEventID = 0;//将节点从事件链表中删除,并使用该节点重新初始化事件链表LOS_ListDelInit(&eventCB->stEventList);SCHEDULER_UNLOCK(intSave);OsHookCall(LOS_HOOK_TYPE_EVENT_DESTROY, eventCB);return LOS_OK;
}
///清除指定的事件类型
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 eventMask)
{UINT32 intSave;if (eventCB == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}OsHookCall(LOS_HOOK_TYPE_EVENT_CLEAR, eventCB, eventMask);SCHEDULER_LOCK(intSave);eventCB->uwEventID &= eventMask;SCHEDULER_UNLOCK(intSave);return LOS_OK;
}

事件删除
在这里插入图片描述

互斥锁

  • 基本概念
    互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理。
    任意时刻互斥锁的状态只有两种,开锁或闭锁。当有任务持有时,互斥锁处于闭锁状态,这个任务获得该互斥锁的所有权。
    当该任务释放它时,该互斥锁被开锁,任务失去该互斥锁的所有权。当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。
    多任务环境下往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。
    另外互斥锁可以解决信号量存在的优先级翻转问题。

  • 运作机制
    多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,
    只能被独占使用。互斥锁怎样来避免这种冲突呢?
    用互斥锁处理临界资源的同步访问时,如果有任务访问该资源,则互斥锁为加锁状态。此时其他任务
    如果想访问这个临界资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问
    该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个临界资源,保证了
    临界资源操作的完整性。

  • 使用场景
    多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥锁可以提供任务间的互斥机制,
    防止两个任务在同一时刻访问相同的临界资源,从而实现独占式访问。

  • 申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。
    无阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有任务持有,或者持有该互斥锁的任务和申请
    该互斥锁的任务为同一个任务,则申请成功。
    永久阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有被占用,则申请成功。否则,该任务进入阻塞态,
    系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。
    定时阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有被占用,则申请成功。否则该任务进入阻塞态,
    系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后,指定时间超时前有其他任务释放该互斥锁,
    或者用户指定时间超时后,阻塞任务才会重新得以执行。
    释放互斥锁:
    如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。
    如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

  • 互斥锁典型场景的开发流程:
    通过make menuconfig配置互斥锁模块。
    申请互斥锁LOS_MuxLock。
    释放互斥锁LOS_MuxUnLock。
    删除互斥锁LOS_MuxDestroy。

互斥锁控制块

互斥锁控制块
kernel\liteos_a\kernel\include\los_mux.h

typedef struct OsMux { //互斥锁结构体UINT32 magic;        /**< magic number */  //魔法数字LosMuxAttr attr;     /**< Mutex attribute */ //互斥锁属性LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别.VOID *owner;         /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务UINT16 muxCount;     /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次
} LosMux;
初始化互斥锁

kernel\liteos_a\kernel\base\ipc\los_mux.c

// 初始化互斥锁
LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr)
{UINT32 intSave;if (mutex == NULL) {return LOS_EINVAL;}if (attr == NULL) {(VOID)LOS_MuxAttrInit(&mutex->attr);//属性初始化} else {(VOID)memcpy_s(&mutex->attr, sizeof(LosMuxAttr), attr, sizeof(LosMuxAttr));//把attr 拷贝到 mutex->attr}if (OsCheckMutexAttr(&mutex->attr) != LOS_OK) {//检查属性return LOS_EINVAL;}SCHEDULER_LOCK(intSave);		//拿到调度自旋锁mutex->muxCount = 0;			//锁定互斥量的次数mutex->owner = NULL;			//谁持有该锁LOS_ListInit(&mutex->muxList);	//初始化互斥量双循环链表mutex->magic = OS_MUX_MAGIC;	//固定标识,互斥锁的魔法数字SCHEDULER_UNLOCK(intSave);		//释放调度自旋锁return LOS_OK;
}///互斥属性初始化
LITE_OS_SEC_TEXT UINT32 LOS_MuxAttrInit(LosMuxAttr *attr)
{if (attr == NULL) {return LOS_EINVAL;}attr->protocol    = LOS_MUX_PRIO_INHERIT;	//协议默认用继承方式, A(4)task等B(19)释放锁时,B的调度优先级直接升到(4)attr->prioceiling = OS_TASK_PRIORITY_LOWEST;//最低优先级attr->type        = LOS_MUX_DEFAULT;		//默认 LOS_MUX_RECURSIVEreturn LOS_OK;
}
申请互斥锁

kernel\liteos_a\kernel\base\ipc\los_mux.c

// 拿互斥锁,
LITE_OS_SEC_TEXT UINT32 LOS_MuxLock(LosMux *mutex, UINT32 timeout)
{LosTaskCB *runTask = NULL;UINT32 intSave;UINT32 ret;if (mutex == NULL) {return LOS_EINVAL;}if (OS_INT_ACTIVE) {//中断正在进行return LOS_EINTR;}runTask = (LosTaskCB *)OsCurrTaskGet();//获取当前任务/* DO NOT Call blocking API in system tasks */if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {//不要在内核任务里用mux锁PRINTK("Warning: DO NOT call %s in system tasks.\n", __FUNCTION__);OsBackTrace();//打印task信息}SCHEDULER_LOCK(intSave);//调度自旋锁ret = OsMuxLockUnsafe(mutex, timeout);//如果任务没拿到锁,将进入阻塞队列一直等待,直到timeout或者持锁任务释放锁时唤醒它 SCHEDULER_UNLOCK(intSave);return ret;
}UINT32 OsMuxLockUnsafe(LosMux *mutex, UINT32 timeout)
{LosTaskCB *runTask = OsCurrTaskGet();//获取当前任务if (mutex->magic != OS_MUX_MAGIC) {return LOS_EBADF;}if (OsCheckMutexAttr(&mutex->attr) != LOS_OK) {return LOS_EINVAL;}//LOS_MUX_ERRORCHECK 时 muxCount是要等于0 ,当前任务持有锁就不能再lock了. 鸿蒙默认用的是递归锁LOS_MUX_RECURSIVEif ((mutex->attr.type == LOS_MUX_ERRORCHECK) && (mutex->owner == (VOID *)runTask)) {return LOS_EDEADLK;}return OsMuxPendOp(runTask, mutex, timeout);
}//检查互斥锁属性是否OK,否则 no ok :|)
STATIC UINT32 OsCheckMutexAttr(const LosMuxAttr *attr)
{if (((INT8)(attr->type) < LOS_MUX_NORMAL) || (attr->type > LOS_MUX_ERRORCHECK)) {return LOS_NOK;}if (((INT8)(attr->prioceiling) < OS_TASK_PRIORITY_HIGHEST) || (attr->prioceiling > OS_TASK_PRIORITY_LOWEST)) {return LOS_NOK;}if (((INT8)(attr->protocol) < LOS_MUX_PRIO_NONE) || (attr->protocol > LOS_MUX_PRIO_PROTECT)) {return LOS_NOK;}return LOS_OK;
}// 最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行. 
STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout)
{UINT32 ret;if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理/* This is for mutex macro initialization. */mutex->muxCount = 0;//锁计数器清0mutex->owner = NULL;//锁没有归属任务LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去}if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回mutex->muxCount++;				//互斥锁计数器加1mutex->owner = (VOID *)runTask;	//当前任务拿到锁LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);if (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT) {SchedParam param = { 0 };runTask->ops->schedParamGet(runTask, &param);param.priority = mutex->attr.prioceiling;runTask->ops->priorityInheritance(runTask, &param);}return LOS_OK;}//递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方mutex->muxCount++;	//递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE)return LOS_OK;		//成功退出}//到了这里说明锁在别的任务那里,当前任务只能被阻塞了.if (!timeout) {//参数timeout表示等待多久再来拿锁return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock }//自己要被阻塞,只能申请调度,让出CPU core 让别的任务上if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有}OsMuxBitmapSet(mutex, runTask);//设置锁位图,尽可能的提高锁持有任务的优先级runTask->taskMux = (VOID *)mutex;	//记下当前任务在等待这把锁LOS_DL_LIST *node = OsSchedLockPendFindPos(runTask, &mutex->muxList);if (node == NULL) {ret = LOS_NOK;return ret;}OsTaskWaitSetPendMask(OS_TASK_WAIT_MUTEX, (UINTPTR)mutex, timeout);ret = runTask->ops->wait(runTask, node, timeout);if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文OsMuxBitmapRestore(mutex, NULL, runTask);runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了ret = LOS_ETIMEDOUT;//返回超时}return ret;
}
释放互斥锁

kernel\liteos_a\kernel\base\ipc\los_mux.c

//释放锁
LITE_OS_SEC_TEXT UINT32 LOS_MuxUnlock(LosMux *mutex)
{LosTaskCB *runTask = NULL;BOOL needSched = FALSE;UINT32 intSave;UINT32 ret;if (mutex == NULL) {return LOS_EINVAL;}if (OS_INT_ACTIVE) {return LOS_EINTR;}runTask = (LosTaskCB *)OsCurrTaskGet();//获取当前任务/* DO NOT Call blocking API in system tasks */if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {//不能在系统任务里调用,因为很容易让系统任务发生死锁PRINTK("Warning: DO NOT call %s in system tasks.\n", __FUNCTION__);OsBackTrace();}SCHEDULER_LOCK(intSave);ret = OsMuxUnlockUnsafe(runTask, mutex, &needSched);SCHEDULER_UNLOCK(intSave);if (needSched == TRUE) {//需要调度的情况LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令LOS_Schedule();//发起调度}return ret;
}UINT32 OsMuxUnlockUnsafe(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched)
{if (mutex->magic != OS_MUX_MAGIC) {return LOS_EBADF;}if (OsCheckMutexAttr(&mutex->attr) != LOS_OK) {return LOS_EINVAL;}if ((LosTaskCB *)mutex->owner != taskCB) {return LOS_EPERM;}if (mutex->muxCount == 0) {return LOS_EPERM;}//注意 --mutex->muxCount 先执行了-- 操作.if ((--mutex->muxCount != 0) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//属性类型为LOS_MUX_RECURSIVE时,muxCount是可以不为0的return LOS_OK;}if (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT) {//属性协议为保护时SchedParam param = { 0 };taskCB->ops->schedParamGet(taskCB, &param);taskCB->ops->priorityRestore(taskCB, NULL, &param);}/* Whether a task block the mutex lock. *///任务是否阻塞互斥锁return OsMuxPostOp(taskCB, mutex, needSched);//一个任务去唤醒另一个在等锁的任务
}/*!* @brief OsMuxPostOp	* 是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的* OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题* @param mutex	* @param needSched	* @param taskCB	* @return	** @see*/
STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched)
{if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉mutex->owner = NULL;return LOS_OK;}LosTaskCB *resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务OsMuxBitmapRestore(mutex, &mutex->muxList, resumedTask);mutex->muxCount = 1;//互斥锁数量为1mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录OsTaskWakeClearPendMask(resumedTask);resumedTask->ops->wake(resumedTask);resumedTask->taskMux = NULL;if (needSched != NULL) {//如果不为空*needSched = TRUE;//就走起再次调度流程}return LOS_OK;
}
销毁互斥锁

kernel\liteos_a\kernel\base\ipc\los_mux.c

///销毁互斥锁
LITE_OS_SEC_TEXT UINT32 LOS_MuxDestroy(LosMux *mutex)
{UINT32 intSave;if (mutex == NULL) {return LOS_EINVAL;}SCHEDULER_LOCK(intSave);	//保存调度自旋锁if (mutex->magic != OS_MUX_MAGIC) {SCHEDULER_UNLOCK(intSave);//释放调度自旋锁return LOS_EBADF;}if (mutex->muxCount != 0) {SCHEDULER_UNLOCK(intSave);//释放调度自旋锁return LOS_EBUSY;}(VOID)memset_s(mutex, sizeof(LosMux), 0, sizeof(LosMux));//很简单,全部清0处理.SCHEDULER_UNLOCK(intSave);	//释放调度自旋锁return LOS_OK;
}

信号量

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。
一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。正值,表示该信号量当前可被获取。

以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:

  • 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,
    然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,
    从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。
  • 用作同步时,初始信号量计数值为0。任务1获取信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。

信号量运作原理

  • 信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过LOSCFG_BASE_IPC_SEM_LIMIT宏实现),并把所有信号量初始化成未使用,
    加入到未使用链表中供系统使用。

  • 信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。

  • 信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。
    当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。

  • 信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。

  • 信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。

信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。当访问资源的任务数达到该资源允许的最大数量时,
会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。

开发流程
- 创建信号量LOS_SemCreate,若要创建二值信号量则调用LOS_BinarySemCreate。
- 申请信号量LOS_SemPend。
- 释放信号量LOS_SemPost。
- 删除信号量LOS_SemDelete。

信号量控制块

信号量控制块资源由内核创建和维护,内核初始化时会调用函数OsSemInit()对信号量资源进行初始化。初始化时申请LOSCFG_BASE_IPC_SEM_LIMIT个信号量控制块,g_allSem指向信号量控制块的首地址,创建好的信号量控制块会挂载到空闲链表g_unusedSemList中。申请信号量的任务会在控制块的链表semList上排队,semCount指示可以被访问的资源数
kernel\liteos_a\kernel\base\include\los_sem_pri.h

/*** @ingroup los_sem* Semaphore control structure.*/
typedef struct {UINT8 semStat; /**< Semaphore state */UINT16 semCount; /**< Number of available semaphores */UINT16 maxSemCount;  /**< Max number of available semaphores */UINT32 semID; /**< Semaphore control structure ID */LOS_DL_LIST semList; /**< Queue of tasks that are waiting on a semaphore */
} LosSemCB;

kernel\liteos_a\kernel\common\los_config.c

LITE_OS_SEC_TEXT_INIT STATIC UINT32 OsIpcInit(VOID)
{UINT32 ret;#ifdef LOSCFG_BASE_IPC_SEMret = OsSemInit();if (ret != LOS_OK) {PRINT_ERR("OsSemInit error\n");return ret;}
#endif
.
.
.
}

kernel\liteos_a\kernel\base\ipc\los_sem.c

LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_unusedSemList; ///< 可用的信号量列表
LITE_OS_SEC_BSS LosSemCB *g_allSem = NULL; ///< 信号池,一次分配 LOSCFG_BASE_IPC_SEM_LIMIT 个信号量/** Description  : Initialize the semaphore doubly linked list | 信号量初始化* Return       : LOS_OK on success, or error code on failure*/
LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)
{LosSemCB *semNode = NULL;UINT32 index;LOS_ListInit(&g_unusedSemList);//初始化链表,链表上挂未使用的信号量,用于分配信号量,鸿蒙信号量的个数是有限的,默认LOSCFG_BASE_IPC_SEM_LIMIT = 1024;/* system resident memory, don't free */g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));//分配信号池if (g_allSem == NULL) {return LOS_ERRNO_SEM_NO_MEMORY;}for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) {semNode = ((LosSemCB *)g_allSem) + index;//拿信号控制semNode->semID = SET_SEM_ID(0, index);//保存IDsemNode->semStat = OS_SEM_UNUSED;//标记未使用LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);//通过semList把 信号块挂到空闲链表上}if (OsSemDbgInitHook() != LOS_OK) {return LOS_ERRNO_SEM_NO_MEMORY;}return LOS_OK;
}

初始化信号量控制块
在这里插入图片描述

创建信号量

kernel\liteos_a\kernel\base\ipc\los_sem.c

///对外接口 创建信号量 
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemCreate(UINT16 count, UINT32 *semHandle)
{//OS_SEM_COUNT_MAX = 0xFFFEreturn OsSemCreate(count, OS_SEM_COUNT_MAX, semHandle);
}///对外接口 创建二值信号量,其计数值最大为1,可以当互斥锁用
LITE_OS_SEC_TEXT_INIT UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)
{//OS_SEM_BINARY_COUNT_MAX = 1return OsSemCreate(count, OS_SEM_BINARY_COUNT_MAX, semHandle);
}/** Description  : Create a semaphore,* Input        : count     --- semaphore count,*                maxCount  --- Max number of available semaphores,*                semHandle --- Index of semaphore,* Return       : LOS_OK on success ,or error code on failure*/
LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
{UINT32 intSave;LosSemCB *semCreated = NULL;LOS_DL_LIST *unusedSem = NULL;UINT32 errNo;UINT32 errLine;if (semHandle == NULL) {return LOS_ERRNO_SEM_PTR_NULL;}if (count > maxCount) {//信号量不能大于最大值,两参数都是外面给的OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_OVERFLOW);}SCHEDULER_LOCK(intSave);//进入临界区,拿自旋锁if (LOS_ListEmpty(&g_unusedSemList)) {//没有可分配的空闲信号提供SCHEDULER_UNLOCK(intSave);OsSemInfoGetFullDataHook();OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_ALL_BUSY);}unusedSem = LOS_DL_LIST_FIRST(&g_unusedSemList);//从未使用信号量池中取首个LOS_ListDelete(unusedSem);//从空闲链表上摘除SCHEDULER_UNLOCK(intSave);semCreated = GET_SEM_LIST(unusedSem);//通过semList挂到链表上的,这里也要通过它把LosSemCB头查到. 进程,线程等结构体也都是这么干的.semCreated->semCount = count;//设置数量semCreated->semStat = OS_SEM_USED;//设置可用状态semCreated->maxSemCount = maxCount;//设置最大信号数量LOS_ListInit(&semCreated->semList);//初始化链表,后续阻塞任务通过task->pendList挂到semList链表上,就知道哪些任务在等它了.*semHandle = semCreated->semID;//参数带走 semIDOsHookCall(LOS_HOOK_TYPE_SEM_CREATE, semCreated);OsSemDbgUpdateHook(semCreated->semID, OsCurrTaskGet()->taskEntry, count);return LOS_OK;ERR_HANDLER:OS_RETURN_ERROR_P2(errLine, errNo);
}

创建信号量
在这里插入图片描述

申请信号量

kernel\liteos_a\kernel\base\ipc\los_sem.c

///对外接口 申请指定的信号量,并设置超时时间
LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
{UINT32 intSave;LosSemCB *semPended = GET_SEM(semHandle);//通过ID拿到信号体UINT32 retErr = LOS_OK;LosTaskCB *runTask = NULL;if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);}if (OS_INT_ACTIVE) {PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\n");OsBackTrace();return LOS_ERRNO_SEM_PEND_INTERR;}runTask = OsCurrTaskGet();//获取当前任务if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {OsBackTrace();return LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK;}SCHEDULER_LOCK(intSave);if ((semPended->semStat == OS_SEM_UNUSED) || (semPended->semID != semHandle)) {retErr = LOS_ERRNO_SEM_INVALID;goto OUT;}/* Update the operate time, no matter the actual Pend success or not */OsSemDbgTimeUpdateHook(semHandle);if (semPended->semCount > 0) {//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了semPended->semCount--;//资源少了一个OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runTask, timeout);goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的 } else if (!timeout) {retErr = LOS_ERRNO_SEM_UNAVAILABLE;goto OUT;}if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\n");OsBackTrace();retErr = LOS_ERRNO_SEM_PEND_IN_LOCK;goto OUT;}OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runTask, timeout);OsTaskWaitSetPendMask(OS_TASK_WAIT_SEM, semPended->semID, timeout);//等待信号量retErr = runTask->ops->wait(runTask, &semPended->semList, timeout);if (retErr == LOS_ERRNO_TSK_TIMEOUT) {//注意:这里是涉及到task切换的,把自己挂起,唤醒其他task retErr = LOS_ERRNO_SEM_TIMEOUT;}OUT:SCHEDULER_UNLOCK(intSave);return retErr;
}

申请信号量
在这里插入图片描述

释放信号量

kernel\liteos_a\kernel\base\ipc\los_sem.c

//对外接口 释放指定的信号量
LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
{UINT32 intSave;UINT32 ret;BOOL needSched = FALSE;if (GET_SEM_INDEX(semHandle) >= LOSCFG_BASE_IPC_SEM_LIMIT) {return LOS_ERRNO_SEM_INVALID;}SCHEDULER_LOCK(intSave);ret = OsSemPostUnsafe(semHandle, &needSched);SCHEDULER_UNLOCK(intSave);if (needSched) {//需要调度的情况LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令LOS_Schedule();发起调度}return ret;
}///以不安全的方式释放指定的信号量,所谓不安全指的是不用自旋锁
LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched)
{LosTaskCB *resumedTask = NULL;LosSemCB *semPosted = GET_SEM(semHandle);if ((semPosted->semID != semHandle) || (semPosted->semStat == OS_SEM_UNUSED)) {return LOS_ERRNO_SEM_INVALID;}/* Update the operate time, no matter the actual Post success or not */OsSemDbgTimeUpdateHook(semHandle);if (semPosted->semCount == OS_SEM_COUNT_MAX) {//当前信号资源不能大于最大资源量return LOS_ERRNO_SEM_OVERFLOW;}if (!LOS_ListEmpty(&semPosted->semList)) {//当前有任务挂在semList上,要去唤醒任务resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));//semList上面挂的都是task->pendlist节点,取第一个task下来唤醒OsTaskWakeClearPendMask(resumedTask);resumedTask->ops->wake(resumedTask);if (needSched != NULL) {//参数不为空,就返回需要调度的标签*needSched = TRUE;//TRUE代表需要调度}} else {//当前没有任务挂在semList上,semPosted->semCount++;//信号资源多一个}OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);return LOS_OK;
}

释放信号量
在这里插入图片描述

删除信号量

kernel\liteos_a\kernel\base\ipc\los_sem.c

//对外接口 删除指定的信号量,参数就是 semID 
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 semHandle)
{UINT32 intSave;LosSemCB *semDeleted = NULL;UINT32 errNo;UINT32 errLine;if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);}semDeleted = GET_SEM(semHandle);//通过ID拿到信号量实体SCHEDULER_LOCK(intSave);if ((semDeleted->semStat == OS_SEM_UNUSED) || (semDeleted->semID != semHandle)) {//参数判断SCHEDULER_UNLOCK(intSave);OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);}if (!LOS_ListEmpty(&semDeleted->semList)) {//当前还有任务挂在这个信号上面,当然不能删除SCHEDULER_UNLOCK(intSave);OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_PENDED);//这个宏很有意思,里面goto到ERR_HANDLER}LOS_ListTailInsert(&g_unusedSemList, &semDeleted->semList);//通过semList从尾部插入空闲链表semDeleted->semStat = OS_SEM_UNUSED;//状态变成了未使用semDeleted->semID = SET_SEM_ID(GET_SEM_COUNT(semDeleted->semID) + 1, GET_SEM_INDEX(semDeleted->semID));//设置IDOsHookCall(LOS_HOOK_TYPE_SEM_DELETE, semDeleted);OsSemDbgUpdateHook(semDeleted->semID, NULL, 0);SCHEDULER_UNLOCK(intSave);return LOS_OK;ERR_HANDLER:OS_RETURN_ERROR_P2(errLine, errNo);
}

删除信号量
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

ASP.NET MVC-懒加载-逐步加载数据库信息

环境&#xff1a; win10, .NET 6.0 目录 问题描述解决方案基础版数据库查询部分&#xff08;Entity Framework&#xff09;控制器前端页面 加载到表格版 问题描述 假设我数据库中有N个表&#xff0c;当我打开某页面时&#xff0c;每个表都先加载一部分&#xff08;比如20条&am…

Chainlit集成Dashscope实现语音交互网页对话AI应用

前言 本篇文章讲解和实战&#xff0c;如何使用Chainlit集成Dashscope实现语音交互网页对话AI应用。实现方案是对接阿里云提供的语音识别SenseVoice大模型接口和语音合成CosyVoice大模型接口使用。针对SenseVoice大模型和CosyVoice大模型&#xff0c;阿里巴巴在github提供的有开…

有关vue路由的学习

导言 由于很久没碰前端了&#xff0c;碰到路由都不太会了。趁着后端对接来记录一下&#xff0c;就当复习。不过由于个人能力有限&#xff0c;这篇会偏向整个过程的实现逻辑&#xff0c;其中有很多具体的方法不会给来&#xff0c;有兴趣的可以去看一下源码~ 目的&#xff1a; …

基于springboot vue 校园失物招领平台的设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

SAP_SD模块-销售订单抬头折扣金额分摊到行项目的业务记录

前言&#xff1a; 本文主要是记录24年9月份支持财务月结过程中&#xff0c;用户提出的一个问题&#xff1a;“为什么KE30有部分物料9月份的销售数量少于FAGLL03H的销售数量&#xff1f;&#xff1f;”&#xff0c;主要包括以下两个内容&#xff1b; 1、问题发生的场景复现&am…

毕设分享 基于协同过滤的电影推荐系统

文章目录 0 简介1 设计概要2 课题背景和目的3 协同过滤算法原理3.1 基于用户的协同过滤推荐算法实现原理3.1.1 步骤13.1.2 步骤23.1.3 步骤33.1.4 步骤4 4 系统实现4.1 开发环境4.2 系统功能描述4.3 系统数据流程4.3.1 用户端数据流程4.3.2 管理员端数据流程 4.4 系统功能设计 …

【hot100-java】二叉树的最近公共祖先

二叉树篇 我觉得是比两个节点的深度&#xff0c;取min&#xff08;一种情况&#xff09; DFS解题。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/ clas…

Apache Flink Dashboard

1、Overview Apache Flink Web Dashboardhttp://110.40.130.231:8081/#/overview 这张图片显示的是Apache Flink的Web UI界面&#xff0c;其中包含了以下几个部分&#xff1a; Available Task Slots: 显示当前可用的任务槽位数量。任务槽位是指Flink集群中可用于运行任务的资…

Django makemigrations时出现ModuleNotFoundError: No module named ‘MySQLdb‘

使用Python 3.11、Django 5.1.2 写完model进行makemigrations时出现报错 查找资料发现说是mysqldb适用于Python2&#xff0c;不支持Python3&#xff1b;python3可以使用pymysql 安装pymsql pip install pymysql 然后要在项目的__init__.py中加如下代码&#xff1a; import …

K8s(学习笔记)

swap分区是什么呀&#xff1f; 什么是ipvs呀&#xff1f; yaml是什么呀&#xff1f;&#xff1f;&#xff1f; p20看不下去了&#xff01;&#xff01;&#xff01;

【LeetCode】修炼之路-0004-Median of Two Sorted Arrays【python】

题目 Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays. The overall run time complexity should be O(log (mn)). Example 1: Input: nums1 [1,3], nums2 [2] Output: 2.00000 Explanation: merged…

SPIE出版-EI会议-人机交互 虚拟现实 <<< 11月杭州

EI、Scopus检索|人机交互与虚拟现实国际会议征稿进行中❗会议已通过SPIE出版❗ 2024人机交互与虚拟现实国际会议 ✅大会时间&#xff1a;2024年11月15-17日 ✅大会地点&#xff1a;中国-杭州 ✅报名/截稿&#xff1a;2024年10月15日&#xff08;团队投稿可享优惠&#xff…

实现std::sort,replace,fill,accumulate,equal等函数

std::sort /// <summary>/// std::sort 是从小到大排列的/// </summary>/// <typeparam name"IteratorClass"></typeparam>/// <typeparam name"ComparingFunctions"></typeparam>/// <param name"itBegin&qu…

基于IDEA+SpringBoot+Vue+Uniapp的投票评选小程序系统的详细设计和实现

2. 详细视频演示 文章底部名片&#xff0c;联系我获取更详细的演示视频 3. 论文参考 4. 项目运行截图 代码运行效果图 代码运行效果图 代码运行效果图 代码运行效果图 代码运行效果图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框…

大数据毕业设计选题推荐-B站热门视频数据分析-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

AI助力农作物自动采摘,基于嵌入式端超轻量级模型LeYOLO全系列【n/s/m/l】参数模型开发构建作物生产场景下番茄采摘检测计数分析系统

去年十一那会无意间刷到一个视频展示的就是德国机械收割机非常高效自动化地24小时不间断地在超广阔的土地上采摘各种作物&#xff0c;专家设计出来了很多用于采摘不同农作物的大型机械&#xff0c;看着非常震撼&#xff0c;但是我们国内农业的发展还是相对比较滞后的&#xff0…

计算机的错误计算(一百一十八)

摘要 探讨一个不动点的计算精度问题。 不动点是一类特殊的循环迭代。它有形式 例1. 已知迭代[1] 计算 显然&#xff0c;每个 均为 0.5 . 不妨在Visual Studio 2010 下用下列C语言代码计算&#xff1a; #include <stdio.h> #include <math.h>int main() {do…

【大语言模型-论文速读】GPT的不确定性判断

【大语言模型-论文精读】GPT’s Judgements Under Uncertainty Authors: Payam Saeedi and Mahsa Goodarzi 论文&#xff1a;https://arxiv.org/pdf/2410.02820 文章标题翻译 GPT的不确定性判断 Payam Saeedi Rochester Institute of Technology Mahsa Goodarzi The State …

【exp报错注入】

整数范围 最大整数 exp 函数介绍 报错盲注注入 payload分析 709C-ASCII 值就等于我们下面的 7091-1 &#xff0c;C就是我们要猜的值&#xff0c;当我们猜测的值和ASCII码相等时&#xff0c;那么exp就不会出现报错&#xff0c;因为1-1还是等于709&#xff1a; 练习 id1 an…

【AIGC】OpenAI API在快速开发中的实践与应用:优化ChatGPT提示词Prompt加速工程

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;使用最新型号确保最佳实践利用最新模型进行高效任务处理为什么要选择最新模型&#xff1f;结论 &#x1f4af;指令与上下文的分隔最佳实践分隔指令和上下文的重要性使用符…