信号量和互斥信号量
注:具体参考文档《FreeRTOS实时内核使用指南-中文.PDF》
3.2 延迟中断处理
二值信号量可以在某个特殊的中断发生时,让任务解除阻塞,相当于让任务与中断
同步。这样就可以让中断事件处理量大的工作在同步任务中完成,中断服务例程(ISR)
中只是快速处理少部份工作。如此,中断处理可以说是被”推迟(deferred)”到一个”处理
(handler)”任务。
如果某个中断处理要求特别紧急,其延迟处理任务的优先级可以设为最高,以保证
延迟处理任务随时都抢占系统中的其它任务。这样,延迟处理任务就成为其对应的 ISR
退出后第一个执行的任务,在时间上紧接着 ISR 执行,相当于所有的处理都在 ISR 中
完成一样。这种方案在图 26 中展现。
延迟处理任务对一个信号量进行带阻塞性质的”take”调用,意思是进入阻塞态以等
待事件发生。当事件发生后,ISR 对同一个信号量进行”give”操作,使得延迟处理任务
解除阻塞,从而事件在延迟处理任务中得到相应的处理。
“获取(Taking,带走,按通常的说法译为获取)”和”给出(Giving)”信号量从概念上讲,
在不同的应用场合有不同的含义。在经典的信号量术语中,获取信号量等同于一个 P()
操作,而给出信号量等同于一个 V()操作。
译者注: P 源自荷兰语 Parsseren,即英语的 Pass;V 源自荷兰语 Verhoog,即英语
的 Increment。P(S)/V(S)操作是信号量的两个原子操作,S 为信号量 Semaphore,相
当于一个标志,可以代表一个资源,一个事件等等,初始值视应用场合而定。P(S)/V(S)
原子操作有如下行为:
P(S) : IF (S <= 0) THEN 将本线程加入 S 的等待队列
S = S – 1
V(S) : S = S + 1
IF (S > 0) THEN 唤醒某个等待线程
在这种中断同步的情形下,信号量可以看作是一个深度为 1 的队列。这个队列由于
最多只能保存一个数据单元,所以其不为空则为满(所谓”二值”)。延迟处理任务调用
xSemaphoreTake()时,等效于带阻塞时间地读取队列,如果队列为空的话任务则进入
阻塞态。当事件发生后,ISR 简单地通过调用 xSemaphoreGiveFromISR()放置一个令
牌(信号量)到队列中,使得队列成为满状态。这也使得延迟处理任务切出阻塞态,并移
除令牌,使得队列再次成为空。当任务完成处理后,再次读取队列,发现队列为空,又
进入阻塞态,等待下一次事件发生。整个流程在图 27 中有所展现。
如图 27 所示,中断给出信号量,甚至是在信号量第一次被获取之前就给出;而任
务在获取信号量之后再也不给回来。这就是为什么说这种情况与读写队列相似。这也经
常会给大家造成迷惑,因为这种情形和其它信号量的使用场合大不相同。在其它场合下,
任务获得(Take)了信号量之后,必须得给(Give)回来——如同第四章描述一样。
3.3 计数信号量
例 12 演示了一个二值信号量被用于让任务和中断进行同步。整个执行流程可以描
述为:
1. 中断产生。
2. 中断服务例程启动,给出信号量以使延迟处理任务解除阻塞。
3. 当中断服务例程退出时,延迟处理任务得到执行。延迟处理任务做的第一件事便是
获取信号量。
4. 延迟处理任务完成中断事件处理后,试图再次获取信号量——如果此时信号量无效,
任务将切入阻塞待等待事件发生。
在中断以相对较慢的频率发生的情况下,上面描述的流程是足够而完美的。如果在
延迟处理任务完成上一个中断事件的处理之前,新的中断事件又发生了,等效于将新的
事件锁存在二值信号量中,使得延迟处理任务在处理完上一个事件之后,立即就可以处
理新的事件。也就是说,延迟处理任务在两次事件处理之间,不会有进入阻塞态的机会,
因为信号量中锁存有一个事件,所以当 xSempaphoreTake()调用时,信号量立即有效。
这种情形将在图 30 中进行展现。
在图 30 中可以看到,一个二值信号量最多只可以锁存一个中断事件。在锁存的事
件还未被处理之前,如果还有中断事件发生,那么后续发生的中断事件将会丢失。如果
用计数信号量代替二值信号量,那么,这种丢中断的情形将可以避免。
就如同我们可以把二值信号量看作是只有一个数据单元的队列一样,计数信号量可
以看作是深度大于 1 的队列。任务其实对队列中存储的具体数据并不感兴趣——其只关
心队列是空还是非空。
计数信号量每次被给出(Given),其队列中的另一个空间将会被使用。队列中的有
效数据单元个数就是信号量的”计数(Count)”值。
计数信号量有以下两种典型用法:
1.事件计数
在这种用法中,每次事件发生时,中断服务例程都会“给出(Give)”信号量——信号
量在每次被给出时其计数值加 1。延迟处理任务每处理一个任务都会”获取(Take)”一次
信号量——信号量在每次被获取时其计数值减 1。信号量的计数值其实就是已发生事件
的数目与已处理事件的数目之间的差值。这种机制可以参考图 31。
用于事件计数的计数信号量,在被创建时其计数值被初始化为 0。
2.资源管理
在这种用法中,信号量的计数值用于表示可用资源的数目。一个任务要获取资源的
控制权,其必须先获得信号量——使信号量的计数值减 1。当计数值减至 0,则表示没
有可用资源。当任务利用资源完成工作后,将给出(归还)信号量——使信号量的计数值
加 1。
用于资源管理的信号量,在创建时其计数值被初始化为可用资源总数。第四章涵盖
了使用信号量来管理资源。
4.3 互斥量(及二值信号量)
互斥量是一种特殊的二值信号量,用于控制在两个或多个任务间访问共享资源。单
词MUTEX(互斥量)源于”MUTual EXclusion”。
在用于互斥的场合,互斥量从概念上可看作是与共享资源关联的令牌。一个任务想
要合法地访问资源,其必须先成功地得到(Take)该资源对应的令牌(成为令牌持有者)。
当令牌持有者完成资源使用,其必须马上归还(Give)令牌。只有归还了令牌,其它任务
才可能成功持有,也才可能安全地访问该共享资源。一个任务除非持有了令牌,否则不
允许访问共享资源。这种机制在图 36 中展示。
虽然互斥量与二值信号量之间具有很多相同的特性,但图 36 展示的情形(互斥量用
于互斥功能)完全不同于图 30 展示的情形(二值信号量用于同步)。两者间最大的区别在
于信号量在被获得之后所发生的事情:
- 用于互斥的信号量必须归还。
- 用于同步的信号量通常是完成同步之后便丢弃,不再归还。