UcOs-III 源码阅读: os_sem.c

//作用:用于资源管理和同步的信号量代码;

/*
*********************************************************************************************************
*                                              uC/OS-III
*                                        The Real-Time Kernel
*
*                    Copyright 2009-2022 Silicon Laboratories Inc. www.silabs.com
*
*                                 SPDX-License-Identifier: APACHE-2.0
*
*               This software is subject to an open source license and is distributed by
*                Silicon Laboratories Inc. pursuant to the terms of the Apache License,
*                    Version 2.0 available at www.apache.org/licenses/LICENSE-2.0.
*
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                         SEMAPHORE MANAGEMENT
*
* File    : os_sem.c
* Version : V3.08.02
*********************************************************************************************************
*/

#define MICRIUM_SOURCE
#include "os.h"

#ifdef VSC_INCLUDE_SOURCE_FILE_NAMES
const CPU_CHAR *os_sem__c = "$Id: $";
#endif

#if (OS_CFG_SEM_EN > 0u)
/*
************************************************************************************************************************
*                                                  创建信号量
*
* 描述: 此函数创建一个信号量。
*
* 参数: p_sem         指向要初始化的信号量的指针。您的应用程序负责为信号量分配存储空间。
*
*        p_name        是一个指向您希望给信号量命名的字符串的指针。
*
*        cnt           是信号量的初始值。
*                        如果用于共享资源,应初始化为可用资源的数量。
*                        如果用于信号事件的发生,则应初始化为 0。
*
*        p_err         是一个指向变量的指针,该变量将包含此函数返回的错误代码。
*
*                                OS_ERR_NONE                    如果调用成功
*                                OS_ERR_CREATE_ISR              如果从 ISR 中调用此函数
*                                OS_ERR_ILLEGAL_CREATE_RUN_TIME 如果在调用 OSSafetyCriticalStart() 之后尝试创建信号量
*                                OS_ERR_OBJ_PTR_NULL            如果 'p_sem' 是空指针
*                                OS_ERR_OBJ_CREATED             如果信号量已创建
*
* 返回: 无
*
* 注意: 无
************************************************************************************************************************
*/

// 创建一个信号量并初始化其参数
//
// @param p_sem 指向信号量控制块的指针
// @param p_name 信号量的名称
// @param cnt 信号量的初始计数
// @param p_err 指向错误代码的指针
void OSSemCreate(OS_SEM *p_sem, CPU_CHAR *p_name, OS_SEM_CTR cnt, OS_ERR *p_err)
{
    CPU_SR_ALLOC(); // 定义CPU状态寄存器变量

    // 在安全关键应用中检查错误指针是否为空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    // 在安全关键应用中,如果系统已经开始运行,则不允许创建信号量
#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE)
    {
        *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
        return;
    }
#endif

    // 检查是否在中断服务程序中调用,如果在中断中调用则返回错误
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u)
    {
        *p_err = OS_ERR_CREATE_ISR;
        return;
    }
#endif

    // 检查信号量指针是否为空,如果为空则返回错误
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_sem == (OS_SEM *)0)
    {
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
#endif

    CPU_CRITICAL_ENTER(); // 进入临界区
#if (OS_OBJ_TYPE_REQ > 0u)
#if (OS_CFG_OBJ_CREATED_CHK_EN > 0u)
    // 检查信号量是否已经创建,如果是则返回错误
    if (p_sem->Type == OS_OBJ_TYPE_SEM)
    {
        CPU_CRITICAL_EXIT();
        *p_err = OS_ERR_OBJ_CREATED;
        return;
    }
#endif
    p_sem->Type = OS_OBJ_TYPE_SEM; // 标记数据结构为信号量
#endif
    p_sem->Ctr = cnt; // 设置信号量值
#if (OS_CFG_TS_EN > 0u)
    p_sem->TS = 0u; // 如果配置了时间戳,则初始化时间戳
#endif
#if (OS_CFG_DBG_EN > 0u)
    p_sem->NamePtr = p_name; // 如果配置了调试,则保存信号量的名称
#else
    (void)p_name; // 避免编译器警告
#endif
    OS_PendListInit(&p_sem->PendList); // 初始化等待列表

#if (OS_CFG_DBG_EN > 0u)
    OS_SemDbgListAdd(p_sem); // 如果配置了调试,则将信号量添加到调试列表中
    OSSemQty++;              // 增加信号量数量计数
#endif

    OS_TRACE_SEM_CREATE(p_sem, p_name); // 跟踪信号量创建

    CPU_CRITICAL_EXIT();  // 退出临界区
    *p_err = OS_ERR_NONE; // 设置无错误
}

