µC/OS-III 信号量与互斥信号量

        信号量是操作系统中重要的一部分,信号量是任务间同步的一种机制,信号量可以用在多任务访问统一资源时的资源管理。µC/OS-III 提供了多种信号量,按照信号量的功能可以分为二值信号量、计数型信号量和互斥信号量,不同类新的信号量有其不同的应用场景,合理地使用信号量可以帮助开发者快速开发稳健的系统。

1 µC/OS-III 信号量简介

        信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。其中,“同步”指的是任务间的同步,即信号量可以使得一个任务等待另一个任务完成某件事情后,才继续执行; 而“有序访问”指的是对被多任务或中断访问的共享资源(如全局变量)的管理,当一个任务在访问(读取或写入)一个共享资源时,信号量可以防止其他任务或中断在这期间访问(读取或写入)这个共享资源。
        举一个例子,假设某个停车场有 100 个停车位(共享资源),这个 100 个停车位对所有人 (访问共享资源的任务或中断)开放。如果有一个人要在这个停车场停车,那么就需要先判断这个停车场是否还有空车位(判断信号量是否有资源),如果此时停车场正好有空车位(信号量有资源),那么就可以直接将车开入空车位进行停车(获取信号量成功),如果此时停车场已经没有空车位了(信号量没有资源),那么这个人可以选择不停车(获取信号量失败),也可以选择等待(任务阻塞)其他人将车开出停车场(释放信号量资源),然后再将车停入停车场。
        在上面的这个例子中,空车位的数量相当于信号量的资源数,获取信号量相当于占用了空车位,而释放信号量就相当于让出了占用的空车位。信号量用于管理共享资源的场景相当于对共享资源上了个锁,只有任务成功获取到了锁的钥匙,才能够访问这个共享资源,访问完共享资源后还得归还要是,当然钥匙可以不只一把,即信号量可以有多个资源。

2 µC/OS-III 二值信号量

2.1 µC/OS-III 二值信号量简介

        正如其名,二值信号量实际上就是只有两种情况的信号量,二值信号量只有两种情况,分别为有资源和无资源,这也就意味着,如果把二值信号量比喻成一把锁,那么这把锁只有一把钥匙,当一个任务获取了这个二值信号量之后,在这个任务释放这个二值信号量直之前,其他任务都是获取不到这个二值信号量的。二值信号量通常用于互斥访问或任务同步,与之后要讲解的互斥信号量是比较类似的,但是二值信号量有可能会导致严重的优先级翻转问题。优先级翻转问题指的是,当一个高优先级任务因获取一个被低优先级任务获取而处于没有资源状态的二值信号量时,这个高优先级的任务将被阻塞,直到低优先级的任务释放这个二值信号量,而在这之前,如果有一个优先级介于高优先级任务和低优先级任务之间的任务就绪,那么这个中等优先级的任务就会抢占低优先级任务的运行,这么一来,这三个任务中优先级最高的任务反而要最后才能运行,这就是二值信号量带来的优先级翻转问题,用户在实际开发中要注意这个问题。
        信号量的组成可以简单地理解为是一个资源计数器和一个任务等待挂起链表组成的,二值信号量与下文要讲解的计数型信号量的最大的不同之处就在于,组成二值信号量的资源计数器只会用来表示 0 1 ,一次来区分无资源和有资源的两种状态,而计数型信号量的资源计数器就能够表示 0~计数型信号量最大资源数的多种情况,下面看一下组成信号量的结构体(二值信 号量和计数型信号量共用一个结构体),该结构体定在文件 os.h 中,具体的代码如下所示:
struct os_sem{

#if (OS_OBJ_TYPE_REQ > 0u)
 OS_OBJ_TYPE Type;
#endif
 
#if (OS_CFG_DBG_EN > 0u)
 CPU_CHAR *NamePtr;
#endif
 
 OS_PEND_LIST PendList;
 
#if (OS_CFG_DBG_EN > 0u)
 OS_SEM *DbgPrevPtr;
 OS_SEM *DbgNextPtr;
 CPU_CHAR *DbgNamePtr;
#endif
 
 OS_SEM_CTR Ctr;
 
#if (OS_CFG_TS_EN > 0u)
 CPU_TS TS;
#endif

#if (defined(OS_CFG_TRACE_EN) && (OS_CFG_TRACE_EN > 0u))
 CPU_INT16U SemID;
#endif
};
        从上面的代码中可以看出,信号量的结构体中,有两个最重要的成员变量,分别为 PendList(任务等待挂起链表)和 Ctr (信号量资源计数器)。

