uCOS-II任务间通信之信号量
信号量是什么?信号量有什么用?
信号量是可以用来表示一个或多个事件的发生,还可以用来对共享资源的访问。
uCOS-II提供了5个对信号量进行操作的函数。如下所示:
1. 建立一个信号量 -- OSSemCreate()
2. 等待一个信号量 -- OSSemPend()
3. 发送一个信号量 -- OSSemPost()
4. 无等待地请求一个信号量 -- OSSemAccept()
5. 查询一个信号量的当前状态 -- OSSemQuery()
在MCU看来,创建一个信号量就是申请一个事件控制块,接着初始化这个事件控制块。
首先,它从空闲任务控制块链表中得到一个事件控制块(1),并对空闲事件控制链表的指针进行适当的调整,使它指向下一个空闲的事件控制块(2)。如果这时有任务控制块可用(3),就将该任务控制块的事件类型设置成信号量OS_EVENT_TYPE_SEM(4)。其它的信号量操作函数OSSem???()通过检查该域来保证所操作的任务控制块类型的正确。例如,这可以防止调用OSSemPost()函数对一个用作邮箱的任务控制块进行操作。
接着,用信号量的初始值对任务控制块进行初始化(5)(如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0,如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1,并调用OSEventWaitListInit()函数对事件控制任务控制块的等待任务列表进行初始化(6)。因为信号量正在被初始化,所以这时没有任何任务等待该信号量。
最后,OSSemCreate()返回给调用函数一个指向任务控制块的指针。以后对信号量的所有操作,如OSSemPend(), OSSemPost(), OSSemAccept()和OSSemQuery()都是通过该指针完成的。因此,这个指针实际上就是该信号量的句柄。如果系统中没有可用的任务控制块,OSSemCreate()将返回一个NULL指针。
创建好一个信号之后,可以调用OSSemQuery()查询一个信号的状态。该函数有两个参数:一个是指向信号量对应事件控制块的指针pevent,另一个是指向用于记录信号量信息的数据结构OS_SEM_DATA。
简单来说就是把信号量对应的事件控制块的信息复制到数据结构OS_SEM_DATA。
在MCU看来,这么多代码就是做了一个重要的事情:就是将任务控制块中的状态标志.OSTCBStat置1,也就是把任务置于睡眠状态。这样任务就处于等待信号量来激活任务的状态了。可以这样理解,当任务等待一个信号量的时候,任务是被挂起了,需要等待发送信号量来激活。
下面具体分析下代码的实现过程。
首先检查指针pevent所指的任务控制块是否是由OSSemCreate()建立的(1)。如果信号量当前是可用的(信号量的计数值大于0)(2),将信号量的计数值减1 (3),然后函数将“无错”错误代码返回给它的调用函数。显然,如果正在等待信号量,这时的输出正是我们所希望的,也是运行OSSemPend()函数最快的路径。
如果此时信号量无效(计数器的值是0),OSSemPend()函数要进一步检查它的调用函数是不是中断服务子程序(4)。在正常情况下,中断服务子程序是不会调用OSSemPend()函数的。这里加入这些代码,只是为了以防万一。当然,在信号量有效的情况下,即使是中断服务子程序调用的OSSemPend(),函数也会成功返回,不会出任何错误。
OSSemPend()函数通过将任务控制块中的状态标志.OSTCBStat置1,把任务置于睡眠状态(5),等待时间也同时置入任务控制块中(6),该值在OSTimeTick()函数中被逐次递减。注意,OSTimeTick()函数对每个任务的任务控制块的.OSTCBDly域做递减操作(只要该域不为0)。真正将任务置入睡眠状态的操作在OSEventTaskWait()函数中执行(7)。
因为当前任务已经不是就绪态了,所以任务调度函数将下一个最高优先级的任务调入,准备运行(8)。当信号量有效或者等待时间到后,调用OSSemPend()函数的任务将再一次成为最高优先级任务。这时OSSched()函数返回。这之后,OSSemPend()要检查任务控制块中的状态标志,看该任务是否仍处于等待信号量的状态(9)。如果是,说明该任务还没有被OSSemPost()函数发出的信号量唤醒。事实上,该任务是因为等待超时而由TimeTick()函数把它置为就绪状态的。这种情况下,OSSemPend()函数调用OSEventTO()函数将任务从等待任务列表中删除(10),并返回给它的调用任务一个“超时”的错误代码。如果任务的任务控制块中的OS_STAT_SEM标志位没有置位,就认为调用OSSemPend()的任务已经得到了该信号量,将指向信号量ECB的指针从该任务的任务控制块中删除,并返回给调用函数一个“无错”的错误代码(11)。
理解了等待信号量函数的源码后理解发送信号量函数的源码也就很容易了。
以上源码的作用简单来说就是查找有没有任务在等待这个信号量,如果有,就把该任务从睡眠态拉回就绪态。
首先检查参数指针pevent指向的任务控制块是否是OSSemCreate()函数建立的(1),接着检查是否有任务在等待该信号量(2)。如果该任务控制块中的.OSEventGrp域不是0,说明有任务正在等待该信号量。这时,就要调用函数OSEventTaskRdy(),把其中的最高优先级任务从等待任务列表中删除(3)并使它进入就绪状态。然后,调用OSSched()任务调度函数检查该任务是否是系统中的最高优先级的就绪任务(4)。如果是,这时就要进行任务切换[当OSSemPost()函数是在任务中调用的],准备执行该就绪任务。如果不是,OSSched()直接返回,调用OSSemPost()的任务得以继续执行。如果这时没有任务在等待该信号量,该信号量的计数值就简单地加1 (5)。
信号量是什么?信号量有什么用?
信号量是可以用来表示一个或多个事件的发生,还可以用来对共享资源的访问。
uCOS-II提供了5个对信号量进行操作的函数。如下所示:
1. 建立一个信号量 -- OSSemCreate()
2. 等待一个信号量 -- OSSemPend()
3. 发送一个信号量 -- OSSemPost()
4. 无等待地请求一个信号量 -- OSSemAccept()
5. 查询一个信号量的当前状态 -- OSSemQuery()
OSSemCreate()的实现代码如下:
在MCU看来,创建一个信号量就是申请一个事件控制块,接着初始化这个事件控制块。
首先,它从空闲任务控制块链表中得到一个事件控制块(1),并对空闲事件控制链表的指针进行适当的调整,使它指向下一个空闲的事件控制块(2)。如果这时有任务控制块可用(3),就将该任务控制块的事件类型设置成信号量OS_EVENT_TYPE_SEM(4)。其它的信号量操作函数OSSem???()通过检查该域来保证所操作的任务控制块类型的正确。例如,这可以防止调用OSSemPost()函数对一个用作邮箱的任务控制块进行操作。
接着,用信号量的初始值对任务控制块进行初始化(5)(如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0,如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1,并调用OSEventWaitListInit()函数对事件控制任务控制块的等待任务列表进行初始化(6)。因为信号量正在被初始化,所以这时没有任何任务等待该信号量。
最后,OSSemCreate()返回给调用函数一个指向任务控制块的指针。以后对信号量的所有操作,如OSSemPend(), OSSemPost(), OSSemAccept()和OSSemQuery()都是通过该指针完成的。因此,这个指针实际上就是该信号量的句柄。如果系统中没有可用的任务控制块,OSSemCreate()将返回一个NULL指针。
创建好一个信号之后,可以调用OSSemQuery()查询一个信号的状态。该函数有两个参数:一个是指向信号量对应事件控制块的指针pevent,另一个是指向用于记录信号量信息的数据结构OS_SEM_DATA。
简单来说就是把信号量对应的事件控制块的信息复制到数据结构OS_SEM_DATA。
OSSemQuery()程序代码如下:
当我们创建好了信号量之后,就可以使用信号量了。使用信号量需要调用OSSemPost()和OSSemPend()。关于具体怎么使用信号量,就得先看看这两个系统函数的代码。
先看等待信号量OSSemPend()的代码:
在MCU看来,这么多代码就是做了一个重要的事情:就是将任务控制块中的状态标志.OSTCBStat置1,也就是把任务置于睡眠状态。这样任务就处于等待信号量来激活任务的状态了。可以这样理解,当任务等待一个信号量的时候,任务是被挂起了,需要等待发送信号量来激活。
下面具体分析下代码的实现过程。
首先检查指针pevent所指的任务控制块是否是由OSSemCreate()建立的(1)。如果信号量当前是可用的(信号量的计数值大于0)(2),将信号量的计数值减1 (3),然后函数将“无错”错误代码返回给它的调用函数。显然,如果正在等待信号量,这时的输出正是我们所希望的,也是运行OSSemPend()函数最快的路径。
如果此时信号量无效(计数器的值是0),OSSemPend()函数要进一步检查它的调用函数是不是中断服务子程序(4)。在正常情况下,中断服务子程序是不会调用OSSemPend()函数的。这里加入这些代码,只是为了以防万一。当然,在信号量有效的情况下,即使是中断服务子程序调用的OSSemPend(),函数也会成功返回,不会出任何错误。
OSSemPend()函数通过将任务控制块中的状态标志.OSTCBStat置1,把任务置于睡眠状态(5),等待时间也同时置入任务控制块中(6),该值在OSTimeTick()函数中被逐次递减。注意,OSTimeTick()函数对每个任务的任务控制块的.OSTCBDly域做递减操作(只要该域不为0)。真正将任务置入睡眠状态的操作在OSEventTaskWait()函数中执行(7)。
因为当前任务已经不是就绪态了,所以任务调度函数将下一个最高优先级的任务调入,准备运行(8)。当信号量有效或者等待时间到后,调用OSSemPend()函数的任务将再一次成为最高优先级任务。这时OSSched()函数返回。这之后,OSSemPend()要检查任务控制块中的状态标志,看该任务是否仍处于等待信号量的状态(9)。如果是,说明该任务还没有被OSSemPost()函数发出的信号量唤醒。事实上,该任务是因为等待超时而由TimeTick()函数把它置为就绪状态的。这种情况下,OSSemPend()函数调用OSEventTO()函数将任务从等待任务列表中删除(10),并返回给它的调用任务一个“超时”的错误代码。如果任务的任务控制块中的OS_STAT_SEM标志位没有置位,就认为调用OSSemPend()的任务已经得到了该信号量,将指向信号量ECB的指针从该任务的任务控制块中删除,并返回给调用函数一个“无错”的错误代码(11)。
接下来看下发送信号量的代码:
理解了等待信号量函数的源码后理解发送信号量函数的源码也就很容易了。
以上源码的作用简单来说就是查找有没有任务在等待这个信号量,如果有,就把该任务从睡眠态拉回就绪态。
首先检查参数指针pevent指向的任务控制块是否是OSSemCreate()函数建立的(1),接着检查是否有任务在等待该信号量(2)。如果该任务控制块中的.OSEventGrp域不是0,说明有任务正在等待该信号量。这时,就要调用函数OSEventTaskRdy(),把其中的最高优先级任务从等待任务列表中删除(3)并使它进入就绪状态。然后,调用OSSched()任务调度函数检查该任务是否是系统中的最高优先级的就绪任务(4)。如果是,这时就要进行任务切换[当OSSemPost()函数是在任务中调用的],准备执行该就绪任务。如果不是,OSSched()直接返回,调用OSSemPost()的任务得以继续执行。如果这时没有任务在等待该信号量,该信号量的计数值就简单地加1 (5)。