/*
************************************************************************************************************************
*                                                  删除信号量
*
* 描述: 此函数删除一个信号量。
*
* 参数: p_sem         指向要删除的信号量的指针
*
*        opt           确定删除选项如下:
*
*                                OS_OPT_DEL_NO_PEND          仅在没有任务等待时删除信号量
*                                OS_OPT_DEL_ALWAYS           即使有任务在等待也删除信号量。在这种情况下,所有等待的任务将被就绪。
*
*        p_err         是一个指向变量的指针,该变量将包含此函数返回的错误代码。
*
*                                OS_ERR_NONE                    调用成功且信号量已删除
*                                OS_ERR_DEL_ISR                 如果尝试从 ISR 中删除信号量
*                                OS_ERR_ILLEGAL_DEL_RUN_TIME    如果在调用 OSStart() 之后尝试删除信号量
*                                OS_ERR_OBJ_PTR_NULL            如果 'p_sem' 是空指针
*                                OS_ERR_OBJ_TYPE                如果 'p_sem' 不是指向信号量的指针
*                                OS_ERR_OPT_INVALID             指定了无效的选项
*                                OS_ERR_OS_NOT_RUNNING          如果 uC/OS-III 尚未运行
*                                OS_ERR_TASK_WAITING            一个或多个任务在等待信号量
*
* 返回: == 0          如果没有任务在等待信号量,或者发生错误。
*              >  0          如果一个或多个等待信号量的任务现在已被就绪并被告知。
*
* 注意: 1) 使用此函数时必须小心。通常期望信号量存在的任务必须检查 OSSemPend() 的返回代码。
*        2) 因为所有等待信号量的任务都将被就绪,所以在信号量用于互斥的应用程序中必须特别小心,因为资源将不再受信号量保护。
************************************************************************************************************************
*/

#if (OS_CFG_SEM_DEL_EN > 0u)
// 删除一个信号量,并处理相关等待任务
OS_OBJ_QTY OSSemDel(OS_SEM *p_sem,
                    OS_OPT opt,
                    OS_ERR *p_err)
{
    OS_OBJ_QTY nbr_tasks;
    OS_PEND_LIST *p_pend_list;
    OS_TCB *p_tcb;
    CPU_TS ts;
    CPU_SR_ALLOC();

    // 安全关键代码检查:确保p_err参数不为空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

    // 进入删除信号量的跟踪
    OS_TRACE_SEM_DEL_ENTER(p_sem, opt);

    // 安全关键代码检查:在运行时不允许删除信号量
#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE)
    {
        OS_TRACE_SEM_DEL_EXIT(OS_ERR_ILLEGAL_DEL_RUN_TIME);
        *p_err = OS_ERR_ILLEGAL_DEL_RUN_TIME;
        return (0u);
    }
#endif

    // 检查是否在中断服务程序中尝试删除信号量
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u)
    {
        OS_TRACE_SEM_DEL_EXIT(OS_ERR_DEL_ISR);
        *p_err = OS_ERR_DEL_ISR;
        return (0u);
    }
#endif

    // 检查操作系统内核是否正在运行
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING)
    {
        OS_TRACE_SEM_DEL_EXIT(OS_ERR_OS_NOT_RUNNING);
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

    // 参数有效性检查:确保p_sem不为空
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_sem == (OS_SEM *)0)
    {
        OS_TRACE_SEM_DEL_EXIT(OS_ERR_OBJ_PTR_NULL);
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
#endif

    // 检查信号量的类型是否正确
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_sem->Type != OS_OBJ_TYPE_SEM)
    {
        OS_TRACE_SEM_DEL_EXIT(OS_ERR_OBJ_TYPE);
        *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    // 进入临界区,防止多任务环境下的竞态条件
    CPU_CRITICAL_ENTER();
    p_pend_list = &p_sem->PendList;
    nbr_tasks = 0u;
    switch (opt)
    {
    case OS_OPT_DEL_NO_PEND:
        if (p_pend_list->HeadPtr == (OS_TCB *)0)
        {
#if (OS_CFG_DBG_EN > 0u)
            OS_SemDbgListRemove(p_sem);
            OSSemQty--;
#endif
            OS_TRACE_SEM_DEL(p_sem);
            OS_SemClr(p_sem);
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_NONE;
        }
        else
        {
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_WAITING;
        }
        break;

    case OS_OPT_DEL_ALWAYS:
#if (OS_CFG_TS_EN > 0u)
        ts = OS_TS_GET();
#else
        ts = 0u;
#endif
        while (p_pend_list->HeadPtr != (OS_TCB *)0)
        {
            p_tcb = p_pend_list->HeadPtr;
            OS_PendAbort(p_tcb,
                         ts,
                         OS_STATUS_PEND_DEL);
            nbr_tasks++;
        }
#if (OS_CFG_DBG_EN > 0u)
        OS_SemDbgListRemove(p_sem);
        OSSemQty--;
#endif
        OS_TRACE_SEM_DEL(p_sem);
        OS_SemClr(p_sem);
        CPU_CRITICAL_EXIT();
        OSSched();
        *p_err = OS_ERR_NONE;
        break;

    default:
        CPU_CRITICAL_EXIT();
        *p_err = OS_ERR_OPT_INVALID;
        break;
    }

    // 删除信号量操作结束的跟踪
    OS_TRACE_SEM_DEL_EXIT(*p_err);

    return (nbr_tasks);
}
#endif

