论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
第14章 ThreadX调度锁,任务锁和中断锁(调度阀值)
本章教程为大家讲解调度锁,任务锁和中断锁的概念,以及ThreadX调度锁的使用。
目录
14.1 临界段
代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
如果源码中有临界段的话,会给系统带来什么问题呢?比如此时某个任务正在调用系统API函数,而且此时中断正好关闭了,也就是进入到了临界区中,这个时候如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行,如果关中断时间超过了紧急中断能够容忍的限度,危害是可想而知的。像uCOS-II,uCOS-III和FreeRTOS的源码中都是有临界段的。
除了上面说的操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:
- 读取或者修改变量(特别是任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。
- 调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。
总之,对于临界段要做到执行时间越短越好,要不会影响系统的实时性。
14.2 中断锁
中断锁就是ThreadX提供的开关中断函数:
#define TX_INTERRUPT_SAVE_AREA unsigned int was_masked; #define TX_DISABLE was_masked = __disable_irq(); #define TX_RESTORE if (was_masked == 0) __enable_irq();
14.3 任务锁
简单的说,为了防止当前任务的执行被其它高优先级的任务打断而提供的锁机制就是任务锁。实现任务锁可以通过给调度器加锁或者直接关闭RTOS内核定时器(就是前面一直说的系统滴答定时器)来实现。
- 通过给调度器加锁实现。
给调度器加锁的话,就无法实现任务切换,高优先级任务也就无法抢占低优先级任务的执行,同时高优先级任务也是无法向低优先级任务切换的。像uCOS-II和uCOS-III是采用的这种方法实现任务锁。特别注意,这种方式只是禁止了调度器工作,并没有关闭任何中断。
- 通过关闭RTOS内核定时器实现
关闭了RTOS内核定时器的话,也就关闭了通过RTOS内核定时器中断实现任务切换的功能,因为在退出定时器中断时需要检测当前需要执行的最高优先级任务,如果有高优先级任务就绪的话需要做任务切换。RTX操作系统是采用的这种方式实现任务锁的。
ThreadX有更好的方法实现,可以使用抢占阀值。
14.4 调度锁
调度锁就是RTOS提供的调度器开关函数,如果某个任务调用了调度锁开关函数,处于调度锁开和调度锁关之间的代码在执行期间是不会被高优先级的任务抢占的,即任务调度被禁止。这一点要跟临界段的作用区分开,调度锁只是禁止了任务调度,并没有关闭任何中断,中断还是正常执行的。而临界段进行了开关中断操作。
与任务锁一样,也可以采用调度阀值实现。
14.5 ThreadX的抢占阀值
为了缓解抢占式调度存在的一些固有问题,ThreadX 提供了一个独特的高级功能---抢占阈值。
抢占阈值允许任务指定禁止抢占的优先级上限。优先级高于上限的任务仍可以执行抢占,但不允许优先级低于上限的任务执行抢占。
例如,假设优先级为 20 的任务只与一组优先级介于 15 到 20 之间的任务进行交互。 在其关键部分中,优先级为 20 的任务可将其抢占阈值设置为 15,从而防止该任务和与之交互的所有任务发生抢占。 这仍允许(优先级介于 0和 14 之间)真正重要的任务在其关键部分处理中抢占此任务的资源,这会使处理的响应速度更快。
而且可以将抢占阈值设置为 0 来禁止所有任务抢占。同时,用户也可以在运行时更改抢占阈值。注:使用抢占阈值会禁止指定线程的时间调度。
举个例子,方便大家使用,比如一个任务的优先级是5,我们希望执行某代码期间禁止优先级0-4的任务抢占。
TX_THREAD my_thread; UINT my_old_threshold; UINT status; status = tx_thread_preemption_change(&my_thread, 0, &my_old_threshold); 用户可以在此处执行关键代码。 status = tx_thread_preemption_change(&my_thread, 5, &my_old_threshold);
通过这种方式就可以方便的实现调度锁和任务锁。
14.6 实验例程
配套例子:
V6-3009_ThreadX Preemption Threshold
实验目的:
- 学习ThreadX抢占阀值。
- 通过抢占阀值可以方便的实现任务锁,调度锁的功能
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
========================================================
OS CPU Usage = 1.05%
========================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
30 1020 303 303 App Task STAT
31 1020 87 71 App Task IDLE
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任务 :消息处理,这里用作LED闪烁。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
App Task STAT任务 :统计任务
App Task IDLE任务 :空闲任务
System Timer Thread任务:系统定时器任务
2、 (1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。
3、默认上电是通过串口打印信息,如果使用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不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
14.7 总结
本章节是ThreadX操作系统的核心,初学者要深入理解的话需要多花些时间。当然,如果有其它RTOS的基础的话,这个学起来也是很快的。