信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步, FreeRTOS
中信号量又分为二值信号量、 计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其
应用场景不同,但有些应用场景是可以互换着使用的。
信号量常常用于控制对共享资源的访问和任务同步。举一个很常见的例子,某个停车场有
100 个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。
假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车
了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当
这个值到 100 的时候说明停车场满了。停车场满的时你可以等一会看看有没有其他的车开出停
车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车
停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。 这就是一个典型的使用
信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。再看另外一个案例:
使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:
使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。
信号量用于控制共享资源访问的场景相当于一个上锁机制, 代码只有获得了这个锁的钥匙
才能够执行。
上面我们讲了信号量在共享资源访问中的使用,信号量的另一个重要的应用场合就是任务
同步,用于任务与任务或中断与任务之间的同步。 在执行中断服务函数的时候可以通过向任务
发送信号量来通知任务它所期待的事件发生了, 当退出中断服务函数以后在任务调度器的调度
下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函
数里面不能放太多的代码,否则的话会影响的中断的实时性。 裸机编写中断服务函数的时候一
般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使
用 RTOS 系统的时候我们就可以借助信号量完成此功能, 当中断发生的时候就释放信号量,中
断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获
取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间
非常短。 这个例子就是中断与任务之间使用信号量来完成同步,当然了, 任务与任务之间也可
以使用信号量来完成同步。
二值信号量通常用于互斥访问或同步, 二值信号量和互斥信号量非常类似,但是还是有一
些细微的差别, 互斥信号量拥有优先级继承机制, 二值信号量没有优先级继承。 因此二值信号
另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问,
有关互斥信号量的内容后面会专门讲解,本节只讲解二值信号量在同步中的应用。
和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时
候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一
一个信号量上的话那么优先级最高的哪个任务优先获得信号量, 这样当信号量有效的时候高优
先级的任务就会解除阻塞状态。
二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空
的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。
在实际应用中通常会使用一个任务来处理 MCU 的某个外设,比如网络应用中,一般最简
单的方法就是使用一个任务去轮询的查询 MCU 的 ETH(网络相关外设,如 STM32 的以太网
MAC)外设是否有数据,当有数据的时候就处理这个网络数据。这样使用轮询的方式是很浪费
CPU 资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候网络任
务就进入阻塞态,把 CPU 让给其他的任务,当有数据的时候网络任务才去执行。现在使用二值
信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入
阻塞态,而网络中断服务函数(大多数的网络外设都有中断功能,比如 STM32 的 MAC 专用 DMA
中断,通过中断可以判断是否接收到数据)通过释放信号量来通知任务以太网外设接收到了网络
数据,网络任务可以去提取处理了。 网络任务只是在一直的获取二值信号量,它不会释放信号
量,而中断服务函数是一直在释放信号量,它不会获取信号量。在中断服务函数中发送信号量
可以使用函数 xSemaphoreGiveFromISR(), 也可以使用任务通知功能来替代二值信号量,而且使
用任务通知的话速度更快,代码量更少,有关任务通知的内容后面会有专门的章节介绍。
使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及
时的处理,这样做相当于推迟了中断处理过程。 也可以使用队列来替代二值信号量,在外设事
件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话
任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。
由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数
xSemaphoreTake()获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务
将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR()
释放信号量。
同队列一样,要想使用二值信号量就必须先创建二值信号量。
因篇幅问题不能全部显示,请点此查看更多更全内容