/*
************************************************************************************************************************
*                                                  等待信号量
*
* 描述: 此函数等待一个信号量。
*
* 参数: p_sem         指向信号量的指针
*
*        timeout       是一个可选的超时时间(以时钟滴答为单位)。如果非零,您的任务将在指定的时间(以“滴答”为单位)内等待资源。如果您指定 0,则您的任务将在指定的信号量上无限期等待,直到资源可用(或事件发生)。
*
*        opt           确定用户是否希望在信号量不可用时阻塞:
*
*                                OS_OPT_PEND_BLOCKING
*                                OS_OPT_PEND_NON_BLOCKING
*
*        p_ts          是一个指向变量的指针,该变量将接收信号量被发送、等待被终止或信号量被删除的时间戳。如果您传递一个空指针(即 (CPU_TS*)0),则不会获取时间戳。换句话说,传递空指针是有效的,表示您不需要时间戳。
*
*        p_err         是一个指向变量的指针,该变量将包含此函数返回的错误代码。
*
*                                OS_ERR_NONE               调用成功且您的任务拥有资源,或您等待的事件已发生
*                                OS_ERR_OBJ_DEL            如果 'p_sem' 被删除
*                                OS_ERR_OBJ_PTR_NULL       如果 'p_sem' 是空指针
*                                OS_ERR_OBJ_TYPE           如果 'p_sem' 不是指向信号量的指针
*                                OS_ERR_OPT_INVALID        如果指定了无效的 'opt' 值
*                                OS_ERR_OS_NOT_RUNNING     如果 uC/OS-III 尚未运行
*                                OS_ERR_PEND_ABORT         如果等待被其他任务终止
*                                OS_ERR_PEND_ISR           如果从 ISR 中调用此函数且结果会导致挂起
*                                OS_ERR_PEND_WOULD_BLOCK   如果指定了非阻塞但信号量不可用
*                                OS_ERR_SCHED_LOCKED       如果在调度器锁定时调用此函数
*                                OS_ERR_STATUS_INVALID     等待状态无效
*                                OS_ERR_TIMEOUT            在指定的超时时间内未接收到信号量
*                                OS_ERR_TICK_DISABLED      如果内核滴答被禁用且指定了超时时间
*
* 返回: 信号量计数器的当前值,如果不可用则返回 0。
*
* 注意: 此 API “不得” 从定时器回调函数中调用。
************************************************************************************************************************
*/

/**
 * @brief 挂起任务直到信号量可用
 *
 * 此函数使当前任务挂起,直到信号量变为可用状态或达到指定的超时时间。如果信号量在指定时间内未变为可用状态,
 * 则根据提供的选项参数决定是否继续等待或返回错误。这是嵌入式实时操作系统(RTOS)中用于任务间同步和资源管理的基本机制。
 *
 * @param p_sem 指向信号量的指针,该信号量用于任务同步
 * @param timeout 任务等待信号量变为可用状态的最大时间,如果设置为0,则无限期等待
 * @param opt 控制函数行为的选项,例如是否在等待期间允许任务调度
 * @param p_ts 指向用于存储当前时间戳的变量的指针,如果不需要时间戳,则可以设置为NULL
 * @param p_err 指向用于存储函数执行结果的错误代码的指针
 * @return OS_SEM_CTR 返回当前信号量的计数器值,表示还有多少次获取操作可以使信号量变为不可用状态
 */
