-
信号量的作用
-
优先级翻转现象
-
uCOS中的特殊信号量——互斥信号量
本文作为一个学习uCOS的经验分享,希望能给初学小白们一个参考。以例程和运行效果来说明,对一些概念性的东西这里不做过多解释,网上相关文章多如牛毛。本文中的实验需要一些uCOS相关的前置知识,比如OS初始化,任务创建等。
测试前准备:
1、串口一个,当做可被抢占的资源。
2、准备好三个用户任务,设置优先级为高、中、低(具体值自己设置,比如5、10、15),按照优先级命名函数。
3、创建一个信号量。
创建信号量的代码:
OS_EVENT *testsem;
testsem = OSSemCreate(1);
任务代码分别为:
void TaskHigh(void *p_arg)
{
u8 err;
while(1)
{
OSSemPend(testsem, 0, &err); //申请(等待)信号量
printf("%c",'T');delay_ms(10);printf("%c",'a');delay_ms(10);
printf("%c",'s');delay_ms(10);printf("%c",'k');delay_ms(10);
printf("%c",'H');delay_ms(10);printf("%c",'i');delay_ms(10);
printf("%c",'g');delay_ms(10);printf("%c",'h');delay_ms(10);
printf("%c",' ');delay_ms(10);printf("%c",'g');delay_ms(10);
printf("%c",'e');delay_ms(10);printf("%c",'t');delay_ms(10);
printf("%c",' ');delay_ms(10);printf("%c",'t');delay_ms(10);
printf("%c",'h');delay_ms(10);printf("%c",'e');delay_ms(10);
printf("%c",' ');delay_ms(10);printf("%c",'c');delay_ms(10);
printf("%c",'o');delay_ms(10);
printf("%c",'m');printf("%c",'\r');printf("%c",'\n');
OSSemPost(testsem);
OSTimeDly(1);
}
}
void TaskMedium(void *pdata)
{
while(1)
{
printf("_\r\n");
OSTimeDly(10);
}
}
void TaskLow(void *p_arg)
{
u8 err;
while(1)
{
OSSemPend(testsem, 0, &err); //申请(等待)信号量,不设置超时。
printf("%c",'T');delay_ms(10);printf("%c",'a');delay_ms(10);
printf("%c",'s');delay_ms(10);printf("%c",'k');delay_ms(10);
printf("%c",'L');delay_ms(10);printf("%c",'o');delay_ms(10);
printf("%c",'w');delay_ms(10);printf("%c",' ');delay_ms(10);
printf("%c",'g');delay_ms(10);printf("%c",'e');delay_ms(10);
printf("%c",'t');delay_ms(10);printf("%c",' ');delay_ms(10);
printf("%c",'t');delay_ms(10);printf("%c",'h');delay_ms(10);
printf("%c",'e');delay_ms(10);printf("%c",' ');delay_ms(10);
printf("%c",'c');delay_ms(10);printf("%c",'o');delay_ms(10);
printf("%c",'m');printf("%c",'\r');printf("%c",'\n');
OSSemPost(testsem); //释放信号量
OSTimeDly(1);
}
}
说明一下这么设计的意图,为了更好观察相关的现象,选择串口作为公共资源比设置一个变量数组更加直观,被抢占后能直接打印当前任务的字符串。代码中使用了阻塞延时是为了延迟任务的执行速度,另一个角度说就是能更明显的看到被抢占的现象。
-
信号量的作用---------------------------------
简单归纳为,对于一些资源,在使用时可能会被打扰或被抢占,而我又不想被抢占,这时就需要有个规则或约定来防止这种事情发生,这个规则或约定就是信号量。
直接进入正题。
- 先看看没信号量情况下的现象。
测试条件:不创建TaskMedium,只保留TaskLow、TaskHigh,在TaskLow、TaskHigh 中屏蔽OSSemPend() 和OSSemPost()。
运行效果如下:
红圈多出来的一个字符就是TaskLow任务需要打印的字符串。由此可知TaskLow任务在使用串口时,被优先级高的TaskHigh给抢去了。如果不想被抢走就需要用到信号量。
- 接下来看看使用信号量后的现象。
测试条件:不创建TaskMedium,只保留TaskLow、TaskHigh。在TaskLow、TaskHigh 中使用OSSemPend() 和OSSemPost()。
运行效果如下:
使用信号量后, TaskLow任务能完整打印字符串,说明在使用串口时不会被抢走了。
需要注意的是信号量可以分为二值信号量和计数信号量。计数信号量可根据资源数量设置信号量的数量,举个栗子比如银行业务窗口只能同时有5个人办理业务,再多的就要排队,这时就设置信号量数量为5,来一个人办理信号量就减一,减到0时说明人满了,其他人就要排队。当有人办理完时,信号量就加一,队伍里的人就可以出来一个去办理,这时信号量又减一,以此类推。计数信号量目前我还没使用过,不多讨论。
而二值信号量,数量只有1(0和1两个值),其中一个功能就是互斥。什么叫互斥,就是你用了我就不能用,我用了你就不能用,上面例程中使用的就是二值信号量,体现了互斥功能。二值信号量还有个功能就是同步,简单说就是当做一个标志量来使用,即一个地方只释放信号量,另一个地方等到信号量后就能执行。
-
优先级翻转现象---------------------------------
在uCOSII中,每个任务都有一个唯一的优先级。一句话概括优先级翻转现象,爸爸都还没出声,就轮到儿子大声说话了。
- 使用二值信号量出现的优先级翻转现象。
测试条件:创建所有任务。在TaskLow、TaskHigh 中使用OSSemPend() 和OSSemPost()。
运行结果如下:
小横杠是TaskMedium任务打印的字符串,可以看到TaskLow任务不停被打断。
这么看可能看不出什么名堂,低优先级被高优先级打断不是很正常吗?哪里能看到优先级翻转了呢?结合下图继续往下看。
有时候,低优先级的任务在占着资源未释放信号量,高优先级的任务也要用资源的话就得等待(比如TaskLow任务正在占用串口打印字符串,TaskHigh任务在等待),这时候有个中优先级的任务需要需要运行(比如TaskMedium任务过来打印小横杠)。因为TaskHigh优先级高,按理说TaskHigh任务只是在等待CPU使用权(使用了OSTimeDly()之类叫主动释放,这时谁要用CPU都不关心了),并不允许其他任务(优先级比TaskHigh低的)比它先得到使用权。就像排队,下一个就到我了,不许有人插队进来。
但实际就偏有人插队进来。如运行效果,TaskLow任务不断被打扰,但由于还未释放信号量,TaskHigh任务是没法获得CPU使用权来运行的,只能干瞪眼看着可恶的TaskMedium任务在捣乱,这看起来是TaskMedium优先级比TaskHigh高了,这就是优先级翻转现象。TaskMedium任务捣乱会让TaskLow任务不能尽快完成,TaskHigh任务就需要花更多时间等待。如果TaskMedium任务本身就需要很多时间来运行,TaskHigh任务将等到天荒地老,这对于实时操作系统是个灾难。
这里需要说明一下,TaskMedium任务不通过信号量使用了同一个串口,原则上违背了例程“使用信号量达到资源独占”的目的,但为了能体现优先级反转现象才这么设计,TaskMedium可以不用串口,去做别的事,但依旧会因优先级高于TaskLow而打断TaskLow。
-
uCOS中的特殊信号量——互斥信号量
互斥信号量是一种二值信号量,在uCOS中能解决优先级翻转的问题。
- 使用互斥信号量解决优先级翻转问题。
测试条件:创建所有任务。创建一个互斥信号量。将TaskLow、TaskHigh 中OSSemPend() 和OSSemPost()替换成OSMutexPend() 和OSMutexPost() 。
创建互斥信号量的代码:
OS_EVENT *testmutex;
testmutex = OSMutexCreate(MUTEX_PRIO, &err); //MUTEX_PRIO是占用互斥信号量的任务被提升时所需的优先级
运行结果如下:
可以看到TaskLow任务已经能完整打印字符串。
互斥信号量能解决优先级翻转问题的关键在于,把占用信号量任务的优先级给提高了。TaskLow任务在占用信号量期间,优先级会被提升,这样就不会被打断以便尽快运行完释放信号量,释放信号量后TaskLow任务优先级会恢复到原来的。
使用互斥信号量需要注意一些地方。
官方明确规定,任务占用互斥信号量期间(还未释放信号量),不能挂起该任务,也就是不能使用延时之类的将任务挂起。否则这互斥信号量就失效了。
对于提升优先级到底需要提升到多少,这个根据情况而定了,一般比所有等待互斥信号量的任务高一级就行,比如TaskHigh 优先级5,可以把TaskLow优先级提升至4。
还有优先级天花板的问题,即等待互斥信号的任务是最高了(0级),占用信号量的任务还能不能提升?此问题网上也有很多讨论,这里不多赘述。