2.2 µC/OS-III 二值信号量相关 API 函数

        µC/OS-III 提供了信号量的一些相关操作 API 函数,前面也说到了,二值信号量与计数型信号量是很相似的,因此二值信号量与计数型信号量是共用一套 API 函数的,这些操作函数如下表所示:
函数
描述
OSSemCreate()
创建一个信号量
OSSemDel()
删除一个信号量
OSSemPend()
尝试获取信号量资源
OSSemPendAbort()
终止任务挂起等待信号量资源
OSSemPost()
释放信号量资源
OSSemSet()
强制设置信号量的资源数

3 µC/OS-III 计数型信号量

        计数型信号量与二值信号量是很相似的,二值信号量的最大资源数只能是 1 ,因此只有“有资源”和“无资源”两种情况,但计数型信号量的资源数不限于 0 1 两种情况,计数型信号量的资源数是可以递归的,只要有任务释放信号量,信号量的资源数就会被加 1 ,直到溢出, 只要有任务获取信号量,那么信号量的资源数就会被减 1 ,直到 0. 计数型信号量相对于二值信号量,适用于当共享资源可以被多个任务访问的情况。因为计数型信号量于二值信号量十分相似,因此 µC/OS-III 提供的信号量相关的 API 函数,是计数型信号量和二值信号量公用的,并且使用起来也没有什么很大的区别。

4 µC/OS-III 优先级翻转

        在使用二值信号量和计数型信号量的时候,会经常地遇到优先级翻转的问题,优先级翻转的问题在抢占式内核中是很常见的,但是在实时操作系统中是不允许出现优先级翻转的现象的,因为优先级翻转会破坏任务执行的预期顺序,可能会导致未知的严重后果,下面就展示了一个优先级翻转的例子。
        
        定义:任务 H 为优先级最高的任务,任务 L 为优先级最低的任务,任务 M 为优先级介于任务 H 与任务 L 之间的任务。
        (1) 任务 H 和任务 M 为挂起状态,等待某一事件发生,此时任务 L 正在运行。
        (2) 此时任务 L 要访问共享资源,因此需要获取信号量。
        (3) 任务 L 成功获取信号量,并且此时信号量已无资源,任务 L 开始访问共享资源。
        (4) 此时任务 H 就绪,抢占任务 L 运行。
        (5) 任务 H 开始运行。
        (6) 此时任务 H 要访问共享资源,因此需要获取信号量,但信号量已无资源,因此任务 H 挂起等待信号量资源。
        (7) 任务 L 继续运行。
        (8) 此时任务 M 就绪,抢占任务 L 运行。
        (9) 任务 M 正在运行。
        (10) 任务 M 运行完毕,继续挂起。
        (11) 任务 L 继续运行。
        (12) 此时任务 L 对共享资源的访问操作完成,释放信号量,虽有任务 H 因成功获取信号量,解除挂起状态并抢占任务 L 运行。
        (13) 任务 H 得以运行。
        从上面优先级翻转的示例中,可以看出,任务 H 为最高优先级的任务,因此任务 H 执行的操作需要有较高的实时性,但是由于优先级翻转的问题,导致了任务 H 需要等到任务 L 释放信号量才能够运行,并且,任务 L 还会被其他介于任务 H 与任务 L 任务优先级之间的任务 M 抢占,因此任务 H 还需等待任务 M 运行完毕,这显然不符合任务 H 需要的高实时性要求。
        
        那有没有办法解决优先级翻转的问题呢?答案是肯定的,接下来将讲解 µC/OS-III 中的另一种信号量,它就能够很好的优化优先级翻转带来的问题。

5 µC/OS-III 互斥信号量