OS_SEM_CTR OSSemPend(OS_SEM *p_sem,
                     OS_TICK timeout,
                     OS_OPT opt,
                     CPU_TS *p_ts,
                     OS_ERR *p_err)
{
    OS_SEM_CTR ctr;
    CPU_SR_ALLOC();

#if (OS_CFG_TS_EN == 0u)
    (void)p_ts; // 防止编译器警告未使用 'ts'
#endif

#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

    OS_TRACE_SEM_PEND_ENTER(p_sem, timeout, opt, p_ts); // 进入信号量等待跟踪

#if (OS_CFG_TICK_EN == 0u)
    if (timeout != 0u)
    {
        *p_err = OS_ERR_TICK_DISABLED;
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_TICK_DISABLED);
        return (0u);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u)
    { // 不允许从ISR调用
        if ((opt & OS_OPT_PEND_NON_BLOCKING) != OS_OPT_PEND_NON_BLOCKING)
        {
            OS_TRACE_SEM_PEND_FAILED(p_sem);
            OS_TRACE_SEM_PEND_EXIT(OS_ERR_PEND_ISR);
            *p_err = OS_ERR_PEND_ISR;
            return (0u);
        }
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING)
    { // 检查内核是否正在运行
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_OS_NOT_RUNNING);
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_sem == (OS_SEM *)0)
    { // 验证 'p_sem'
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_OBJ_PTR_NULL);
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
    switch (opt)
    { // 验证 'opt'
    case OS_OPT_PEND_BLOCKING:
    case OS_OPT_PEND_NON_BLOCKING:
        break;

    default:
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_OPT_INVALID);
        *p_err = OS_ERR_OPT_INVALID;
        return (0u);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_sem->Type != OS_OBJ_TYPE_SEM)
    { // 确保信号量已创建
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_OBJ_TYPE);
        *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    CPU_CRITICAL_ENTER(); /* 进入临界区,保护共享资源 */
    if (p_sem->Ctr > 0u)
    {                 /* 检查信号量是否可用 */
        p_sem->Ctr--; /* 信号量可用,减少计数器 */
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = p_sem->TS; /* 获取最后一次发布的时间戳 */
        }
#endif
        ctr = p_sem->Ctr;
        OS_TRACE_SEM_PEND(p_sem);
        CPU_CRITICAL_EXIT();
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_NONE);
        *p_err = OS_ERR_NONE;
        return (ctr);
    }

    if ((opt & OS_OPT_PEND_NON_BLOCKING) != 0u)
    { /* 检查是否非阻塞模式 */
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = 0u;
        }
#endif
        ctr = p_sem->Ctr; /* 信号量不可用,返回当前计数 */
        CPU_CRITICAL_EXIT();
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        OS_TRACE_SEM_PEND_EXIT(OS_ERR_PEND_WOULD_BLOCK);
        *p_err = OS_ERR_PEND_WOULD_BLOCK;
        return (ctr);
    }
    else
    {
        if (OSSchedLockNestingCtr > 0u)
        { /* 检查调度程序是否锁定 */
#if (OS_CFG_TS_EN > 0u)
            if (p_ts != (CPU_TS *)0)
            {
                *p_ts = 0u;
            }
#endif
            CPU_CRITICAL_EXIT();
            OS_TRACE_SEM_PEND_FAILED(p_sem);
            OS_TRACE_SEM_PEND_EXIT(OS_ERR_SCHED_LOCKED);
            *p_err = OS_ERR_SCHED_LOCKED;
            return (0u);
        }
    }

    OS_Pend((OS_PEND_OBJ *)((void *)p_sem), /* 任务阻塞,等待信号量 */
            OSTCBCurPtr,
            OS_TASK_PEND_ON_SEM,
            timeout);
    CPU_CRITICAL_EXIT();
    OS_TRACE_SEM_PEND_BLOCK(p_sem);
    OSSched(); /* 调度程序寻找下一个最高优先级的任务 */

    CPU_CRITICAL_ENTER();
    switch (OSTCBCurPtr->PendStatus)
    {
    case OS_STATUS_PEND_OK: /* 获取到信号量 */
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = OSTCBCurPtr->TS;
        }
#endif
        OS_TRACE_SEM_PEND(p_sem);
        *p_err = OS_ERR_NONE;
        break;

    case OS_STATUS_PEND_ABORT: /* 中止等待 */
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = OSTCBCurPtr->TS;
        }
#endif
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        *p_err = OS_ERR_PEND_ABORT;
        break;

    case OS_STATUS_PEND_TIMEOUT: /* 等待超时 */
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = 0u;
        }
#endif
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        *p_err = OS_ERR_TIMEOUT;
        break;

    case OS_STATUS_PEND_DEL: /* 信号量被删除 */
#if (OS_CFG_TS_EN > 0u)
        if (p_ts != (CPU_TS *)0)
        {
            *p_ts = OSTCBCurPtr->TS;
        }
