第19章 ThreadX信号量
本章节开始讲解ThreadX的另一个重要的任务间的同步和资源共享机制,信号量。
19.1 信号量
19.2 信号量任务通知(又称Event Chaining事件)
19.3 信号量创建函数
19.4 信号量发送函数
19.5 信号量接收函数
19.6 实验例程
19.7 总结
19.1 信号量
19.1.1 信号量的概念及其作用
信号量(semaphores)是20世纪60年代中期Edgser Dijkstra发明的。使用信号量的最初目的是为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
实际的应用中,信号量的作用又该如何体现呢?比如有个30人的电脑机房,我们就可以创建信号量的初始化值是30,表示30个可用资源,不理解的初学者表示信号量还有初始值?是的,信号量说白了就是共享资源的数量。另外我们要求一个同学使用一台电脑,这样每有一个同学使用一台电脑,那么信号量的数值就减一,直到30台电脑都被占用,此时信号量的数值就是0。如果此时还有几个同学没有电脑可以使用,那么这几个同学就得等待,直到有同学离开,有一个同学离开,那么信号量的数值就加1,有两个就加2,依次类推。刚才没有电脑用的同学此时就有电脑可以用了,有几个同学用,信号量就减几,直到再次没有电脑可以用,这么一个过程就是使用信号量来管理共享资源的过程。
平时使用信号量主要实现以下两个功能:
- 两个任务或者中断函数跟任务之间的同步功能,这个和上章节讲解的事件标志组是类似的。其实就是共享资源为1的时候。
- 多个共享资源的管理,就像上面举的机房上机的例子。
实际上信号量还有很多其它用法,而且极具挑战性,可以大大的开拓大家的视野,有兴趣的同学可以阅读一下《The Little Book Of Semaphores》,作者是Allen B. Downy。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=15645
19.1.2 ThreadX信号量原理
ThreadX提供的信号量是32bit计数信号量,计数范围0 到 4,294,967,295。通过函数tx_semaphore_create创建计数信号量,并可以计数信号量初始值。调用一次函数tx_semaphore_get,可以将计数值减1。调用一次函数tx_semaphore_put,可以将计数值加1。
19.1.3 ThreadX任务间信号量的实现
任务间信号量的实现是指各个任务之间使用信号量实现任务的同步或者资源共享功能。下面我们通过如下的框图来说明一下TheadX信号量的实现,让大家有一个形象的认识。
运行条件:
- 创建2个任务Task1和Task2。
- 创建信号量可用资源为1。
运行过程描述如下:
- 任务Task1运行过程中调用函数tx_semaphore_get获取信号量资源,如果信号量没有被任务Task2占用,Task1将直接获取资源。如果信号被Task2占用,任务Task1将由运行态转到挂起状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数tx_semaphore_put释放掉资源。
- 任务Task2运行过程中调用函数tx_semaphore_get获取信号量资源,如果信号量没有被任务Task2占用,Task1将直接获取资源。如果信号被Task2占用,任务Task1将由运行态转到挂起状态,等待资源可以。一旦获取了资源并使用完毕后会通过函数tx_semaphore_put释放掉资源。
上面就是一个简单ThreadX任务间信号量的使用过程。
19.1.4 ThreadX中断方式信号量的实现
ThreadX中断方式信号量的实现是指中断函数和ThreadX任务之间使用信号量。信号量的中断方式主要是用于实现任务同步,与上个章节讲解事件标志组中断方式是一样的。
下面我们通过如下的框图来说明一下ThreadX中断方式信号量的实现,让大家有一个形象的认识。
运行条件:
- 创建1个任务Task1和一个串口接收中断。
- 信号量的初始值为0,串口中断调用函数tx_semaphore_put释放信号量,任务Task1调用函数tx_semaphore_get获取信号量资源。
运行过程描述如下:
- 任务Task1运行过程中调用函数tx_semaphore_get,由于信号量的初始值是0,没有信号量资源可用,任务Task1由运行态进入到挂起态。
- Task1挂起的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中调用函数tx_semaphore_put释放信号量资源,信号量数值加1,此时信号量计数值为1,任务Task1由挂起态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,任务Task1获得信号量后,信号量数值减1,此时信号量计数值又变成了0。
- 再次循环执行时,任务Task1调用函数tx_semaphore_get由于没有资源可用再次进入到挂起态,等待串口释放信号量资源,如此往复循环。
上面就是一个简单ThreadX中断方式信号量同步过程。实际应用中,中断方式的消息机制切记注意以下四个个问题:
1、 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
2、 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
3、 中断服务程序中调用发送函数,一定要设置超时形参为TX_NO_WAIT。
4、 在ThreadX操作系统中实现中断函数跟裸机编程是一样的。
- 另外强烈推荐用户将NVIC优先级分组设置为4,即:HAL_NVIC_SetPriorityGrouping (NVIC_PRIORITYGROUP_4);这样中断优先级的管理将非常方便。
- 用户要在ThreadX多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
19.2 信号量任务通知(又称Event Chaining事件链)
ThreadX 中的通知功能可用于将各种信号量“连接”在一起。当单个线程必须处理多个同步事件时,这通常很有用。
例如,应用程序可以为每个对象注册一个通知回调函数,而不是为队列消息、事件标志和信号量挂起单独的线程。当被调用时,应用程序通知例程然后可以恢复单个线程,该线程可以询问每个对象以便查找和处理新事件。
通常,事件链导致更少的线程、更少的开销和更小的 RAM 需求。它还提供了一种高度灵活的机制来处理更复杂系统的同步要求。
19.3 信号量创建函数tx_semaphore_create
函数原型:
UINT tx_semaphore_create(
TX_SEMAPHORE *semaphore_ptr,
CHAR *name_ptr,
ULONG initial_count);
函数描述:
此函数用于创建信号量。
1、 第1个参数是信号量控制块。
2、 第2个参数是信号量名字。
3、 第3个参数是信号量初始值,范围0x00000000 到 0xFFFFFFFF。
4、 返回值
TX_SUCCESS (0x00) 创建成功。
TX_SEMAPHORE_ERROR (0x0C) 信号量控制块无效。
TX_CALLER_ERROR (0x13) 无效的调用
注意事项:
- 可以在初始化和任务中调用。
使用举例:
TX_SEMAPHORE Semaphore;
/* 创建信号量,初始值为0,用于信号同步 */
tx_semaphore_create(&Semaphore, "Semaphore", 0);
19.4 信号量发送函数tx_semaphore_put
函数原型:
UINT tx_semaphore_put(TX_SEMAPHORE *semaphore_ptr);
函数描述:
此函数用于信号量发送(信号量释放),反映到ThreadX内核操作上,对计数值执行加一操作。
1、 第1个参数是信号量控制块。
2、 返回值
TX_SUCCESS (0x00) 发送成功。
TX_SEMAPHORE_ERROR (0x0C) 信号量控制块无效。
注意事项:
- 如果当前信号量的计数值是0xFFFFFFFF,那么执行此函数后,计数值将复位为0。
使用举例:
TX_SEMAPHORE Semaphore;
/* 信号量释放 */
tx_semaphore_put(&Semaphore);
19.5 信号量接收函数tx_semaphore_get
函数原型:
UINT tx_semaphore_get(
TX_SEMAPHORE *semaphore_ptr,
ULONG wait_option);
函数描述:
此函数用于信号量接收(获取),反映到ThreadX内核操作上,对计数值执行减一操作。
1、 第1个参数是信号量控制块。
2、 第2个参数是等待选项:
如果信号量计数值为零,这个形参将派上用场:
- TX_NO_WAIT (0x00000000),表示不管是否有信号量可用(计数值非0),立即返回。如果在定时器组,初始化或中断里面调用,必须要设置成这个参数。
- TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到有信号量可用(计数值非0)。
- 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍、
3、 返回值
TX_SUCCESS(0x00)设置成功。
TX_DELETED (0x01) 任务挂起阶段,消息队列被删除。
TX_NO_INSTANCE (0x0D)信号量计数值为0,包含等待了指定时间后信号量计数值依然为0。
TX_WAIT_ABORTED (0x1A) 消息队列被其它任务,定时器组或者中断服务程序终止。
TX_SEMAPHORE _ERROR (0x09) 无效的信号量控制块。
TX_WAIT_ERROR (0x04) 无效调用,主要是在非常任务代码中使用TX_NO_WAIT 以外的形参。比如在中断服务程序里面设置等待。
注意事项:
- 可以在初始化,任务,定时器组和中断服务程序里面调用。
使用举例:
TX_SEMAPHORE Semaphore;
/*
*********************************************************************************************************
* 函 数 名: AppTaskMsgPro
* 功能说明: 消息处理,这里用作信号量获取。
* 形 参: thread_input 是在创建该任务时传递的形参
* 返 回 值: 无
优 先 级: 3
*********************************************************************************************************
*/
static void AppTaskMsgPro(ULONG thread_input)
{
UINT status;
while(1)
{
status = tx_semaphore_get(&Semaphore, TX_WAIT_FOREVER);
if(status == TX_SUCCESS)
{
/* 接收到信号量 */
printf("接收到同步信号量\r\n");
}
}
}
19.6 实验例程
配套例子:
V7-3014_ThreadX Semaphore
实验目的:
- 学习ThreadX信号量
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
========================================================
CPU利用率 = 0.89%
任务执行时间 = 0.586484645s
空闲执行时间 = 85.504470575s
中断执行时间 = 0.173225395s
系统总执行时间 = 86.264180615s
=======================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
5 4092 167 167 App Msp Pro
4 4092 167 167 App Task UserIF
5 4092 167 167 App Task COM
0 1020 191 191 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理,这里用作信号量获取。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
System Timer Thread任务:系统定时器任务
2、K2键按下,发送信号量同步信号。
3、任务App Task MspPro接收到消息后,串口打印。
4、(1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。
5、默认上电是通过串口打印信息,如果使用RTT打印信息
(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可
#define Enable_RTTViewer 1
(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
RTT打印信息方式(AC5,AC6和IAR):
Embedded Studio仅支持调试状态RTT打印:
由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
19.7 总结
本章节主要为大家讲解了另一个重要的任务间的同步和资源共享机制信号量,建议初学者务必将其掌握,因为实际项目中用到信号量的地方很多。
微信公众号:armfly_com