一个二值信号量被用于让任务和中断进行同步。整个执行流程可以描
述为:
1. 中断产生。
2. 中断服务例程启动,给出信号量以使延迟处理任务解除阻塞。
3. 当中断服务例程退出时,延迟处理任务得到执行。延迟处理任务做的第一件事便是
获取信号量。
4. 延迟处理任务完成中断事件处理后,试图再次获取信号量——如果此时信号量无效,
任务将切入阻塞待等待事件发生。
在中断以相对较慢的频率发生的情况下,上面描述的流程是足够而完美的。如果在
延迟处理任务完成上一个中断事件的处理之前,新的中断事件又发生了,等效于将新的
事件锁存在二值信号量中,使得延迟处理任务在处理完上一个事件之后,立即就可以处
理新的事件。也就是说,延迟处理任务在两次事件处理之间,不会有进入阻塞态的机会,
因为信号量中锁存有一个事件,所以当 xSempaphoreTake()调用时,信号量立即有效。
这种情形将在图 图 30 中进行展现。
在图 图 30 中可以看到,一个二值信号量最多只可以锁存一个中断事件。在锁存的事
件还未被处理之前,如果还有中断事件发生,那么后续发生的中断事件将会丢失。如果
用计数信号量代替二值信号量,那么,这种丢中断的情形将可以避免。
就如同我们可以把二值信号量看作是只有一个数据单元的队列一样,计数信号量可
以看作是深度大于 1 的队列。任务其实对队列中存储的具体数据并不感兴趣——其只关
心队列是空还是非空。
计数信号量每次被给出(Given),其队列中的另一个空间将会被使用。队列中的有
效数据单元个数就是信号量的”计数(Count)”值
计数信号量有以下两种典型用法:
1.事件计数
在这种用法中,每次事件发生时,中断服务例程都会“给出(Give)”信号量——信号
量在每次被给出时其计数值加 1。延迟处理任务每处理一个任务都会”获取(Take)”一次
信号量——信号量在每次被获取时其计数值减 1。信号量的计数值其实就是已发生事件
的数目与已处理事件的数目之间的差值。这种机制可以参考 图 31。
用于事件计数的计数信号量,在被创建时其计数值被初始化为 0。
2.资源管理
在这种用法中,信号量的计数值用于表示可用资源的数目。一个任务要获取资源的
控制权,其必须先获得信号量——使信号量的计数值减 1。当计数值减至 0,则表示没
有可用资源。当任务利用资源完成工作后,将给出(归还)信号量——使信号量的计数值
加 1。
用于资源管理的信号量,在创建时其计数值被初始化为可用资源总数。第四章涵盖
了使用信号量来管理资源。
xSemaphoreCreateCounting() API 函数
FreeRTOS 中所有种类的信号量句柄都由声明为 xSemaphoreHandle 类型的变量
保存。
信号量在使用前必须先被创建。使用 xSemaphoreCreateCounting() API 函数来创
建一个计数信号量。
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,
unsigned portBASE_TYPE uxInitialCount );
uxMaxCount 最大计数值。如果把计数信号量类比于队列的话,uxMaxCount 值
就是队列的最大深度。
当此信号量用于对事件计数或锁存事件的话,uxMaxCount 就是可
锁存事件的最大数目。
当此信号量用于对一组资源的访问进行管理的话,uxMaxCount 应
当设置为所有可用资源的总数。
uxInitialCount 信号量的初始计数值。
当此信号量用于事件计数的话,uxInitialCount 应当设置为 0——因
为当信号量被创建时,还没有事件发生。
当 此 信 号 量 用 于 资 源 管 理 的 话 , uxInitialCount 应 当 等 于
uxMaxCount——因为当信号量被创建时,所有的资源都是可用的。
返回值 如果返回 NULL 值,表示堆上内存空间不足,所以 FreeRTOS 无法
为信号量结构分配内存导致信号量创建失败。第五章有提供更多的
内存管理方面的信息。
如果返回非 NULL 值,则表示信号量创建成功。此值应当被保存起
来作为这个的信号量的句柄。