#endif
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        *p_err = OS_ERR_OBJ_DEL;
        break;

    default:
        OS_TRACE_SEM_PEND_FAILED(p_sem);
        *p_err = OS_ERR_STATUS_INVALID;
        CPU_CRITICAL_EXIT();
        OS_TRACE_SEM_PEND_EXIT(*p_err);
        return (0u);
    }
    ctr = p_sem->Ctr;
    CPU_CRITICAL_EXIT();
    OS_TRACE_SEM_PEND_EXIT(*p_err);
    return (ctr);
}

/*
************************************************************************************************************************
*                                             终止对信号量的等待
*
* 描述: 此函数终止并使当前正在等待信号量的所有任务就绪。此函数应用于故障终止对信号量的等待,而不是通过 OSSemPost() 正常发送信号量。
*
* 参数: p_sem     指向信号量的指针
*
*        opt       确定执行的终止类型:
*
*                            OS_OPT_PEND_ABORT_1          终止单个任务(最高优先级任务)对信号量的等待
*                            OS_OPT_PEND_ABORT_ALL        终止所有等待信号量的任务
*                            OS_OPT_POST_NO_SCHED         不调用调度器
*
*        p_err     是一个指向变量的指针,该变量将包含此函数返回的错误代码。
*
*                            OS_ERR_NONE                  至少有一个等待信号量的任务被就绪并被告知等待已终止;检查返回值以获取等待被终止的任务数量。
*                            OS_ERR_OBJ_PTR_NULL          如果 'p_sem' 是空指针
*                            OS_ERR_OBJ_TYPE              如果 'p_sem' 不是指向信号量的指针
*                            OS_ERR_OPT_INVALID           如果指定了无效的选项
*                            OS_ERR_OS_NOT_RUNNING        如果 uC/OS-III 尚未运行
*                            OS_ERR_PEND_ABORT_ISR        如果从 ISR 中调用了此函数
*                            OS_ERR_PEND_ABORT_NONE       没有任务在等待
*
* 返回: == 0          如果没有任务在等待信号量,或者发生错误。
*              >  0          如果一个或多个等待信号量的任务现在已被就绪并被告知。
*
* 注意: 无
************************************************************************************************************************
*/

#if (OS_CFG_SEM_PEND_ABORT_EN > 0u)
// OSSemPendAbort函数用于中止在指定信号量上等待的任务。
// 该函数可以中止一个或所有在信号量上等待的任务,并且可以选择是否重新调度。
// 参数:
//   p_sem: 指向信号量控制块的指针。
//   opt: 指定中止等待的选项,可以是OS_OPT_PEND_ABORT_1(中止一个任务)或OS_OPT_PEND_ABORT_ALL(中止所有任务),
//        结合OS_OPT_POST_NO_SCHED选项使用时,中止等待后不进行重新调度。
//   p_err: 指向错误代码的指针,用于返回函数执行的错误代码。
// 返回值:
//   返回成功中止等待的任务数量。
OS_OBJ_QTY OSSemPendAbort(OS_SEM *p_sem,
                          OS_OPT opt,
                          OS_ERR *p_err)
{
    OS_PEND_LIST *p_pend_list;
    OS_TCB *p_tcb;
    CPU_TS ts;
    OS_OBJ_QTY nbr_tasks;
    CPU_SR_ALLOC();

    // 安全关键代码检查:确保错误指针不为空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

    // 检查是否在中断中调用,不允许在中断中中止等待
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u)
    { /* Not allowed to Pend Abort from an ISR                */
        *p_err = OS_ERR_PEND_ABORT_ISR;
        return (0u);
    }
