目录
2.2.1 vSemaphoreCreateBinary()
2.2.2 xSemaphoreCreateBinary()
2.2.3 xSemaphoreCreateBinaryStatic()
2.4.2 函数 xSemaphoreGiveFromISR()
2.5.2 函数 xSemaphoreTakeFromISR()
信号量是操作系统中重要的一部分,信号量一般用于进行资源管理和任务同步,在 FreeRTOS 中,信号量可以分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其应用场景不同,但是有些应用场景是可以互换着使用的,从这里开始,我们进行信号量的学习!!!
1. 信号量简介
信号量常常用于控制对共享资源的访问和任务同步。举个很常见的例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以使用,对于大家来说,这 100 个停车位就是共享资源。现在假设这个停车场正常运行,我们要把车停到这个停车场首先肯定要先看一下现在已经停了多少车?还有没有停车位?当前的停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到达 100 的时候就说明停车场满了。停车场满的时候我们可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会加一,也就是信号量加一。
这是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。我们再看另外一个,使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。
信号量的另一个重要的应用场合是任务同步,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候可以通过向任务发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话就会影响中断的实时性。裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,中断服务函数不作具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了,那么就开始完成相应的处理,这么做的好处就是中断执行的时间非常短。
这里区别信号量和队列?
队列是用来传输数据的,而信号量是用来传输状态的;
信号量表示一种状态:
如果信号量为 0 和 1,那么此时称为二值信号量;
如果信号量为 1 2 3 4 5 6 7 8 9 10,那么此时称为计数型信号量;
队列:
可以容纳多个数据;
创建队列有两部分内存:队列结构体 + 队列项存储空间;
写入队列:当队列满时,可阻塞;
读取队列:当队列为空时,可阻塞;
信号量:仅存放计数值,无法存放其他数据;
创建信号量,只需分配信号量结构体;
释放信号量:不可阻塞,计数值++;当计数值为最大值时,返回失败;
获取信号量:计数值--;当没有资源时,可阻塞;
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问?
在计算机中,为了合理的进行内存管理,引入共享资源的概念。同步问题就是说,比如我让计算机去算 A + B*C,我们肯定知道要先算 B*C,然后再去算 A + B*C 的结果,但是如果我们不加以限制,计算机不会这样,计算机会先去计算 A + B,然后再去计算乘法,因此,同步就是为了保证任务与任务之间的运行次序,比如,我先给乘法运行的任务一个信号量,乘法运算的任务收到这个信号量,开始在任务中执行乘法运算;然后同样的过程进行加法运算,这样就可以保证乘法运算是在加法运算之前进行的;共享资源就是计算机中的所有任务都可以使用的资源,但是同一时刻只能有一个任务进行使用,你能想象打印机第一页打印 A 文档,第二页打印 B 文档吗?所以同步机制对于操作系统而言是至关重要的!
2. 二值信号量
2.1 二值信号量简介
二值信号量通常用于互斥访问或同步。二值信号量和互斥信号量非常类似,但是也是存在一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号量更加适用于同步(任务与任务或任务与中断的同步),而互斥信号量适合于简单的互斥访问。
和队列一样,信号量 API 函数允许设置一个阻塞时间。阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话,那么优先级最高的那个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。
二值信号量其实就是一个只有一个队列项的队列(队列长度为 1,该队列就只有空 0 和满 1 两种情况,这就是二值;就像硬币的正反面一样)。这个特殊的队列要么是满的,要么是空的,正如其名,二值?任务与中断使用这个特殊的队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的即可。可以利用这个机制完成任务与中断之间的同步。
在实际应用中通常会使用一个任务来处理 MCU 的某个外设,比如网络应用中,一般最简单的方法就是使用一个任务去轮询的查询 MCU 的 ETH(网络相关外设,如 STM32 的以太网 MAC)外设是否有数据,当有数据的时候就处理这个网络数据。这样的使用轮询的方式是很浪费 CPU 资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候网络任务就进入阻塞态,把 CPU 让给其他任务,当有数据的时候网络任务才去执行。现在使用二值信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入阻塞态,而网络中断服务函数(大多数的网络外设都有中断功能,比如 STM32 的 MAC 专用 DMA 中断,通过中断可以判断是否接收到了数据)通过释放信号量来通知任务以太网外设接收到了网络数据,网络任务可以去提取处理了。网络任务只是在一直的获取二值信号量,它不会释放信号量,而中断服务函数是一直在释放信号量,它不会获取信号量。在中断服务函数中发送信号量可以使用 xSemaphoreGiveFromISR(),也可以使用任务通知功能来代替二值信号量,而且使用任务通知的话速度更快,代码量更少。
使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及时的处理,这样做相当于推迟了中断处理过程。也可以使用队列来代替二值信号量,在外设事件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。
2.1.1 二值信号量无效

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

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

由于信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相关的处理过程。
2.1.4 任务再次进入阻塞态
由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数 xSemaphoreTake() 获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR() 释放信号量。
2.2 创建二值信号量
和队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数如下表所示:
vSemaphoreCreateBinary() 动态创建二值信号量(老版本的 FreeRTOS 创建信号量的 API 函数)
xSemaphoreCreateBinary() 动态创建二值信号量(新版本的 FreeRTOS 创建信号量的 API 函数)
xSemaphoreCreateBinaryStatic() 静态创建二值信号量

本文围绕信号量展开,介绍其用于资源管理和任务同步,区分了信号量与队列。着重阐述二值信号量,包括原理、创建、释放和获取方法,分析新旧版本创建函数差异。还给出二值信号量操作实验,通过串口指令控制开发板外设,实现中断与任务同步。
最低0.47元/天 解锁文章
3313

被折叠的 条评论
为什么被折叠?



