Vxworks信号量高阶分析(互斥死锁)


VXWORKS实时操作系统中信号量用于多任务同步与互斥的讨论
赵佑春

摘要:实时操作系统中对任务的响应时间提供了机制上的保障,但任务的同步与互斥又会削弱对任务响应的实时性,本文通过针对一个具体的实时操作系统VxWorks,详细的讨论了任务的同步与互斥机制,重点的讨论了互斥信号量机制对系统的影响。
关键字 Vxworks 、实时操作系统、任务的同步与互斥、优先权继承协议、临界区
Discuss of Synchronism and Exclusion of Semaphore in VxWorks
Zhao Youchun
(Dep. Of Automatization, Nanjing University of Science and Technology, Nanjing 210094)
Abstract In real-time operating system, mechanisms is supported in order to guarantee that the deadline time of tasks will be met. But mechanism of synchronism and exclusion supported by operation also weaken the response to deadline of tasks. Synchronism and exclusion of a particular operating system VxWorks is studied and focus is put on exclusion semaphore which is important to access share resource.
Key Words VxWorks,real time operating system ,synchronism and exclusion of tasks, protocol of priority inheritance, critical area
1.概述
实时操作系统中对于共享资源的保护与任务的同步协作,一般都提供了信号量机制。通常将信号量分为三种,二值信号量,计数信号量,互斥信号量。二值信号量常用于各相互协作任务间的同步,计数信号量常用于管理多个共享资源的使用。互斥信号量常用于对单一共享资源的保护。
下面我们主要以VxWorks实时操作系统为例,讨论信号量机制在任务间的同步与互斥中的应用。
2.二值信号量用于任务间同步
任务同步,首先由最先执行的主任务创建二值信号量synSem = semBCreate ( SEM_Q_FIFO(或SEM_PRIORITY) , SEM_EMPTY),用于同步的二值信号量必须初始化为空。紧接着由同步触发任务(初始化任务)将所有的必须初始化工作做完后,释放信号量semGive ( synSem),唤醒后面的具体执行任务,具体执行任务在任务的开始执行处提取信号量semTake ( synSem)。如下面框架所示。
taskA ( parameter )
{
FOREVER /* 任务为一个无限循环*/
{
相应的初始化和准备工作
· · · ·
semGive(synSem);/*信号量释放后,唤醒较高优先权的执行任务*/
}
}
taskB (parameter)
{
FOREVER
{
semTake (synSem);
· · · ·
执行具体的操作
· · · ·
}/*开始下一个循环时,由于得不到信号量,阻塞,让出CPU,任务A可以执行*/
}
http://www.elecfans.com 电子发烧友 http://bbs.elecfans.com 电子技术论坛
有些任务需要与外界交互(数据采集,信息接收等)这时可以在中断处理程序中加入对二值同步信号量的释放语句(不允许加入提取语句,否则会破坏中断程序响应,从而破坏系统的正常运行),由中断程序来唤醒相应的处理程序。这样,平时该任务处于阻塞状态,系统运行其它的任务,等到有了外部输入,该任务提取信号量,从而被唤醒运行。
同步问题中往往会有多个任务等待在一个同步点上,当一个任务触发同步点时,所有的任务都处于就绪状态。这时使用semGive(synSem),只能将其中一个任务唤醒进入就绪队列,而其他等待该信号量的任务仍在睡眠,将无法进入就绪队列。使用semFlush( )可以唤醒所有阻塞在该信号量上的任务,使其进入就绪队列,等待被执行。这也就是同步机制中的广播。
3.互斥信号量用于任务间的资源保护
使用互斥信号量机制可以保护对共享资源的正确使用,同时也为任务之间的同步提供了保障。申请不到互斥信号量的任务即转入睡眠被放入等待信号量的队列中,让出对CPU的使用。若任务得到了互斥信号量即可对共享资源进行访问,这时系统仍然允许抢占。
若有高优先权任务被唤醒,它会立即剥夺低优先权任务的运行,抢占CPU开始执行,若高优先权任务也要访问共享资源,则它被阻塞,让出CPU。此时存在任务的同步与任务实时性之间的矛盾。由于共享资源的有限性(往往是内存或其它昂贵的装置),当一个低优先权的任务占有了共享资源,已就绪的高优先权的任务将不得不等待,直到低优先权的任务使用完共享资源,释放信号量。这样高优先权的任务的响应时间被延迟,如果在低优先权的任务得到互斥信号量,阻塞了高优先权信号量时,一个中等优先权的任务随后发起,就会出现优先权反转问题。
在VxWorks中,系统提供互斥信号量API函数SEM_ID semMCreate ( int options ),该信号量主要用于任务间对共享资源的互斥。与二值信号量提供的互斥功能不同,它更加严格的保护共享资源以及占用共享资源的任务。二值信号量没有优先权反转的保护;而且对于二值信号量,不同的任务都可以对其进行释放,在中断任务中也可释放二值信号量。甚至当任务持有二值信号量时,其它任务都可以删除它。但互斥信号量只能由申请该信号量的任务来释放它,决不允许在其他任务中释放它(由系统编译程序检查以实现该功能),也不允许在中断处理程序中提取与释放互斥信号量,更不允许任务锁住互斥信号量时,被其它任务删除(由参数options的值SEM_DELETE_SAFE来指定该项保护);互斥信号量提供选择字参数options,可以按优先权(SEM_Q_PRIORITY)与先入先出队列(SEM_Q_FIFO)两种方式排列等待对该互斥信号量进行上锁的任务,在选用优先权方式时,系统还提供优先权反转的保护。
在创建互斥信号量时,选择SEM_INVERSION_SAFE与SEM_Q_PRIORITY两选择值的域,即可避免优先权反转。但此时系统在检查临界区的信号量上锁(semTake())与解锁(semGive( ))时,比二值信号量实现互斥会有很大的开销,它花费了系统的开销来保护系统的稳定性。它的实现框架如下。
SEM_ID muxSem /*将其说明为全局变量,使所有使用它的任务都能访问到它*/
taskA( )
{
muxSem = semMCreate ( SEM_Q_PRORITY | SEM_INVERSION_SAFE );
}
taskB( )
{
semTake ( muxSem ,WAIT_FOREVER);
访问临界区的代码
semGive(muxSem); /*临界区上锁与解锁必须成对出现在一个任务中*/
非临界区代码
}
VxWorks通过优先权继承协议可以克服优先权的反转问题。优先权继承协议分为基本优先权继承协议,顶层优先权继承协议两种。基本优先权继承协议规定一个任务T获得了互斥信号量,同时阻塞了比其本身优先权高的多个任务后,该任务T在临界区放弃使用原先的优先权,将从被它阻塞的多个高优先
http://www.elecfans.com 电子发烧友 http://bbs.elecfans.com 电子技术论坛
权任务中,选出一个最高优先权任务,将该任务的优先权传给在临界区执行的低优先权任务T,这样,低优先权任务T在临界区执行时,它的优先权被提高了,当其执行完临界区代码后,恢复原先进入临界区之前的优先权。基本优先权协议保证了高优先权任务被低优先权任务在临界区阻塞后不会发生优先权反转。VxWorks系统实现了基本优先权协议,但基本优先权协议无法避免潜在的死锁。例如,A任务得到了互斥信号量1,高优先权的任务B抢占了任务A,然后任务B得到了信号量2此时它又想得到信号量1,当它去提取信号量1时,死锁发生了。这时要求程序员能够仔细严谨的设计对信号量的访问顺序,与多个任务的执行顺序。同时由于保护临界资源提升了低优先权任务的优先级,基本优先权协议降低了高优先权任务实时性。如当任务1得到了信号量1,任务2发起(较任务1优先权高),抢占了任务1并得到信号量2,之后任务3发起,任务3是最高优先权任务,抢占了任务2,此时它试图去得到信号量1和信号量2,肯定被阻塞,不得不等到任务2和任务1都执行完临界区代码后,才能执行自己的代码。
4.互斥信号量对于任务运行时间的影响
顶层优先权协议可以解除潜在的阻塞,并且保证高优先权任务至多被一个低优先权任务的临界代码区阻塞。但是从总体上讲,它牺牲了系统的开销,在互斥信号量上锁与解锁时要花费比基本优先权更多的开销。但它保证了高优先权任务的实时性,不会形成阻塞链。通过优先权的提升,顶层优先权协议破坏了死锁中的循环等待条件,因而不会发生死锁。顶层优先权的定义为:程序初始化创建信号量时为每个互斥信号量分配一个优先权称其为顶层优先权,该优先权设为将会使用该互斥信号量的所有任务中的最高优先权。当一个任务T对互斥信号量m上锁时,系统首先检查运行系统中已被上锁的信号量的顶层优先权是否小于任务T自己的优先权,1)若小于则任务T可以试图上锁信号量m,当执行获取信号量操作时,若该信号量m已被锁住,则系统将任务T的优先权值传递给锁住信号量m的任务,同时将任务T阻塞。若信号量没有被锁住,任务T得到信号量m;2)如果大于等于,任务T不允许执行上锁信号量的操作,任务T阻塞。直到将其阻塞的信号量被释放(该信号量与任务T无关联),任务T被唤醒,再重复前面的检查,如果通过检查,转入执行1)。当一个任务T释放信号量时,它首先检查是否有被阻塞在该信号量上的任务,如果有则选最高优先权的任务,将其唤醒,投入运行。
与基本优先权相比,在任务获取信号量时,系统要检查其它已获取信号量的任务,察看信号量的顶层优先权,也就是将来要访问该信号量的优先权最高的任务的优先权值,通过检察顶层优先权,就可以避免低优先级任务提前获取随后发起的高优先权任务将要用到的互斥信号量,从而保证对高优先权任务的实时性响应,防止死锁与阻塞链的产生。
在VxWorks中没有实现顶层优先权协议,如果程序员对互斥信号量的上锁与解锁顺序安排不当,会产生死锁。同时也会产生阻塞链,降低了实时任务的响应时间。通过下面的简单演示程序可以看到这种情况。
该程序发起三个任务,任务3优先权最低,先运行,获得了信号量mux1,两个时钟节拍后,任务2开始运行获得了信号量mux2,第四个时钟节拍,最高优先权任务1运行,试图得到信号量1和2,被阻塞,必须等待任务1和任务2都释放了信号量后,它才能获得全部信号量继续执行。
#define Hiprio 200
#define Miprio 220
#define Loprio 240
/* 说明3个任务的原型 */
void task1();
void task2();
void task3();
/* 定义互斥信号量 */
SEM_ID mux1,mux2;
void TimeElapse(int tasknum ) /*用于在临界区内模仿临界资源的执行*/
{
int m,n;
http://www.elecfans.com 电子发烧友 http://bbs.elecfans.com 电子技术论坛
for(m=0;m<10000;m++)
{
for(n=1;n<1000;n++)
if((m%5000==0)&&(n%500==0)) /*输出2条语句供输出察看结果*/
printf("task%d in critcal section...!\n",%tasknum);
}
}
void PendDemo()
{
int i=0;
mux1 = semMCreate(SEM_Q_PRIORITY|SEM_INVERSION_SAFE);
mux2 = semMCreate(SEM_Q_PRIORITY|SEM_INVERSION_SAFE);
printf("now begin to DEMO!\n");
/* 创建三个任务并投入运行 */
taskSpawn("task1",Hiprio,0x0100,1024,(FUNCPTR )task1,0,0,0,0,0,0,0,0,0,0);
taskSpawn("task2",Miprio,0x0100,1024,(FUNCPTR )task2,0,0,0,0,0,0,0,0,0,0);
taskSpawn("task3",Loprio,0x0100,1024,(FUNCPTR )task3,0,0,0,0,0,0,0,0,0,0);
}
void task1()
{
STATUS f; /* semTake( )调用后的返回值,用于判断任务是否阻塞*/
taskDelay(4); /*将任务延迟4个时钟节拍后,开始运行*/
printf("Now,high prio task1 release!\n");
printf("task1 try to get sem1.....\n");
/*参数NO_WAIT指定,当信号量已被上锁,不阻塞,直接返回,继续下面代码的执行*/
f = semTake( mux1,NO_WAIT);
if(f== -1)
{
printf("semTake(mux1,NO_WAIT)return %d: mux1 has been lock,task1 has to be pended!\n",f);
}
if(semTake(mux1,WAIT_FOREVER)==ERROR)
printf("mux1 take failed!\n");
else
printf("task1 has taken mux1\n");
f = semTake( mux2,NO_WAIT);
if(f== -1)
{
printf("semTake(mux2,NO_WAIT)return %d: mux2 has been lock,task1 has to be pended!\n",f);
}
if(semTake(mux2,WAIT_FOREVER)==ERROR)
printf("mux2 take failed!\n");
else
printf("task1 has taken mux2\n");
http://www.elecfans.com 电子发烧友 http://bbs.elecfans.com 电子技术论坛
printf("task1 now is in its critical section.\n");
semGive(mux2);
printf("task1 has given mux2...!\n");
semGive(mux1);
printf("task1 has given mux1...!\n");
printf("task1 end....!\n");
}
void task2()
{
int n=0,m=0;
taskDelay(2);/*任务延迟2个时钟节拍后,开始运行*/
printf("Now,Middle prio task2 release!\n");
if(semTake(mux2,WAIT_FOREVER)==ERROR)
printf("mux2 take failed!\n");
else
printf("task2 has taken mux2\n");
Time Elapse(2 ); /*模拟临界区中对资源的计算*/
printf("task2 has given mux2....\n");
semGive(mux2); /*唤醒了任务1,控制转移到任务1*/
printf("task2 end....!\n");/*任务1结束后,任务而才能结束*/
}
void task3()
{
int m=0,n=1000,pri;
printf("Now,Low prio task3 release!\n");
if(semTake(mux1,WAIT_FOREVER)==ERROR)
printf("mux1 take failed!\n");
else
printf("task3 has taken mux1\n"); /*进入临界区*/
Time Elapse(3); /*模拟临界区中对资源的计算*/
taskPriorityGet(id2,&pri); /*得到任务3退出临界区前的优先权,应等于任务1的优先权*/
printf("task3 priority in critical is %d \n",pri);
printf("task3 has given mux1....\n");
semGive(mux1); /*唤醒任务1,控制转移到任务1*/
/*任务1,任务2结束后,下面的语句才有机会执行*/
printf("task3 is in non-critical section...!\n");
taskPriorityGet(id2,&pri); /*输出任务3原先的优先级*/
printf("task3 priority in non-critical is %d \n",pri);
printf("task3 exit....!\n");
}
在Tornado开发环境下编译后,用Tornado的模拟工具运行输出如下内容。
Now,Low prio task3 release!
task3 has taken mux1
http://www.elecfans.com 电子发烧友 http://bbs.elecfans.com 电子技术论坛
task3 in critcal section...!
Now,Middle prio task2 release!
task2 has taken mux2
task2 is in critical section!....!
Now,high prio task1 release!
task1 try to get sem1.....
semTake(mux1,NO_WAIT)return -1: mux1 has been lock,task1 has to be pended!
task3 in critcal section...!
task3 priority in critical is 200
task3 has given mux1....
task1 has taken mux1
semTake(mux2,NO_WAIT)return -1: mux2 has been lock,task1 has to be pended!
task2 is in critical section!....!
task2 has given mux2
task1 has taken mux2....!
task1 now is in its critical section.
task1 has given mux2...!
task1 has given mux1...!
task1 end....!
task2 end....!
task3 is in non-critical section...!
task3 priority in non-critical is 240
task3 end....!
如果使用顶层优先权协议,由于任务1用到了互斥信号量mux1和mux2,所以信号量mux1和信号量mux2的顶层优先权都为Hiprio(200)。当任务3获取信号量时,系统首先察看是否有已被锁住的互斥信号量,由于它是最先运行的任务,因而系统使任务3成功的获取了信号量mux1,两个节拍之后,任务2发起,试图获得信号量mux2,系统检察已被锁住的信号量mux1,它的顶层优先权为200,大于任务2的优先权因而任务2不能获取信号量mux2,这时称任务2被信号量mux1阻塞,同时任务3优先权提升为Miprio(220)。四个时钟节拍后,任务1发起,试图获取信号量mux1,系统检察已被锁住的信号量mux1,它的顶层优先权等于任务1,任务1同样不能获得信号量mux1,这时称任务1是顶层阻塞,由此避免了阻塞链。任务3运行释放信号量mux1,系统察看被阻塞的任务集,从中选出优先权最高的任务,投入运行,这样任务1可以获得信号量mux1,随后又获得信号量mux2,由此任务1只被一个低优先权任务阻塞,保障了系统对任务1响应的实时性。
5.结论
通过上面的讨论,实时操作系统用于任务间同步与互斥的方法有多种,其系统开销、运行时的稳定性、对任务的实时响应,均有所不同。可以根据具体任务的需要,选择其中的一个方式。优先权基本协议实现了系统反转,但无法避免潜在的死锁,也会延长高优先权任务的实时响应。顶层优先权协议可以避免以上的情况,但系统中的信号量很多时,其上锁与解锁的运行开销会很大。如果系统中互斥信号量较少,又对任务的响应实时性较高时,带有优先权顶层协议的实时操作系统就非常适合。
参考文献:
1. VxWorks Programmer’s Guide .Wind River System,Inc
2. Lui Sha,Ragunathan Rajkumar,John P.Lehoczky ,“Priority Inhertance Protocols:An Approach to Real-Time Synchronization” ,IEEE Trans. Comput., vol.39,1990.9.
3.C L Liu, J W Layland, Scheduling Algorithm for Multiprogramming in a Hard Real-Time Environment , Journal of the ACM,1973,20(1):40-61
4.Andrew S.Tanenbaum,陈向群等译,现代操作系统,北京:机械工业出版社,TP316,1999,11 http://www.elecfans.com 电子发烧友 http://bbs.elecfans.com 电子技术论坛


你可能感兴趣的:(通信,死锁,信号量,多进程,vxworks)