找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 10344|回复: 1
打印 上一主题 下一主题
收起左侧

uC/OS-II学习笔记—信号量管理

[复制链接]
跳转到指定楼层
楼主
信号量在资源共享管理、任务同步与通信等方面都有广泛的应用。uC/OS-II单独为信号量管理编写了C语言文件os_sem.c。信号管理的核心函数如下所示:

信号量通过OSSemCreate创建,并分配一个ECB给该信号量。OSSemSet可以单独设置信号量的计数值,这个值在ECB中。被创建的信号量可以通过OSSemDel来删除。OSSemQuery查询信号量的信息。这4种操作针对的对象是信号量,使用的数据结构式ECB。
OSSemPend是任务请求信号量时执行的操作。例如,请求操作串口、打印机或某共享数据结构,如果这时信号量无效,任务就被阻塞,并在ECB中标记自己在等待。
OSSemPost是使用资源的任务由于资源使用结束而提交信号量,这时ECB中被阻塞的任务中优先级最高的因为获得了信号量,被唤醒回到就绪态。
OSSemAccept是任务无等待的请求信号量,也就是说,如果访问的资源有效,那么就访问;如果访问的资源无效也不阻塞自己,而是去做其他工作。因此,OSSemAccept非常适用于中断服务程序(ISR)。
OSSemPendAbort放弃某任务对信号量的等待,任务仍将被唤醒回到就绪态。
所以,以上4中函数的操作对象不仅包括事件控制块(ECB),还将包括任务管理中如任务控制块(TCB)、就绪表等诸多数据结构。
信号量在操作系统初始化的时候并不存在。这时,操作系统中的事件管理数据结构事件控制块(ECB)为全空,所有的事件控制块都在ECB空闲链表中排队。信号量的建立函数OSSemCreate将使用一个并配置一个ECB,使其具备信号量的属性。创建信号量的函数OSSemCreate的代码如下所示:

首先,如果是在中断服务程序中,那么中断服务程序按照规范,应该先调用OSIntEnter,该函数将中断嵌套数OSIntNesting加1,因此只要是在中断服务中调用OSSemCreate,OSIntNesting>0u就应该成立,OSSemCreate就返回。OSEventFreeList指向ECB空闲链表的表头,如果OSEventFreeList的值是0,即空指针,那么ECB链表就为空。这说明已经没有空闲的ECB可供使用了。在这种情况下,当然就不能创建信号量或任何其他的事件。当将宏OS_MAX_EVENTS定义的太小满足不了需要的时候就会出现这种情况。另外,还需要注意的是事件没有用时要尽快释放还给系统。在OSEventFreeList的值不为0的情况下,既然将空闲ECB链表的表头分配给要创建的信号量事件,就需要将空闲ECB链表的第一个ECB取下来,这时OSEventList应该指向第二个ECB。接下来的5条语句

都是对取得的ECB进行赋值。假设信号量值为5,则赋值后的ECB应该如下图所示:

宏OS_EVENT_TYPE_SEM的值是3,所以ECB中OSEventType的值为3。假设该信号量为创建的第一个事件,那么事件空闲任务链表将去掉第一个事件控制块如下所示:

另外,应用程序在调用OSSemCreate创建了一个信号量后如何找到该ECB以进行操作呢?这个也没有问题,因为OSSemCreate返回了该ECB的指针。代码中也可以看到,如果创建失败,那么返回的是空指针,应用程序可根据返回值是否为空指针判断是否创建失败。如果成功,根据该指针执行其他的如等待信号量、提交信号量等操作。
前面提到系统中的信号量如果不再使用了就应该尽快删除,否则分配多少个ECB也是不够用的。信号量的删除函数时OSSemDel。删除一个信号量要涉及很多方面,因此OSSemDel并不简单。OSSemDel的参数是ECB指针pevent、整形的删除选项opt和用来返回结果的指向整形的指针perr。其中,opt的值为OS_DEL_NO_PEND表示只有当没有任务等待该事件的时候才允许删除,opt的值为OS_DEL_ALWAYS表示无论如何都允许删除。OSSemDel程序如下所示:



可以单刀,删除信号量比创建一个信号量更复杂,首先要进行很多参数检查,检查传递来的参数的正确性。例如,是否是在中断中删除信号量、ECB的属性是否是信号量等。
使用局部变量tasks_waiting来保存是否有任务等待信号量。判断的方法是通过事件等待组是否为0.如果tasks_waiting=OS_TRUE,则表示有任务等待信号量,如果tasks_waiting=OS_FALSE,则表示没有任务等待信号量。
然后根据选项opt决定程序的分支。如果为OS_DEL_NO_PEND,则表示只有在没有事件等待的时候才允许删除信号量,因此,在tasks_waiting = OS_TRUE时不能做任何事情,返回该ECB的指针,表示删除失败了,并在err中填写了出错的原因。
如果opt为OS_DEL_ALWAYS,那么,先把ECB初始化后归还给空闲ECB链表,然后将所有等待该信号量的任务都用OS_EventTaskRdy来就绪,采用的方法是使用while循环,只要OSEventGrp不为0就循环下去,从高优先级到低优先级的任务一个一个就绪,事件等待表中的任务也一个一个减少。等到OSEventGrp为0时循环就结束了,所有等待该事件的任务除了被挂起的之外(OS_EventTaskRdy不就绪挂起的任务),全部都就绪了。这时如果判定tasks_waiting为OS_TRUE,知道有任务被就绪了,就执行一次任务调度来让高优先级的就绪任务获得运行,否则不需要进行任务调度了。
操作系统程序的编写是非常细致的,如果opt不是这两个值中的一个,那就是选项不对,什么也不做,标记错误信息后直接返回原来的ECB指针。
请求信号量又称等待信号量。等待信号量的参数为3个,分别是ECB的指针pevent,32位无符号整数超时时间timeout和用来返回结果的指向整形的指针perr。等待信号量函数OSSemPend的定义如下所示:



信号量的等待函数代码稍多一些,读懂了该代码,其他的事件如互斥信号量、消息等的处理都很相似。
首先还是参数检查,这里增加了一个如果调度器上锁不能等待信号量的限制。perr是以指针的形式传递过来的,其实是用它来返回处理的结果。例如,OS_ERR_PEND_LOCKED,表示是因为调度器上锁了而无功而返。
然后判断OSEventCnt信号量的值,入股该值大于0,则可以访问资源。因为本任务将占有一个资源,因此将OSEventCnt减1,信号量指示的资源数减少一个。然后给*perr赋值为OS_ERR_NONE,表示操作正常。然后本函数返回。应用程序看到OS_ERR_NONE就可以大胆地去使用资源了。
如果不是,那就比较麻烦了!先在TCB中的OSTCBStat打个标记,表示本任务的状体是请求信号量。OSTCBStatPend赋值为OS_STAT_PEND_OK,等待状态正常。把延时时间这个参数给OSTCBDly。
调用OS_EventTaskWait在事件等待表、等待组中占一个地方,在就绪表和就绪组中取消就绪标志。然后执行一次任务调度,彻底被阻塞掉。轮到其他任务运行了。当再运行到此处的时候已经发生了很多事情,本任务是不知道的。总之,任务被从阻塞态唤醒回到就绪态,又获得了运行。查看OSTCBStatPend,如果是OS_STAT_PEND_OK,是由于等待的信号量有效(有其他任务释放了信号量),因为本任务在ECB的事件等待表中有记录,所以被唤醒并得到了运行,虽然经历磨难,但是还是可以去访问资源了。如果是OS_STAT_PEND_ABORT,在获得信号量之前取消了等待,因此不能访问资源,在perr中填写信号为OS_ERR_PEND_ABORT。如果是OS_STAT_PEND_TO或其他,表示超时了,时间到了还没有得到信号量,表示失败了,也不能访问资源!在perr中填写信号为OS_ERR_TIMEOUT。另外需要注意的是,执行了OS_EventTaskRemove,在事件等待表和事件等待组中清除了本任务的等待信息。
无论如何,请求信号量结束了,最后清理一下比较混乱的TCB中的状态标志和ECB指针,结束本函数的运行。
当任务A获得信号量之后将信号量数字减1,然后就可以访问资源R。这时,如果信号量的值为0,任务B如果也要访问资源R,必须等待信号量,因此将任务B阻塞。任务A在对资源的访问完成之后,应将信号量的值加1。因为资源已经可以被其他的任务访问了,因此应该将任务B唤醒,使任务B就绪。再复杂一些,访问资源的任务有2个以上,资源R可以同时被N个任务访问,因此信号量的值在最开始创建的时候应该等于N。当任务A访问信号量时,信号量值变为N-1,任务B又访问,信号量等于N-2,当第M个任务访问时,信号量等于N-M。当N-M=0时,也就是当N=M时,如果第N+1个任务来访问资源R,那么它必须等待。当任何一个任务(如第2个)访问资源完成后,应该唤醒第N+1个任务,让其他资源访问。当第N+1个任务访问完成之后,因为没有其他的任务等待信号量,只需要简单地将信号量值加1即可。
提交信号量的函数时OSSemPost,参数是信号量所在的ECB的指针。代码如下所示:

代码中首先进行参数检查,然后判断是否有任务在等待该信号量。如果有,那么就唤醒阻塞中的最高优先级的任务,方法就是调用OS_EventTaskRdy,然后进行任务调度之后返回即可。如果没有,就简单地将信号量加1。
在中断服务程序和有些用户任务中,需要无等待的请求信号量。也就是说,使用信号量请求资源,当没有可用的资源,信号量为0时,并不阻塞自己,而是继续执行其他代码。OSSemAccept就是无等待的请求信号量函数,参数是请求信号量的ECB指针,返回值是当前信号量的数值。当有有效的资源时,返回值大于0,否则返回0.代码如下所示:

代码中首先进行参数检查,然后将信号量的值赋值给局部变量cnt,如果cnt>0说明资源有效或信号量有效,因此将信号量的值减1,然后返回cnt,就可以访问资源的代码了。如果函数返回值为0,说明要么参数检查失败要么资源被其他任务占用而不能访问,都不能执行访问资源的代码。
放弃等待信号量并非放弃本任务对信号量的等待。可以采用反证法:如果是放弃本任务对信号量的等待,那么本任务应该处于阻塞状态,一个处于阻塞状态的任务得不到运行,怎么能执行放弃等待信号量的代码呢?因此,一定是放弃其他任务对一个信号量的等待。放弃等待信号量的第一个参数是ECB的指针。这个ECB必须是信号量的,如果不是则返回。如果这个ECB的事件等待表中没有任务等待,那么也无须做什么操作。否则,根据第二个参数opt的值分两种情况处理。一种是opt的值是宏OS_PEND_OPT_BROADCAST,那么就要将等待该信号量的所有任务就绪。另一种是opt的值是OS_PEND_OPT_NONE或其他值,只将等待该信号量的最高优先级的任务就绪。另一个参数是返回结果的指向整形的指针perr,使用方法与前面类似。
放弃等待信号量函数OSSemPendAbort代码如下所示:


分析该函数流程如下:
(1)参数检查,如果ECB指针无效或ECB的类型不是信号量类型,返回参数检查错误信息。
(2)如果pevent->OSEventGrp为0说明没有任务等待信号量,返回0.
(3)否则根据参数opt(选项)进行分支转移,如果为OS_PEND_OPT_BROADCAST,使用while语句循环地将等待该信号量的每个任务用OS_EventTaskRdy来取消等待并使其就绪(除非任务还被挂起);如果为其他值则只将最高优先级取消等待并使之就绪。两种情况下都返回取消等待信号量的任务数。
总之,OSSemPendAbort取消任务对某信号量的等待,操作的对象是ECB等待中的任务。一般在极为特殊的情况下(如要删除一个任务,而这个任务当前在等待信号量时),才使用该函数。
操作系统提供了直接设置信号量值的函数OSSemSet。一般情况下无须使用该函数设置信号量的值,应该在信号量创建的时候初始化信号量的值。当一个信号量的值在创建之后为N,每次有任务请求信号量就将该值减1,反之,将该值加1,一般情况下是不允许随便改动的。但是在极其特殊的情况下,因为某种特殊的需要(如突然增加了其他的资源),需要修改资源数N,可采用OSSemSet直接对信号量赋值,但条件是这时没有任务在等待该信号量。OSSemset函数代码如下所示:


该函数比较简单,进行参数检查之后,查看该信号的值是否大于0,如果大于0则说明米有任务在等待该信号量,因此可以修改信号量值。否则,查看是否有任务等待,如果没有任务等待仍可修改信号量值,否则不允许修改信号量的值。
信号量状态查询将ECB中关于信号量的信息复制到另一个数据结构信号量数据OS_SEM_DATA,信号量数据OS_SEM_DATA的声明如下所示:

信号量状态查询函数OSSemQuery的代码如下所示:

该函数比较简单,进行参数检查之后,将ECB中的事件等待组、事件等待表和信号量值的内容,完全复制到信号量数据OS_SEM_DATA中

55.png (14.45 KB, 下载次数: 118)

55.png
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏2 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:56293 发表于 2013-10-25 10:51 | 只看该作者
请问楼主的程序使用什么软件打开的?有颜色看的方便多了~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表