5.1 µC/OS-III 互斥信号量简介

        互斥信号量也叫互斥锁,可以理解为是一种特殊的二值信号量,相对于上一章中讲解的二值信号量,互斥信号量拥有优先级继承的机制使得互斥信号量能够在一定的程度上解决优先级翻转的问题。互斥信号量一般用于那些需要互斥访问的应用中。在互斥访问的应用中,互斥信号量就相当于是一把钥匙,当任务想要访问共享资源的时候,就必须先获取到这把钥匙,当任务访问完共享资源后,就必须归还这把钥匙,这样其他任务就可以拿着这把钥匙再去访问这个共享资源。
        互斥信号量的优先级继承机制体现在,当一个互斥信号量正被一个低优先级的任务持有时,如果此时有一个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会因获取不到互斥锁而被挂起,不过接下来,高优先级的任务会将持有互斥信号量的低优先级任务的任务优先级提成到与高优先级任务的任务优先级相同的任务优先级,这个过程就是优先级继承。优先级继承可以尽可能地减少高优先级任务挂起等待互斥锁的时间,并且将优先级翻转问题带来的影响降到最低。
        但是优先级继承并不是能完全解决优先级翻转带来的问题,因为优先级继承仅仅是将持有互斥信号量的低优先级任务的任务优先级提高的与高优先级任务相同的任务优先级,而非直接将互斥信号量直接从低优先级的任务手上“ 过来,因此高优先级的任务还是需要等待低优先级的任务释放互斥信号量,高优先级的任务才能够获取到互斥信号量。
        下面先看一些互斥信号量的结构体定义,互斥信号量结构体定义在文件 os.h 中,具体的代码如下所示:
struct os_mutex{

#if (OS_OBJ_TYPE_REQ > 0u)
 OS_OBJ_TYPE Type;
#endif

#if (OS_CFG_DBG_EN > 0u)
 CPU_CHAR *NamePtr;
#endif
 
 OS_PEND_LIST PendList;
 
#if (OS_CFG_DBG_EN > 0u)
 OS_MUTEX *DbgPrevPtr;
 OS_MUTEX *DbgNextPtr;
 CPU_CHAR *DbgNamePtr;
#endif
 
 OS_MUTEX *MutexGrpNextPtr;

 OS_TCB *OwnerTCBPtr;

 OS_NESTING_CTR OwnerNestingCtr;
 
#if (OS_CFG_TS_EN > 0u)
 CPU_TS TS;
#endif

#if (defined(OS_CFG_TRACE_EN) && (OS_CFG_TRACE_EN > 0u))
CPU_INT16U MutexID;
#endif
};

5.2 µC/OS-III 互斥信号量相关 API 函数

        µC/OS-III 提供了一些列操作互斥信号量的 API 函数,这些操作函数如下表所示:
函数
描述
OSMutexCreate()
创建一个互斥信号量
OSMutexDel()
删除一个互斥信号量
OSMutexPend()
尝试获取互斥信号量
OSMutexPendAbort()
终止任务挂起等待互斥信号量
OSMutexPost()
释放互斥信号量

6 µC/OS-III 优先级翻转实验

        本实验的程序流程图,如下图所示:
(1) start_task 任务
        start_task 任务的入口函数代码如下所示:
/**
* @brief start_task
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void start_task(void *p_arg)
{
 OS_ERR err;
 CPU_INT32U cnts;
 
 /* 初始化 CPU 库 */
 CPU_Init();
 
 /* 根据配置的节拍频率配置 SysTick */
 cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OSCfg_TickRate_Hz);
 OS_CPU_SysTickInit(cnts);
 
 /* 开启时间片调度,时间片设为默认值 */
 OSSchedRoundRobinCfg(OS_TRUE, 0, &err);
 
 /* 创建信号量 */
 OSSemCreate((OS_SEM* )&sem,
 (CPU_CHAR* )"sem",
 (OS_SEM_CTR )1,
 (OS_ERR* )&err);
 
 /* 创建 Task1 */
 Task1Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK1_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task1Task_TCB,
 (CPU_CHAR* )"task1",
 (OS_TASK_PTR )task1,
 (void* )0,
 (OS_PRIO )TASK1_PRIO,
 (CPU_STK* )Task1Task_STK,
 (CPU_STK_SIZE )TASK1_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK1_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 创建 Task2 */
 Task2Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK2_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task2Task_TCB,
 (CPU_CHAR* )"task2",
 (OS_TASK_PTR )task2,
 (void* )0,
 (OS_PRIO )TASK2_PRIO,
 (CPU_STK* )Task2Task_STK,
 (CPU_STK_SIZE )TASK2_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK2_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 创建 Task3 */
 Task3Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK3_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task3Task_TCB,
 (CPU_CHAR* )"task3",
 (OS_TASK_PTR )task3,
 (void* )0,
 (OS_PRIO )TASK3_PRIO,
 (CPU_STK* )Task3Task_STK,
 (CPU_STK_SIZE )TASK3_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK3_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 删除 Start Task */
 OSTaskDel((OS_TCB *)0, &err);
}
        从上面的代码中可以看出,start_task 任务使用函数 OSTaskCreate() 创建了 task1 任务、 task2 任务和 task3 任务,并且 task1 任务、 task2 任务和 task3 任务的任务栈都是动态分配的,当然,也可以静态地定任务栈,就像定义一个数组一样。同时还创建一个计数型信号量。
