ucos 对信号量的支持由os_sem.c os_core.c支持,其中os_core.c提供OS_EVENT 数据结构的一些基本操作,os_sem.c则实现具体的信号量,信号量实现的分析,主要数据结构问题。
1.OS_EVENT结构的实现分析
typedef struct{
INT8U OSEventType //事件控制块的类型
INT8U OSEventGrp //等待的任务组
INT16U OSEventCnt;//信号量计数
void *OSEventPtr; //此处作用链表的链接指针
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//等待任务表
}OS_EVENT;
OS_EVENT 结构体中,信号量主要涉及域为OSEventCnt OSEventGrp OSEventTbl 其中OSEventCnt 用于计数,OSEventGrp, OSEventTbl 用于
支持任务挂起,参照前面的分析 OSEventCnt相当于S
由该结构定义了以下变量用于支持多个信号量的链接
OS_EXT OS_EVENT *OSEventFreeList;
OS_EXT OS_EVENT OSEventTbl[OS_MAX_EVENTS];
OSEventTbl构成由OSEventFreeList为头指针,以OSEventPtr为链接指针的单链表,对于OS_EVENT项的获取和收回都是基于链表,由链表OSStart调用
OS_InitEventList构成
对于OS_EVENT基本操作有:
static void OS_InitEventList(void)
功能:初始化信号量列表,将OSEventTbl中的各项构成以OSEventFreeList 为头指针的单链表,以进行各项的插入与操作。
void OS_EventWaitListInit(OS_EVENT *pevent);
功能:仅初始化OS_EVENT中的OSEventGrp OSEventTbl初始化等待的任务列表为空,这里的等待列表和任务就绪表结构和功能类似。
INT8U OS_EventTaskRdy(OS_EVENT *pevent, void *msg,INT8U msk);
功能:将任务挂起,插入到OS_EVENT中等待任务列表中。
void OS_EventT0(OS_EVENT *pevent);
功能:将任务置为就绪,但原因是超时。
具体的代码分析可咨询查看源文件和后面列出的参考书
分析这些代码,会发现代码的实现还是比较简单的,主要涉及到一些任务的挂起和就绪的操作,此时,有可能有这样的问题:
为什么要单独列出OS_EVENT 这样的结构,实际上,不仅是信号量,邮箱模块等也用到OS_EVENT结构,上面的操作时信号量与邮箱共享的一组操作
,需要注意的是,代码涉及到一些任务管理相关操作,如任务的调度,挂起,就绪
2.基于OS_EVENT的信号量实现
在os_sem.c中,实现了完整信号量操作:
OS_EVENT *OSSemCreate(INT16U cnt);
功能:创建一个信号量,从OSEventFreeList 中取出一空闲OS_EVENT,进行必要的初始化,对信号量而言,主要的是初始化OSEventCnt域调用OS_EventWaitLIstInit()
初始化等待任务列表清空。
OS_EVENT *SSemDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
功能:删除一个信号量,收回OS_EVENT,如果OS_EVENT有等待的任务,则调用OS_EventTaskRdy()等待的任务列表任务为就绪。
void OSSemPend(OS_EVENT *pevent,INT16U timeout,INT8U *err);
功能:申请一个信号量,任务在无法获得信号量时会调用OS_EventTaskWait()挂起,再调用OSSched()进行任务切换,超时返回调用OS_EventTo将任务之外就绪。
INT8U OSSemPost(OS_EVEENT *pevent)
功能:查询信号量,可以直接通过pevent 直接获取信号量的信息,但是pevent 很多时候是被多个任务共享的,会随时发生改变,使用额外的pdata可以快速的获取当前信号
量的副本。
INT8U OSSemQuery (OS_EVENT *pevent,OS_SEM_DATA *pdata);
功能:查询信号量,可以直接通过pevent直接获取信号量的信息,但是pevent 很多时候是别多个任务共享的,随时发生改变,
INT16U OSSemAccept(OS_EVENT *pevent);
功能:获取信号量,如果无法获取,立即退出。
参考上面的说明,可以明确信号量的实现与OS_EVENT结构操作函数调用关系,一旦立即这种调用关系,会发现信号量的实现与很
容易理解,对OSEventCnt操作,是通过短暂的开关中断实现,在关中断期间,可以对OS_Event 结构进行操作,而不用担心被中断,
换句话说,通过短暂的开关中断,实现内核内部理解操作。
三 信号量的应用
以下将通过两个例子,实现使用信号量实现资源共享和任务键的同步
1.简单的交通灯控制--信号量进行任务同步的演示,
这里交通灯由6个简单的LED构成,由两个任务分别控制红 绿 黄 三个LED
task1: 绿--黄--红--黄
task0:红--黄--绿--黄--
void task1(void *pdata)
{
INT8U error;
while(1) {
OSSemPend(sem,0,&error);
LED1_ON(GREEN);
LED1_OFF(YELLOW);
OSSemPend(sem,0,&error);
LED1_ON(YELLOW);
LED1_OFF(GREEN);
OSSemPend(sem,0,&error);
LED1_ON(RED);
LED1_OFF(YELLOW);
OSSemPend(sem,0,&error);
LED1_ON(YELLOW);
LED1_OFF(RED);
}
void task0(void *pdata)
{
INT8U error;
t2init();
while(1)
{
LED0_ON(RED);
LED0_OFF(YELLOW);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY0,0);
LED0_ON(YELLOW);
LED0_OFF(RED);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY1,0);
LED0_ON(GREEN);
LED0_OFF(YELLOW);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY2,0);
LED0_ON(YELLOW);
LED0_OFF(GREEN);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY3,0);
}
}
}
int main()
{
OSInit();
LED0_INIT();
LED1_INIT();
sem=OSSemCreate(0);
OSTaskCreate(task0,(void*)0,&stk0[99],3);
OSTaskCreate(task1,(void*)0,&stk0[99],2);
OSStart();
return 0;
}
如航母的代码,创建了task1,task0任务,用信号量进行同步。
交通灯的控制,当然可以用一个任务进行控制,但就用不着信号量了,如果用两个任务,各个任务依次点亮灯,
然后延时,这样两个任务就各自点灯,没有同步,此种方法存在问题是,在系统运行一些时间后,两个任务中其中会运行
过快或过慢,两边的交通灯显示的变换就不同时的。
task0每次在点灯后,向task1发送信号量,通知task1可以进行后续点灯操作,task1在点灯后,会继续申请信号量,这时会
被挂起,不能继续执行,当task0在延时返回,更换led显示,再向task1发送信号量,这样task1又有哪些,更改其LED显示,然后
再挂起,如此反复,
因为task1的点灯操作都是在task0发送信号量进行,所以二者的冬至能做到同步
2)使用信号量闪烁LED - 信号量实现共离资源访问
void task1( void * pdata )
{
INT8U error;
while(1)
{
OSSemPend( sem, 0, &error );
LED1_ON( RED ); // 点灯
OSTimeDlyHMSM( 0, 0, 1, 0 );
OSSemPost( sem );
}
}
void task0( void * pdata )
{
INT8U error;
t2init();
while(1)
{
OSSemPend( sem, 0, &error );
LED1_OFF( RED ); // 关灯
OSTimeDlyHMSM( 0, 0, 1, 0 );
OSSemPost( sem );
}
}
int main()
{
OSInit();
LED0_INIT();
LED1_INIT();
sem = OSSemCreate(1);
OSTaskCreate( task0, (void *)0, &stk0[99], 3 );
OSTaskCreate( task1, ( void *)0, &stk1[99], 2 );
OSStart();
return 0;
}
与前面的代码不同,上面的代码则使用两个任务来控制LED闪烁。task1负责点灯,task0负责熄灯。
因为led只有一盏,在半个周期(1s)内只允许有点灯或者熄灯,即只有task0/task1运行。led此时作为临界资源,一次只允许一个任务占有,直至其放弃使用。
代码中的sem实际上是作为通行证,因为只有一盏led,所以通行证在创建时值1。task1,task0首行通过OSSemPend()申请通行证,当早请到时对LED操作,延时,然后
放弃通行证,此时另外一个任务可以获取通行证再对led操作。可以看出,通过信号量,不会出现多个任务在操作led时的冲突;同时task0和task1在操作LED时也实现了操作的同步.
三、关于信号量的实现的体会
1、Ucos的信号量实现还是比较简单的。这里的简单指的是其代码易读,易理解。简单来说,需要是实现信号量计数与任务挂起列表的实现。对于任务代码而言,信号量提供了共享资源与同步的支持。而信号量本身的实现,则是通过开断中断实现临界区的访问。
2、信号量可以用于共享资源的访问和任务间的同步,实际在应用中如果处理不当,可能会产生“死锁”。死锁问题,在ucos内核中没有提供相应的解决方案,需要用户代码支持。
3、并不是ucos相关的API可在所有情况下可用:ISR不可调用OSSemPend(),OSSemCreate().在使用时需要特别注意.