#endif

    // 检查内核是否正在运行
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING)
    { /* Is the kernel running?                               */
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

    // 参数有效性检查:检查信号量指针和选项参数
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_sem == (OS_SEM *)0)
    { /* Validate 'p_sem'                                     */
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
    switch (opt)
    { /* Validate 'opt'                                       */
    case OS_OPT_PEND_ABORT_1:
    case OS_OPT_PEND_ABORT_ALL:
    case OS_OPT_PEND_ABORT_1 | OS_OPT_POST_NO_SCHED:
    case OS_OPT_PEND_ABORT_ALL | OS_OPT_POST_NO_SCHED:
        break;

    default:
        *p_err = OS_ERR_OPT_INVALID;
        return (0u);
    }
#endif

    // 检查信号量类型
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_sem->Type != OS_OBJ_TYPE_SEM)
    { /* Make sure semaphore was created                      */
        *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    // 进入临界区,保护信号量等待列表
    CPU_CRITICAL_ENTER();
    p_pend_list = &p_sem->PendList;
    if (p_pend_list->HeadPtr == (OS_TCB *)0)
    {                        /* Any task waiting on semaphore?                       */
        CPU_CRITICAL_EXIT(); /* No                                                   */
        *p_err = OS_ERR_PEND_ABORT_NONE;
        return (0u);
    }

    nbr_tasks = 0u;
    // 获取时间戳,为所有被中止的任务提供相同的时间
#if (OS_CFG_TS_EN > 0u)
    ts = OS_TS_GET(); /* Get local time stamp so all tasks get the same time  */
#else
    ts = 0u;
#endif
    // 遍历等待列表,中止等待的任务
    while (p_pend_list->HeadPtr != (OS_TCB *)0)
    {
        p_tcb = p_pend_list->HeadPtr;
        OS_PendAbort(p_tcb,
                     ts,
                     OS_STATUS_PEND_ABORT);
        nbr_tasks++;
        if (opt != OS_OPT_PEND_ABORT_ALL)
        {          /* Pend abort all tasks waiting?                        */
            break; /* No                                                   */
        }
    }
    CPU_CRITICAL_EXIT();

    // 根据选项决定是否进行重新调度
    if ((opt & OS_OPT_POST_NO_SCHED) == 0u)
    {
        OSSched(); /* Run the scheduler                                    */
    }

    *p_err = OS_ERR_NONE;
    return (nbr_tasks);
}
#endif

/*
************************************************************************************************************************
*                                                 向信号量发送信号
*
* 描述: 此函数向信号量发送信号。
*
* 参数: p_sem    指向信号量的指针
*
*        opt     确定执行的发送类型:
*
*                           OS_OPT_POST_1            发送并仅使最高优先级的等待任务就绪(如果有任务在等待)。
*                           OS_OPT_POST_ALL          向所有等待信号量的任务发送信号。
*
*                           OS_OPT_POST_NO_SCHED     不调用调度器
*
*        p_err    是一个指向变量的指针,该变量将包含此函数返回的错误代码。
*
*                           OS_ERR_NONE              调用成功且信号量已发送信号
*                           OS_ERR_OBJ_PTR_NULL      如果 'p_sem' 是空指针
*                           OS_ERR_OBJ_TYPE          如果 'p_sem' 不是指向信号量的指针
*                           OS_ERR_OPT_INVALID       如果指定了无效的选项
*                           OS_ERR_OS_NOT_RUNNING    如果 uC/OS-III 尚未运行
*                           OS_ERR_SEM_OVF           如果发送会导致信号量计数溢出
*
* 返回: 信号量计数器的当前值,或在发生错误时返回 0。
*
* 注意: 1) OS_OPT_POST_NO_SCHED 可以与其他选项之一组合使用。
************************************************************************************************************************
*/

// 函数OSSemPost用于释放信号量,并可选地调度下一个任务。
// 参数p_sem指向信号量控制块,opt指定释放选项,p_err返回错误代码。
// 返回值是信号量计数器的当前值。
OS_SEM_CTR OSSemPost(OS_SEM *p_sem,
                     OS_OPT opt,
                     OS_ERR *p_err)
{
    OS_SEM_CTR ctr;
    OS_PEND_LIST *p_pend_list;
    OS_TCB *p_tcb;
    OS_TCB *p_tcb_next;
    CPU_TS ts;
    CPU_SR_ALLOC();

    // 安全关键代码检查:确保错误参数非空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

    // 追踪:进入信号量发布
    OS_TRACE_SEM_POST_ENTER(p_sem, opt);

    // 确保内核正在运行
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING)
    { /* Is the kernel running?                               */
        OS_TRACE_SEM_POST_EXIT(OS_ERR_OS_NOT_RUNNING);
        *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

    // 参数有效性检查:p_sem非空且opt为有效值
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_sem == (OS_SEM *)0)
    { /* Validate 'p_sem'                                     */
        OS_TRACE_SEM_POST_FAILED(p_sem);
        OS_TRACE_SEM_POST_EXIT(OS_ERR_OBJ_PTR_NULL);
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return (0u);
    }
    switch (opt)
    { /* Validate 'opt'                                       */
    case OS_OPT_POST_1:
    case OS_OPT_POST_ALL:
    case OS_OPT_POST_1 | OS_OPT_POST_NO_SCHED:
    case OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED:
        break;

    default:
        OS_TRACE_SEM_POST_FAILED(p_sem);
        OS_TRACE_SEM_POST_EXIT(OS_ERR_OPT_INVALID);
        *p_err = OS_ERR_OPT_INVALID;
        return (0u);
    }
