FreeRTOS_信号量

FreeRTOS信号量

信号量是操作系统总重要的一部分,信号量一般用来进行资源管理和任务同步,FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其应用场景不同,但是有些场景是可以互换着使用的。

信号量简介

信号量常常用于控制对共享资源的访问和任务同步。举一个很常见的例子,某个停车场有100个停车位,这100个停车位大家都可以使用,对于大家说这100个停车位就是共享资源。假设现在这个停车场正常运行,你要把车停到这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值。当这个值到100的时候,说明停车场满了。停车场满的时候你可以等一会儿看看有没有其他的车开出停车场,当有车看出停车场的时候,停车数量就会减一,也就是说信号量减一,此时你就可以把车停进去了,你把车停进去以后,停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例。在这个案例中,使用的就是计数型信号量。在看另一个案例:使用公共电话。我们知道一次只能一个人使用电话,这个时候,公共电话只可能有两个状态:使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。

信号量用于控制共享资源访问的场景相当于一个上锁机制,代码只有获得了这个锁的钥匙才能够执行。

上面我们讲了信号量在共享资源访问中的使用,信号量的另一个重要的应用场合就是任务同步,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候就可以通过向任务发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后,在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候,我们都知道一定要快进快出,中断服务函数里面不能有太多的代码,否则的话会影响中断的实时性。裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用RTOS系统的时候,我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,如果获取到信号量就说明中断发生了,那么开始完成相应的处理,这样做的好处就是中断执行时间非常短。这个例子就是中断与任务之间使用信号量来完成同步,当然,任务与任务之间也可以使用信号量来完成同步。

FreeRTOS中还有一些其他特殊类型的信号量,比如互斥信号量和递归互斥信号量。

二值信号量

二值信号量简介

二值信号量通常用于互斥访问或同步,二值信号量与互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号量更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。

和队列一样,信号量API函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候,由于信号量无效而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话,那么优先级最高的那个任务优先获得信号量,这样当当信号量有效的时候,高优先级的任务就会解除阻塞状态。

二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,这正要就是二值。任务和中断使用这个特殊队列不同在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。

在使用应用中通常会使用一个任务来处理MCU的某个外设,比如网络应用中,一般最简单的方法就是使用一个任务去轮询地查询MCU的ETH(网络相关外设,如STM32的以太网MAC)外设是否有数据,当有数据的时候就处理这个网络数据。这样使用轮询的方式是很浪费CPU资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候,网络任务就进入阻塞态,把CPU让给其他的任务,当有数据的时候网络任务才去执行。现在使用二值信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入阻塞态,而网络中断服务函数(大多数的网络外设都与中断功能,比如STM32的MAC专用DMA中断,通过中断可以判断是否接收到数据)通过释放信号量来通知任务以太网外设接收到了网络数据,网络任务可以去提取处理了。网络任务只是在一直获取二值信号量,它不会释放信号量,而中断服务函数是一直在释放信号量,它不会获取信号量。在中断服务函数中发送信号量可以使用函数 xSemaphoreGiveFromISR() ,也可以使用任务通知功能来替他二值信号量,而且使用任务通知的话,速度更块,代码量更少。

使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及时的处理,这样做相当于推迟了中断处理过程。也可以使用队列替代二值信号量,在外设事件的中断服务函数中获取相关数据,并将相关数据通过队列发送给任务。如果队列无效的话,任务就进入阻塞态,直到队列中有数据,任务接收到数据以后就开始相关的处理过程。

下面几个步骤演示了二值信号量的工作过程。

1. 二值信号量无效

在上图中任务Task通过函数 xSemaphoreTake() 获取信号量,但是此时二值信号量无效,所以任务Task进入阻塞态。

2. 中断释放信号量

此时中断发生了,在中断服务函数中通过函数 xSemaphoreGiveFromISR() 释放信号量,因此信号量变为有效。

3. 任务获取信号量成功

由于信号量已经有效了,所以任务Task获取信号量成功,任务从阻塞态解除,开始执行相关处理过程。

4. 任务再次进入阻塞态

 由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用 xSemaphoreTask() 获取信号量。在执行完第三步以后,二值信号量就已经变为无效了,所以任务将再次进入阻塞态,和第一步一样,直到中断再次发生并且调用函数 xSemaphoreGiveFromISR() 释放信号量。

创建二值信号量

和队列一样,想要使用二值信号量就必须先创建二值信号量,二值信号量创建函数如下表:

函数 描述
vSemaphoreCreateBinary() 动态创建二值信号量,这个是老版本FreeRTOS中使用的创建二值信号量的API函数。
xSemaphoreCreateBinary() 动态创建二值信号量,新版FreeRTOS使用此函数创建二值信号量。
xSemaphoreCreateBinaryStatic() 静态创建二值信号量。

1. 函数 vSemaphoreCreateBinary()

此函数是老版本FreeRTOS中创建二值信号量函数,新版本已经不再使用了,新版本的FreeRTOS使用 xSemaphoreCreateBinary() 来替代此函数,这里还保留这个函数是为了兼容哪些基于老版本FreeRTOS而做的应用层代码。此函数是个宏,具体创建过程是由函数 xQueueGenericCreate() 

猜你喜欢

转载自www.cnblogs.com/doitjust/p/11024718.html