ucos信号量的理解2

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().在使用时需要特别注意.



 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值