#endif

    // 对象类型检查:确保p_sem是信号量类型
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_sem->Type != OS_OBJ_TYPE_SEM)
    { /* Make sure semaphore was created                      */
        OS_TRACE_SEM_POST_FAILED(p_sem);
        OS_TRACE_SEM_POST_EXIT(OS_ERR_OBJ_TYPE);
        *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    // 获取时间戳(如果配置支持)
#if (OS_CFG_TS_EN > 0u)
    ts = OS_TS_GET(); /* Get timestamp                                        */
#else
    ts = 0u;
#endif

    // 追踪:信号量发布
    OS_TRACE_SEM_POST(p_sem);
    CPU_CRITICAL_ENTER();
    p_pend_list = &p_sem->PendList;
    if (p_pend_list->HeadPtr == (OS_TCB *)0)
    { /* Any task waiting on semaphore?                       */
        if (p_sem->Ctr == (OS_SEM_CTR)-1)
        {
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_SEM_OVF;
            OS_TRACE_SEM_POST_EXIT(*p_err);
            return (0u);
        }
        p_sem->Ctr++; /* No                                                   */
        ctr = p_sem->Ctr;
#if (OS_CFG_TS_EN > 0u)
        p_sem->TS = ts; /* Save timestamp in semaphore control block            */
#endif
        CPU_CRITICAL_EXIT();
        *p_err = OS_ERR_NONE;
        OS_TRACE_SEM_POST_EXIT(*p_err);
        return (ctr);
    }

    // 遍历等待信号量的任务列表并解除阻塞
    p_tcb = p_pend_list->HeadPtr;
    while (p_tcb != (OS_TCB *)0)
    {
        p_tcb_next = p_tcb->PendNextPtr;
        OS_Post((OS_PEND_OBJ *)((void *)p_sem),
                p_tcb,
                (void *)0,
                0u,
                ts);
        if ((opt & OS_OPT_POST_ALL) == 0u)
        {          /* Post to all tasks waiting?                           */
            break; /* No                                                   */
        }
        p_tcb = p_tcb_next;
    }
    CPU_CRITICAL_EXIT();
    if ((opt & OS_OPT_POST_NO_SCHED) == 0u)
    {
        OSSched(); /* Run the scheduler                                    */
    }
    *p_err = OS_ERR_NONE;

    // 追踪:退出信号量发布
    OS_TRACE_SEM_POST_EXIT(*p_err);
    return (0u);
}

/*
************************************************************************************************************************
*                                                    设置信号量
*
* 描述: 此函数将信号量计数设置为作为参数指定的值。通常,这个值为 0,但当然,我们可以将信号量设置为任何值。
*
*        当信号量用作信号机制时,您通常会使用此函数来重置计数值。
*
* 参数: p_sem     指向信号量的指针
*
*        cnt      是信号量计数的新值。要重置信号量计数,可以传递 0。
*
*        p_err    是一个指向变量的指针,该变量将包含此函数返回的错误代码。
*
*                            OS_ERR_NONE           调用成功且信号量值已设置
*                            OS_ERR_OBJ_PTR_NULL   如果 'p_sem' 是空指针
*                            OS_ERR_OBJ_TYPE       如果 'p_sem' 不是指向信号量的指针
*                            OS_ERR_SET_ISR        如果从 ISR 中调用
*                            OS_ERR_TASK_WAITING   如果有任务在等待信号量
*
* 返回: 无
*
* 注意: 无
************************************************************************************************************************
*/

#if (OS_CFG_SEM_SET_EN > 0u)
// 设置信号量的值
// 参数 p_sem 指向信号量控制块
// 参数 cnt 是要设置的信号量计数器值
// 参数 p_err 指向错误代码变量
void OSSemSet(OS_SEM *p_sem,
              OS_SEM_CTR cnt,
              OS_ERR *p_err)
{
    // 指向挂起列表的指针
    OS_PEND_LIST *p_pend_list;
    // 分配CPU状态寄存器
    CPU_SR_ALLOC();

    // 安全关键任务检查,确保p_err不为空
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        // 如果p_err为空,则调用安全关键异常处理函数
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    // 检查是否在中断服务程序中调用此函数
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u)
    { // 如果在中断服务程序中,则返回错误
        *p_err = OS_ERR_SET_ISR;
        return;
    }
#endif

    // 参数检查,确保p_sem不为空
#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_sem == (OS_SEM *)0)
    { // 如果p_sem为空,则返回错误
        *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }
#endif

    // 对象类型检查,确保信号量已创建
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_sem->Type != OS_OBJ_TYPE_SEM)
    { // 如果信号量未创建,则返回错误
        *p_err = OS_ERR_OBJ_TYPE;
        return;
    }
