信号量是一种用于保护临界资源的同步机制。它可以用来控制对共享资源的访问,以避免并发访问导致的数据不一致或竞争条件。信号量的PV操作是原子操作,即不可被中断的操作。
在信号量的操作中,P操作(也称为wait操作)用于申请资源,V操作(也称为signal操作)用于释放资源。P操作会检查信号量的值,如果大于0,则将信号量的值减1,并继续执行;如果等于0,则进入等待状态,直到有其他线程释放资源。V操作会将信号量的值加1,表示资源已经被释放。
当信号量的值为0时,表示该信号量此时不能被申请、被等待了,只有等其他的信号量释放了之后,才能够再次获取。
在UCOSIII中,可以使用信号量API函数来操作信号量。这些函数包括:
1、OS_SEM:定义一个信号量。
OS_SEM MySem; //定义一个信号量
2、OS_SEM_CREATE:创建一个信号量。
void OSSemCreate (
OS_SEM *p_sem, //信号量:MySem
CPU_CHAR *p_name, //信号量名字:自定义
OS_SEM_CTR cnt, //信号量的值:自定义
OS_ERR *p_err //返回值,记录错误信号
)
3、OS_SEM_PEND:请求/等待一个信号量,如果信号量的值为0,则任务会进入阻塞状态。
OS_SEM_CTR OSSemPend (
OS_SEM *p_sem, //信号量:MySem
OS_TICK timeout, //超时时间,设置为0则一直等待
OS_OPT opt, //设置是否阻塞等待这个信号量
CPU_TS *p_ts, //记录请求到信号量时的时间(时间戳),为空则不需要时间戳
OS_ERR *p_err //返回值,记录错误信号
)
4、OS_SEM_POST:释放/发送一个信号量,告知任务某个事件发生
了,任务取得信号量便被唤醒去执行对应的操作。
OS_SEM_CTR OSSemPost (
OS_SEM *p_sem, //信号量:MySem
OS_OPT opt, //决定发送模式,信号量发送模式有三种
OS_ERR *p_err
)
信号量发送模式有三种
OS_OPT_POST_1表示只给最高优先级的任务发(等待信号量的优先级最高的)
OS_OPT_POST_ALL表示给所有等待信号量的任务发
OS_OPT_POST_NO_SCHED 表示不任务调度。这个参数可以和上面2个参数位或
信号量的初值为0,每当任务1发送一次信号量是ctr+1=1,每当任务2请求到一次信号量时ctr-1=0。当ctr=0时,任务2阻塞等待任务1发送信号量,只有任务1发送了信号量时,任务2才会请求到一次信号量,任务2才会执行一次。这样完成任务同步。
//创建一个信号量
OSSemCreate ((OS_SEM* )&SYNC_SEM,
(CPU_CHAR* )"SYNC_SEM",
(OS_SEM_CTR)0,
(OS_ERR* )&err);
//任务1的任务函数
void task1_task(void *p_arg)
{
u8 key;
OS_ERR err;
while(1)
{
key = KEY_Scan(0); //扫描按键
if(key==WKUP_PRES)
{
OSSemPost(&SYNC_SEM,OS_OPT_POST_1,&err);//发送信号量
LCD_ShowxNum(150,111,SYNC_SEM.Ctr,3,16,0); //显示信号量值
}
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err); //延时10ms
}
}
//任务2的任务函数
void task2_task(void *p_arg)
{
u8 num;
OS_ERR err;
while(1)
{
//请求信号量
OSSemPend(&SYNC_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); num++;
LCD_ShowxNum(150,111,SYNC_SEM.Ctr,3,16,0); LCD_Fill(6,131,233,313,lcd_discolor[num%14]); //刷屏
LED1 = ~LED1;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
优先级翻转是在实时系统中使用信号量时可能产生的一个严重的问题。
优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。
优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果,如下历史:
在1997年7月4号火星探路者号(Mars Pathfinder)发射后,在开始搜集气象数据之后没几天,系统(无故)重启了。】 后来,被相关技术人员找到问题根源,就是,这个优先级翻转所导致的。
创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。也就是 Task1 的优先级最高。
任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。
起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。
运行过程描述如下:
任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等待 Task3 释放函数 printf。
在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3的运行。从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。所以这种情况被称之为优先级翻转问题。
任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源,从而可以继续执行。
解决优先级翻转有2种方法:一种被称作优先级继承(priority inheritance);另一种被称作优先级天花板(priority ceilings)。
优先级继承(priority inheritance) :
优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级。当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会临时自动被提升,以使该任务不被其他任务所打断,从而能尽快的使用完共享资源并释放,再恢复该任务原来的优先级别。
优先级天花板(priority ceilings): 优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。(这个优先级称为该资源的优先级天花板) 。这种方法简单易行, 不必进行复杂的判断, 不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。
互斥信号量就是通过优先级继承的方式来解决优先级反转问题的。
低优先级任务 Task1 执行过程中先获得互斥资源执行。此时任务 Task2 抢占了任务 Task1的执行,任务 Task1 被挂起。任务 Task2 得到执行。
任务 Task2 执行过程中也需要调用互斥资源,但是发现任务 Task1 正在访问,此时任务 Task1 的优先级会被提升到与 Task2 同一个优先级,也就是优先级 3,这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务 Task2 被挂起,任务 Task1 有新的优先级继续执行。
任务 Task1 执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2 获得互斥资源后开始执行。