(2) task1 任务、 task2 任务和 task3 任务
/**
* @brief task1
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task1(void *p_arg)
{
 OS_ERR err;
OSTimeDly(500, OS_OPT_TIME_DLY, &err);
 while (1)
 {
 printf("task1 ready to pend semaphore\r\n");
 OSSemPend( (OS_SEM* )&sem,
 (OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_BLOCKING,
 (CPU_TS* )0,
 (OS_ERR* )&err);
 printf("task1 has pended semaphore\r\n");
 printf("task1 runing\r\n");
 printf("task1 post semaphore\r\n");
 OSSemPost( (OS_SEM* )&sem,
 (OS_OPT )OS_OPT_POST_ALL,
 (OS_ERR* )&err);
 OSTimeDly(100, OS_OPT_TIME_DLY, &err);
 }
}
/**
* @brief task2
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task2(void *p_arg)
{
 uint32_t task2_num = 0;
 OS_ERR err;
 
 OSTimeDly(200, OS_OPT_TIME_DLY, &err);
 while (1)
 {
 for (task2_num=0; task2_num<5; task2_num++)
 {
 printf("task2 runing\r\n");
 delay_ms(100);
 }
 OSTimeDly(1000, OS_OPT_TIME_DLY, &err);
 }
}
/**
* @brief task3
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task3(void *p_arg)
{
 uint32_t task3_num = 0;
 OS_ERR err;
 
 while (1)
 {
 printf("task3 ready to pend semaphore\r\n");
 OSSemPend( (OS_SEM* )&sem,
 (OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_BLOCKING,
 (CPU_TS* )0,
 (OS_ERR* )&err);
 printf("task3 has pend semaphore\r\n");
 for (task3_num=0; task3_num<5; task3_num++)
 {
 printf("task3 runing\r\n");
 delay_ms(100);
 }
 printf("task3 post semaphore\r\n");
 OSSemPost( (OS_SEM* )&sem,
 (OS_OPT )OS_OPT_POST_ALL,
 (OS_ERR* )&err);
 OSTimeDly(1000, OS_OPT_TIME_DLY, &err);
 }
}
        以上 task1 任务、 task2 任务和 task3 任务就是展示了优先级翻转的问题。
        通过串口打印了本次实验的相关信息,如下图所示:
          
        1. task3 成功获取信号量,并运行。
        2. task2 任务抢占 task3 任务运行。
        3. task1 任务抢占 task2 任务运行, task1 任务获取信号量失败,被挂起。
        4. task2 任务继续运行。
        5. task2 任务运行完毕, task3 任务运行。
        6. task3 任务释放信号量, task1 任务获取信号量,解除挂起状态,得以运行。
        从串口打印的信息就能看出,优先级最高的 task1 任务,最后才得到 CPU 使用权,这就是优先级翻转带来的问题。

7 µC/OS-III 互斥信号量优化优先级翻转实验

        本实验的程序流程图,如下图所示:
                  
        
(1) start_task 任务
        start_task 任务的入口函数代码如下所示:
/**
* @brief start_task
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void start_task(void *p_arg)
{
 OS_ERR err;
 CPU_INT32U cnts;
 
 /* 初始化 CPU 库 */
 CPU_Init();
 
 /* 根据配置的节拍频率配置 SysTick */
 cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OSCfg_TickRate_Hz);
 OS_CPU_SysTickInit(cnts);
 
 /* 开启时间片调度,时间片设为默认值 */
 OSSchedRoundRobinCfg(OS_TRUE, 0, &err);
 
 /* 创建互斥信号量 */
 OSMutexCreate( (OS_MUTEX* )&mutex,
 (CPU_CHAR* )"mutex",
 (OS_ERR* )&err);
 
 /* 创建 Task1 */
 Task1Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK1_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task1Task_TCB,
 (CPU_CHAR* )"task1",
 (OS_TASK_PTR )task1,
 (void* )0,
