STC单片机 uC/OS-II核心技术(8):中断外任务切换方法
本帖最后由 杨为民 于 2024-3-31 10:57 编辑一、uC/OS-II中断外任务切换过程(1)在单片机RTOS中进行任务调度的函数所在的位置分两类,一类是在中断ISR里,另外一类是在中断ISR外。在uC/OS-II系统中,中断里的任务调度函数是“OSIntExit()”,中断外的任务调度函数是“OS_Sched()”。(2)在uC/OS-II系统中,中断里任务调度函数“OSIntExit()”仅在中断ISR中使用,但是中断外任务调度函数是很多任务管理函数的基础,这些函数在完成任务管理的功能后,都统一通过“OS_Sched()”函数来进行任务调度,控制RTOS系统进入新的运行状态。比如uC/OS-II系统中的任务休眠函数:
其中在完成所有的设置后,第71行调用“OS_Sched()”进行任务调度。
(3)uC/OS-II系统建立之初,为了未来移植到其他各种CPU架构上的方便,中断外的任务调度函数“OS_Sched()”中的任务切换函数“OS_TASK_SW()”是一个宏定义。下图是“OS_Sched()”函数的程序:
(4)任务调度是RTOS的核心功能和核心过程,“OS_Sched()”是uC/OS-II最重要的基础函数。中断外任务调度函数从第1641行开始进入临界区保护开始,到第1655行退出临界区保护结束,整个uC/OS-II的中断外任务调度函数都在临界区保护之下。(5)在uC/OS-II中,具体实现中断外任务切换的函数是上面第1651行的“OS_TASK_SW()”宏函数。在首次引入中国的uC/OS-II的原始版本中,这个宏函数是利用80x86 CPU的软中断指令“INT”实现的。由于80x86软中断指令“INT”不受第1641行uC/OS-II进入临界区保护时关闭总中断的影响,在第1651行处产生一个真正的“软中断”,实现任务切换。在完成任务切换退出“软中断”之后,单片机继续执行第1655行的退出临界区保护程序,打开总中断,至此uC/OS-II系统才脱离临界区。因此,从一开始起,uC/OS-II系统的中断外任务切换整个过程(包括将当前任务的寄存器现场推入堆栈、设置新任务为当前任务和恢复新任务的寄存器现场三大部分)就是在关闭总中断的临界区保护之中进行的。二、挑战者x51的中断外任务切换方法(6)挑战者x51的原型中的中断外任务切换方法是利用STC32G的定时器4硬中断来模拟“软中断”的替代法:
#defineOS_TASK_SW()PendSv_GenerateSWInterrupt()
(7)在原型中第1651行程序仅仅只是执行了上面的第164行的语句设置起定时器4的中断标志,由于这时程序仍然处于临界区保护之下,总中断是关闭的,定时器4的中断这时并不会发生,任务切换也不会开始。(8)在原型中承担中断外任务切换功能的定时器4中断要等待执行完第1655行打开总中断退出临界区保护的程序之后才能发生,这就带来两个巨大的风险:一是如果发生任务切换中断,这时任务切换过程离开了临界区的保护,违背了uC/OS-II系统设计的原理,二是在第1655行程序打开总中断的时刻,发生中断的未必是定时器4,其他中断也有可能与其发生竞争,最终导致任务切换被延迟或者被阻塞,产生不可预料的结果。导致这种风险的根源是用硬中断模拟软中断造成的,真正的“软中断”不存在这种风险。(9)RTOS移植玩家版与产品版的最大区别在于核心标准。对于玩家版,“新颖性”是最重要的核心指标,它直接反映了移植者本人的技术水平,比如在目前还没有开放软中断TRAP指令的STC32G单片机上,采用硬中断模拟软中断的方法实现任务切换技术,为将来STC32G/F系列单片机RTOS探索新路径。而对于产品版,RTOS系统的可靠性是最重要的核心指标,为此在有可能选择的情况,应该选择最可靠的方法。(10)在一个函数中实现任务切换功能(“函数法”),然后作为宏函数对应的实体函数在上面第1651行中代替“OS_TASK_SW()”实现任务切换过程,既符合了uC/OS-II的设计初衷将整个任务切换过程置于临界区保护之下,又不会有任何中断来打断任务切换过程,保证了任务切换过程的连续性。因此对于目前还没有开放软中断TRAP指令的STC32G单片机而言,采用“函数法”作为中断外任务切换方法对于uC/OS-II产品级移植版是一正确的选择(11)挑战者x51移植版(V1.10)的中断外任务切换函数是从定时器4的ISR修改而来的。定时器4的ISR程序见下图:
(12)在80251指令集中,中断时推4个字节的现场进系统堆栈,而“ECALL”和“LCALL”指令分别推3个和2个字节的现场进系统堆栈。
挑战者x51采用4字节的任务切换堆栈,但是CODE大模式下中断外任务切换函数的访问只推入2个字节,因此在函数法的任务切换函数开始的部分要加一段转换程序:
其中第167行和第168行弹出“LCALL”压入堆栈的16位返回地址,第172行到第175行按照硬件中断的格式在系统堆栈中建立一个供80251中断返回指令“RETI”使用的4字节堆栈内容。经过这段程序,CODE大模式的函数访问指令堆栈就转换为硬件中断的堆栈了,这样后面的程序在将当前任务的寄存器现场进行保存的时候就会一起保存到任务堆栈中了。
(13)同样,大模式的函数返回指令“RET”只需要16位2字节的返回地址即可,因此在中断外任务切换函数的后面部分同样也要加一段转换程序:
其中第218行到第221行从新任务的任务堆栈弹出新任务的恢复点,然后将新任务的恢复点压入堆栈,在第230行打开总中断之后,完全恢复了新任务现场,实现了中断外任务切换。
(14)为了对比用中断外任务切换的“函数法”和“替代法”,本文范例只是将函数法的程序附加在挑战者x51的V1.01版本上形成V1.10版本,两种方法都提供给用户选择和测试比较。下图是用户选择界面:
(15)下图是本文范例采用替代法运行效果的逻辑分析仪截图:
其中通道1是替代法的中断外任务切换信号,任务切换时间是1.125微秒。
(16)下图是本文范例采用函数法运行效果的逻辑分析仪截图:
其中通道3是函数法的中断外任务切换信号,任务切换时间是1.5微秒。(17)函数法时间较替代法长的原因是增加了开通和结尾的转换部分,其差别是1.5/1.125=134%。对于玩家版,“任务切换速度”是一个核心指标,它直接反映了移植者本人的编程技巧水平。而对于产品版,RTOS系统的可靠性是最重要的核心指标,因此在任务切换速度满足要求的情况,应该选择最可靠的方法。如果真的有实际应用需要那么高的任务切换速度,只要采用更高的工作主频即可,同时又不必牺牲RTOS系统的可靠性。
附件:本文范例源程序
老杨辛苦啦, 节日也没好好休息 "在原型中承担中断外任务切换功能的定时器4中断要等待执行完第1655行打开总中断退出临界区保护的程序之后才能发生,这就带来两个巨大的风险:一是如果发生任务切换中断,这时任务切换过程离开了临界区的保护,违背了uC/OS-II系统设计的原理,二是在第1655行程序打开总中断的时刻,发生中断的未必是定时器4,其他中断也有可能与其发生竞争,最终导致任务切换被延迟或者被阻塞,产生不可预料的结果。导致这种风险的根源是用硬中断模拟软中断造成的,真正的“软中断”不存在这种风险。"
杨老师,你玩你的, 别老是说我的不行啊,不可靠呀, 真是的! 哪里有不可靠了呀? 我不是硬要和你争个长短, 这种论坛暴露在大厅广众这下, 你这么说很容易误人了弟的!
我不得不出来解释一下. 就像你另一个贴,刚才就把"LAOXU" 同学貌似要带到最高优先级中断切换任务一样, 被你弄得迷迷糊糊的.
1.你去下载一下UCOSII官方移植Cortex-M3的例子, 人家出了这么久, 也没有人说这种方法不可靠呀.你看看你上面说的, "一是如果发生任务切换中断,这时任务切换过程离开了临界区的保护,违背了uC/OS-II系统设计的原理", 这个是你自己认为违背了吧, 你把它追逆到最早的版本,那是永恒的真理,那你为什么不去用老的芯片呢? 那你为何不用UCOS1.0呢? 后来有便优秀的方法了, 为什么不能用?
2."二是在第1655行程序打开总中断的时刻,发生中断的未必是定时器4,最终导致任务切换被延迟或者被阻塞,产生不可预料的结果。" 这一点就更说不过去了,这点在设计之初就考虑进去了,本来就是这样的逻辑, 有其它的中断发生, 当然是优先处理其它中断呀, 处理完之后不是会继续切换任务吗? 你忘记了UOOCII中断优先级是永远高于任务优先级的了吗? 我就发现你根本就没有明白中断切换任务和代码切换任务之间的区别,还一天到晚讲道理给别人听.那我耐心的给你讲一遍吧, <和代码切换任务不同,中断切换任务,是可以被打断的,原因是别的中断处理完自己的事件以后,会原路返回之前的断点,并且编译器会自动恢复之前的上下文. 你不用管高优先级中断他做了什么,你只要知道他不会抢了你的任务切换,也不会改变你的上下文,你仍然可以继续切换,高优先级任务只会占用你一点切换时间,别的都没变>
熊仔证明了UCOS3 和FREERTOS, 切换任务的时候都没有全程关中断 (当然了, 我不是让你去怼熊仔), 你可以去怼UCOS官方和FREERTOS官方,经过你的提醒官方一定会"深刻的反思".
盯着我也没关系,来点货真价实的东西, 别整天有的没的一大堆没用的, 还带个图, 搞的像真的一样的, 啥也不是
本帖最后由 杨为民 于 2023-10-7 08:51 编辑
tzz1983 发表于 2023-10-4 16:33
"在原型中承担中断外任务切换功能的定时器4中断要等待执行完第1655行打开总中断退出临界区保护的程序之后才 ...
“我耐心的给你讲一遍吧, <和代码切换任务不同,中断切换任务,是可以被打断的,原因是别的中断处理完自己的事件以后,会原路返回之前的断点,并且编译器会自动恢复之前的上下文. 你不用管高优先级中断他做了什么,你只要知道他不会抢了你的任务切换,也不会改变你的上下文,你仍然可以继续切换,高优先级任务只会占用你一点切换时间,别的都没变>”
(1)我终于知道你的误区在哪里了:你说的这是裸机编程或者只有一个中断中调用“OSIntExit()”中断任务切换函数的情况,但实际中总会有不止一个中断调用,包括你第一次在论坛问除了在TIMER0外的中断里如何切换任务。
(2)比如下图例子:
在串口中断中也调用了“OSIntExit()”中断任务切换函数。
(3)另外熊仔修改你的测试程序时也给出了例子:
而且也告诉你如果在两个中断里调用“OSIntExit()”中断任务切换函数的话你的程序会死机,可惜你没听进去。
(4)如果是一个带“OSIntExit()”中断任务切换函数的中断打断了你的任务切换函数,那么该中断会因为切换任务不会返回的,因此“别的中断处理完自己的事件以后,会原路返回之前的断点”这句话对RTOS系统未必成立。
本帖最后由 熊仔 于 2023-10-7 10:14 编辑
中断切换任务方式,会原路返回到断点的。因为触发切换任务过程只是设置中断标志位。
莫非思维一直停留在代码切换方式?还是先入为主,切换中断是最高优先级?
1,切换任务是设定为最低优先级。有其他高优先级中断产生,会执行高优先级都中断服务函数,如果在中断服务函数中触发任务任务,也就是弄了一个标志位,然后返回断点。
2,切换任务中断服务函数执行中,被高优先级中断打断,还是能返回到断点位置,执行完当前都中断服务函数,退出中断。如果切换中断标志位置位了,还是再次进入中断切换服务函数。
这里有可能出现高优先级任务进行了2次切换。其实没关系,还是这个任务。
(4)如果是一个带“OSIntExit()”中断任务切换函数的中断打断了你的任务切换函数,那么该中断会因为切换任务不会返回的,因此“别的中断处理完自己的事件以后,会原路返回之前的断点”这句话对RTOS系统未必成立。
==========================================================================
OSIntExit() 函数, 如果是可重入的, 肯定没问题,否则,无论RTOS系统, 还是裸机编程, 都可能 中断返回出错. 本帖最后由 tzz1983 于 2023-10-7 10:16 编辑
杨为民 发表于 2023-10-7 08:48
“我耐心的给你讲一遍吧, ”
(1)我终于知道你的误区在哪里了:你说的这是裸机编程或者只有一个中断中调 ...
杨老师: 呵呵, 实际上还是不会的, 要不你自己去想?只有自己想出来的, 才会理解的.
给您几个提示吧:
1.无论你给多少次 T4IF=1, T4IF最终都是等于1, 绝对不会等于2.
2.无论任务切换过程中被其它中断打断过多少次(可以是嵌套), 中断都是会返回原来的断点, 因为别的中断在执行OSIntExit()时, 如果有切换任务的需求, 只是会做一个 T4IF=1的动作, 除PendSv外, 所有其它的中断都不会真的做任务切换, 它们就是普通的中断, 从哪里来, 回哪里去. 其它的中断切换任务的需求由PendSv中断代劳.
3.在多级嵌套中断里, 由于OSIntNesting++的存在, T4IF=1这个动作也会没有, 只有在最后一级中断(PendSv除外)退出时, 才有T4IF=1的动作
4.任何时候操作(包括切任务的中断)OS核心变量的操作如:
OSPrioCur = OSPrioHighRdy;
OSTCBCur= OSTCBHighRdy;
都是在临界段的, 如果有更高的优先级任务就绪, 那么OSPrioHighRdy和OSTCBHighRdy这两个参数一定会改变, 同时在最后一次OSIntExit()内T4IF=1, 接下来正常退出中断.
再接下来有两种情况:
(1)不是正在做任务切换,此时会激活任务切换.
(2)正在做任务切换(即对应PendSv被打断的情形), 此时会继续做完当前的任务切换, 然后, 重点来了, 因为T4IF=1, 所以在做完当前的任务切换后, 会紧接着再做一次任务切换, 对应的就是先前被打断时, 有高级的优先级任务就绪了, 所以此步必不可少.
老师, 您懂了吗?
杨老师, 我们不要再吵架了, 呵呵, 没什么意思的.
其实我反驳您时: 主要是反驳您有时候发出的不当的观点, 并不是反驳您个人, 您是一位老前辈, 我们要尊重您的.
就拿装饮料瓶子任务做例子。
高优先级任务是执行装瓶子,执行了2次高优先级任务切换,这里会出现装2次吗?
答案是不会的。他是顺序执行,切换2次任务也就是顺序都执行过程,不会重复过程。
例如详细一点比如这样都流程,1,放瓶子;2,注水;3,盖盖子
第一次切换到任务是放瓶子,第二次会顺序执行,切换到注水。
像 老杨 上次举了一个 例子, 两个任务分别调用 printf 函数, 当发生嵌套时, 被打断的printf 函数, 无法恢复 继续输出数据.
这是因为printf 函数,是不可重入函数, 我原想将她改成可重入的函数 (注: 在c51上比较难实现, 在 c251上很容易改成功的).
后来想想没必要, 既然输出数据已打断, 重新输出新(下一个)数据, 返回后再继续输出已无实用价值(显示数据接不上).
在 c251上, 如改为调用vprintf 函数(可重入),肯定不会出现返回出错问题.
LAOXU 发表于 2023-10-7 10:06
(4)如果是一个带“OSIntExit()”中断任务切换函数的中断打断了你的任务切换函数,那么该中断会因为切换任 ...
OSIntExit() 函数,必须是可重入的,莫非你没有看过代码?