鸿蒙微内核IPC数据结构

news2024/10/10 5:58:18

鸿蒙内核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_STATISTICS
    UINT64 startNsec, endNsec, durationUsec;
#endif

    ret = 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_SEM
    ret = OsSemInit();
    if (ret != LOS_OK) {
        PRINT_ERR("OsSemInit error\n");
        return ret;
    }
#endif

#ifdef LOSCFG_BASE_IPC_QUEUE
    ret = OsQueueInit();//创建队列控制块
    if (ret != LOS_OK) {
        PRINT_ERR("OsQueueInit error\n");
        return ret;
    }
#endif
    return 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_CONTAINER
    g_allQueue = OsAllQueueCBInit(&g_freeQueueList);
    if (g_allQueue == NULL) {
        return LOS_ERRNO_QUEUE_NO_MEMORY;
    }
#endif
    return 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 支持的最大的队列数为1024
    UINT32 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);
    //初始化双向链表freeQueueList
    LOS_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_CONTAINER
    if (OsQueueDbgInitHook() != LOS_OK) {
        (VOID)LOS_MemFree(m_aucSysMem0, allQueue);
        return NULL;
    }
#endif
    return 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;//带走队列ID
    OsHookCall(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]上,挂的都等待读/写消息的task
        LosTaskCB *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大于1024
    if (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;	//队列buf
    queueCB->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_RECURSIVE
    return 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_RECURSIVE
    if ((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;//锁计数器清0
        mutex->owner = NULL;//锁没有归属任务
        LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去
    }

    if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回
        mutex->muxCount++;				//互斥锁计数器加1
        mutex->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;//互斥锁数量为1
    mutex->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_SEM
    ret = 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);//保存ID
        semNode->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 = 0xFFFE
    return 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 = 1
    return 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;//参数带走 semID
    OsHookCall(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));//设置ID

    OsHookCall(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.coloradmin.cn/o/2201245.html

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

相关文章

《ASP.NET Web Forms 实现短视频点赞功能的完整示例》

在现代Web开发中&#xff0c;实现一个动态的点赞功能是非常常见的需求。本文将详细介绍如何在ASP.NET Web Forms中实现一个视频点赞功能&#xff0c;包括前端页面的展示和后端的处理逻辑。我们将确保点赞数量能够实时更新&#xff0c;而无需刷新整个页面。 技术栈 ASP.NET We…

Java进阶之路—单元测试Juint(完整详解Juint使用以及Juin注解,附有代码+案例)

文章目录 单元测试Juint35.1 概述35.2 用法手动导包正确的使用方式 35.3 Junit常用注解 单元测试Juint 35.1 概述 针对最小功能单元编写测试代码&#xff0c;Java中最小功能单元是方法&#xff0c;因此单元测试就是针对Java方法的测试。 对部分代码进行测试。 35.2 用法 &…

【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的新能源停车场管理系统

开题报告 随着新能源汽车的快速普及和普及&#xff0c;新能源车辆的停车和充电需求也越来越大。传统的停车场管理系统无法满足这些新能源车辆的特殊需求&#xff0c;如充电桩分配、充电桩使用情况的实时监测等。因此&#xff0c;开发一种基于 Java 的新能源停车场管理系统成为…

计算机视觉之YOLO算法基本原理和应用场景

YOLO算法基本原理 整体流程 YOLO 将目标检测问题转化为一个回归问题。它将输入图像划分成多个网格单元&#xff0c;每个网格单元负责预测中心点落在该网格内的目标。对于每个网格单元&#xff0c;YOLO 预测多个边界框以及这些边界框中包含目标的类别概率。边界框通常由中心点坐…

Spring Cloud Stream 3.x+kafka 3.8整合

Spring Cloud Stream 3.xkafka 3.8整合&#xff0c;文末有完整项目链接 前言一、如何看官方文档(有深入了解需求的人)二、kafka的安装tar包安装docker安装 三、代码中集成创建一个测试topic&#xff1a;testproducer代码producer配置&#xff08;配置的格式&#xff0c;上篇文章…

PHP中的HTTP请求:简化你的网络通信

在当今的网络应用开发中&#xff0c;PHP作为一种流行的服务器端脚本语言&#xff0c;经常需要与外部服务进行通信。这通常涉及到发送HTTP请求来获取或提交数据。幸运的是&#xff0c;PHP提供了多种方式来简化HTTP请求的过程&#xff0c;使得网络通信变得轻而易举。 PHP中的HTTP…

stm32h743 threadx + filex(SD卡读写) + CubeMX + CubeIDE

今天整了一下正点原子阿波罗h743的filex,按部就班的使用CubeMX去搭建环境,再用CubeIDE去编写程序,里面也有几个小坑,问题不大。 Step1. 创建CubeMX 首先设置RCC晶振,SYS为tim6 然后勾选sdmmc1 选择4bits,分频系数为4。这个分频系数选4对应一般的sd卡都可以用了,如果好…

单片机死机后在不破坏现场的情况下连接调试器进入调试模式

经常遇到程序在现场卡死了&#xff0c;但是这个时候没有连接调试器&#xff0c;不好找死机原因。 下面说下在程序卡死的时候&#xff0c;在不破坏死机现场的情况下&#xff0c;连接上调试器进行程序调试的方法。 把调试器连接电脑&#xff0c;打开keil做下面的配置&#xff0c…

Llama系列上新多模态!3.2版本开源超闭源,还和Arm联手搞了手机优化版,Meta首款多模态Llama 3.2开源!1B羊驼宝宝,跑在手机上了

Llama系列上新多模态&#xff01;3.2版本开源超闭源&#xff0c;还和Arm联手搞了手机优化版&#xff0c;Meta首款多模态Llama 3.2开源&#xff01;1B羊驼宝宝&#xff0c;跑在手机上了&#xff01; 在多模态领域&#xff0c;开源模型也超闭源了&#xff01; 就在刚刚结束的Met…

Web自动化Demo-PHP+Selenium

1.新建工程 打开PhpStorm新建工程如下&#xff1a; 打开终端输入如下命令安装selenium&#xff1a; composer require php-webdriver/webdriver 2.编写代码 <?php require vendor/autoload.php;use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver…

分治算法(8)_归并排序_翻转对

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(8)_归并排序_翻转对 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 温…

云原生日志ELK( logstash安装部署)

logstash 介绍 LogStash由JRuby语言编写&#xff0c;基于消息&#xff08;message-based&#xff09;的简单架构&#xff0c;并运行在Java虚拟机 &#xff08;JVM&#xff09;上。不同于分离的代理端&#xff08;agent&#xff09;或主机端&#xff08;server&#xff09;&…

【python】:pycharm2024.2.2使用

参考链接&#xff1a; 文件连接&#xff1a; 方法1&#xff1a;临时有效&#xff0c;后期官方更新提示激活无效 输入激活码&#xff1a; X9MQ8M5LBM-eyJsaWNlbnNlSWQiOiJYOU1ROE01TEJNIiwibGljZW5zZWVOYW1lIjoiZ3VyZ2xlcyB0dW1ibGVzIiwiYXNzaWduZWVOYW1lIjoiIiwiYXNzaWduZWVF…

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

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

视频流媒体融合与视频监控汇聚管理系统集成方案

流媒体视频融合与汇聚管理系统可以实现对各类模块化服务进行统一管理和配置等操作&#xff0c;可实现对应用服务的整合、管理及共享&#xff0c;以标准接口的方式&#xff0c;业务平台及其他第三方业务平台可以方便地调用各类数据&#xff0c;具有开放性和可扩展性。在流媒体视…

opencascade鼠标拖拽框选功能

1.首先在OccView中添加用于显示矩形框的类 //! rubber rectangle for the mouse selection.Handle(AIS_RubberBand) mRectBand; 2.设置框选的属性 mRectBand new AIS_RubberBand(); //设置属性 mRectBand->SetLineType(Aspect_TOL_SOLID); //设置变宽线型为实线 mRe…

scanMiR:使用R语言预测 miRNA 结合位点

生信碱移 scanMiR 结合预测 scanMiR&#xff0c;一款R语言工具包&#xff0c;能够高效地扫描任何自定义序列上的典型和非典型miRNA结合位点&#xff0c;估计解离常数并预测聚合的转录物抑制。 最近&#xff0c;几项高通量研究试图阐明miRNA–mRNA靶向的生化决定因素。在一项发…

Unity 克隆Timeline并保留引用

Timeline的资源是.playable文件&#xff0c;简单的复制不会保留引用关系。 下面的脚本可以复制引用关系。 using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; u…

Node.js入门——fs、path模块、URL端口号、模块化导入导出、包、npm软件包管理器

Node.js入门 1.介绍 定义&#xff1a;跨平台的JS运行环境&#xff0c;使开发者可以搭建服务器端的JS应用程序作用&#xff1a;使用Node.Js编写服务器端代码Node.js是基于Chrome V8引擎进行封装&#xff0c;Node中没有BOM和DOM 2.fs模块-读写文件 定义&#xff1a;封装了与…

(03)python-opencv图像处理——图像的几何变换

前言 1、变换 2、缩放 3、平移变换 4、旋转 5、仿射变换 6、翻转 参考文献 前言 在本教程中&#xff1a; 你将会学到将不同的几何变换应用于图像&#xff0c;如平移、旋转、仿射变换等。你会学到如下函数&#xff1a;cv.getPerspectiveTransform 图像的几何变换是图像…