(OS_PRIO )TASK1_PRIO,
 (CPU_STK *)Task1Task_STK,
 (CPU_STK_SIZE )TASK1_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK1_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 创建 Task2 */
 Task2Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK2_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task2Task_TCB,
 (CPU_CHAR* )"task2",
 (OS_TASK_PTR )task2,
 (void* )0,
 (OS_PRIO )TASK2_PRIO,
 (CPU_STK* )Task2Task_STK,
 (CPU_STK_SIZE )TASK2_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK2_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 创建 Task3 */
 Task3Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK3_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task3Task_TCB,
 (CPU_CHAR* )"task3",
 (OS_TASK_PTR )task3,
 (void* )0,
 (OS_PRIO )TASK3_PRIO,
 (CPU_STK* )Task3Task_STK,
 (CPU_STK_SIZE )TASK3_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK3_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
/* 删除 Start Task */
 OSTaskDel((OS_TCB *)0, &err);
}
        从上面的代码中可以看出,start_task 任务使用函数 OSTaskCreate() 创建了 task1 任务、 task2任务和 task3 任务,并且 task1 任务、 task2 任务和 task3 任务的任务栈都是动态分配的,当然,也可以静态地定任务栈,就像定义一个数组一样。同时还创建一个互斥信号量。
(2) task1 任务、 task2 任务和 task3 任务
/**
* @brief task1
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task1(void *p_arg)
{
 OS_ERR err;
 
 OSTimeDly(500, OS_OPT_TIME_DLY, &err);
 while (1)
 {
 printf("task1 ready to pend mutex\r\n");
 OSMutexPend( (OS_MUTEX* )&mutex,
 (OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_BLOCKING,
 (CPU_TS* )0,
 (OS_ERR* )&err);
 printf("task1 has pended mutex\r\n");
 printf("task1 runing\r\n");
 printf("task1 post mutex\r\n");
 OSMutexPost( (OS_MUTEX* )&mutex,
 (OS_OPT )OS_OPT_POST_NONE,
 (OS_ERR* )&err);
 OSTimeDly(100, OS_OPT_TIME_DLY, &err);
 }
}
/**
* @brief task2
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task2(void *p_arg)
{
uint32_t task2_num = 0;
 OS_ERR err;
 
 OSTimeDly(200, OS_OPT_TIME_DLY, &err);
 while (1)
 {
 for (task2_num=0; task2_num<5; task2_num++)
 {
 printf("task2 runing\r\n");
 delay_ms(100);
 }
 OSTimeDly(1000, OS_OPT_TIME_DLY, &err);
 }
}
/**
* @brief task3
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task3(void *p_arg)
{
 uint32_t task3_num = 0;
 OS_ERR err;
 
 while (1)
 {
 printf("task3 ready to pend mutex\r\n");
 OSMutexPend( (OS_MUTEX* )&mutex,
 (OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_BLOCKING,
 (CPU_TS* )0,
 (OS_ERR* )&err);
 printf("task3 has pend mutex\r\n");
 for (task3_num=0; task3_num<5; task3_num++)
 {
 printf("task3 runing\r\n");
 delay_ms(100);
 }
 printf("task3 post mutex\r\n");
 OSMutexPost( (OS_MUTEX* )&mutex,
 (OS_OPT )OS_OPT_POST_NONE,
 (OS_ERR* )&err);
OSTimeDly(1000, OS_OPT_TIME_DLY, &err);
 }
}
        以上 task1 task2 task3 任务就是展示了使用互斥信号量优化优先级翻转的问题。
         通过串口打印了本次实验的相关信息,如下图所示:
             
        1. task3 成功获取互斥信号量,并运行。
        2. task2 任务抢占 task3 任务运行。
        3. task1 任务抢占 task2 任务运行, task1 任务获取互斥信号量失败,被挂起,同时将互斥信号量的持有者进行优先级继承,此时 task3 任务的任务优先级已经提高到与 task1 任务相同的任务优先级。
        4. 因为 task3 任务的任务优先级被提高了,因此 task3 抢占了 task2 任务运行。
        5. task3 任务释放互斥信号量,此时 task3 任务的任务优先级会被恢复到优先级继承之前的原始优先级。
        6. 因为互斥信号量已经被释放了,因此终止了 task1 任务的挂起状态,并让 task1 任务成为 互斥信号量的持有者从串口打印的信息就能看出,相较于使用二值信号量和计数型信号量,由于互斥信号量具有优先级继承的机制,因此在本实验中,任务优先级最高的 task1 任务并没有因为获取不到互斥信号量,而一直被其他比自身任务优先级低的任务抢占,而仅仅只需要等待互斥信号量的持有者释放互斥信号量,即可运行,这么一来系统的实时性得到了保障。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值