#endif

    // 初始化错误代码为无错误
    *p_err = OS_ERR_NONE;
    // 进入临界区
    CPU_CRITICAL_ENTER();
    // 检查信号量计数器是否大于0
    if (p_sem->Ctr > 0u)
    { // 如果信号量已经有计数,则直接设置为新的计数值
        p_sem->Ctr = cnt;
    }
    else
    {
        // 获取挂起列表指针
        p_pend_list = &p_sem->PendList;
        // 检查是否有任务在等待此信号量
        if (p_pend_list->HeadPtr == (OS_TCB *)0)
        { // 如果没有任务等待,则设置信号量值
            p_sem->Ctr = cnt;
        }
        else
        {
            // 如果有任务在等待,则返回错误
            *p_err = OS_ERR_TASK_WAITING;
        }
    }
    // 退出临界区
    CPU_CRITICAL_EXIT();
}
#endif

/*
************************************************************************************************************************
*                                           清除信号量的内容
*
* 描述: 此函数由OSSemDel()调用,用于清除信号量的内容
*
* 参数: p_sem      指向要清除的信号量的指针
*              -----
*
* 返回: 无
*
* 注意: 1) 此函数是uC/OS-III的内部函数,您的应用程序不得调用它。
************************************************************************************************************************
*/

// 清除信号量函数
// 该函数用于初始化或清除一个信号量,将其计数器设置为0,并执行相关初始化操作。
// 参数: p_sem - 指向要清除的信号量的指针,不能为空。
void OS_SemClr(OS_SEM *p_sem)
{
    // 根据配置可选地将数据结构类型设置为NONE,用于对象类型检查。
    #if (OS_OBJ_TYPE_REQ > 0u)
    p_sem->Type = OS_OBJ_TYPE_NONE;

    #endif

    // 将信号量的计数值设置为0。
    p_sem->Ctr = 0u;

    // 根据配置可选地清除时间戳。
    #if (OS_CFG_TS_EN > 0u)
    p_sem->TS = 0u;

    #endif

    // 根据配置可选地将信号量名称设置为默认值。
    #if (OS_CFG_DBG_EN > 0u)
    p_sem->NamePtr = (CPU_CHAR *)((void *)"?SEM");

    #endif

    // 初始化等待列表。
    OS_PendListInit(&p_sem->PendList);
}

/*
************************************************************************************************************************
*                                        将信号量添加/移除到调试列表
*
* 描述: 这些函数由 uC/OS-III 调用,用于将信号量添加或移除到调试列表。
*
* 参数: p_sem     指向要添加或移除的信号量的指针
*
* 返回: 无
*
* 注意: 这些函数是 uC/OS-III 的内部函数,您的应用程序不应调用它们。
************************************************************************************************************************
*/

#if (OS_CFG_DBG_EN > 0u)
void OS_SemDbgListAdd(OS_SEM *p_sem)
{
    p_sem->DbgNamePtr = (CPU_CHAR *)((void *)" ");
    p_sem->DbgPrevPtr = (OS_SEM *)0;
    if (OSSemDbgListPtr == (OS_SEM *)0)
    {
        p_sem->DbgNextPtr = (OS_SEM *)0;
    }
    else
    {
        p_sem->DbgNextPtr = OSSemDbgListPtr;
        OSSemDbgListPtr->DbgPrevPtr = p_sem;
    }
    OSSemDbgListPtr = p_sem;
}

void OS_SemDbgListRemove(OS_SEM *p_sem)
{
    OS_SEM *p_sem_next;
    OS_SEM *p_sem_prev;

    p_sem_prev = p_sem->DbgPrevPtr;
    p_sem_next = p_sem->DbgNextPtr;

    if (p_sem_prev == (OS_SEM *)0)
    {
        OSSemDbgListPtr = p_sem_next;
        if (p_sem_next != (OS_SEM *)0)
        {
            p_sem_next->DbgPrevPtr = (OS_SEM *)0;
        }
        p_sem->DbgNextPtr = (OS_SEM *)0;
    }
    else if (p_sem_next == (OS_SEM *)0)
    {
        p_sem_prev->DbgNextPtr = (OS_SEM *)0;
        p_sem->DbgPrevPtr = (OS_SEM *)0;
    }
    else
    {
        p_sem_prev->DbgNextPtr = p_sem_next;
        p_sem_next->DbgPrevPtr = p_sem_prev;
        p_sem->DbgNextPtr = (OS_SEM *)0;
        p_sem->DbgPrevPtr = (OS_SEM *)0;
    }
}
#endif
#endif

posted @ 2024-10-31 15:35  炽杨  阅读(58)  评论(0)    收藏  举报