杨为民 发表于 2023-9-30 21:04:53

STC单片机 uC/OS-II核心技术(7):挑战者x51 uC/OS-II出发版

本帖最后由 杨为民 于 2024-3-31 10:56 编辑

一、挑战者x51 uC/OS-II移植版简介
(1)挑战者x51是将uC/OS-II移植到STC32G单片机上的移植版的名称。挑战者x51的设计目标是一个公开源代码的具有产品级可靠性的移植版,通过开源的代码展示单片机RTOS产品的可靠性与底层代码和顶层设计之间的关系,供教学、学习和研究使用。由于挑战者x51的RTOS的核心部分直接使用了Micrium公司的uC/OS-II以源代码,因此挑战者x51的使用者如果计划在商业产品中使用,则需要联系Micrium公司得到正确的许可。(2)挑战者x51的原型来源于网友tzz1983的“UCOSII - STC32G12K128”,按照他自己的介绍是“自已花了两天时间把USOS2.91版本移植到了STC32G12K128上面。 移植过程还算比较顺利。(参考了STC官网上FREERSOS的汇编代码,和M3的移植思路),只花了一天多一点的时间。”通常我们将这种花几天的时间就推出来的RTOS移植版称为“玩家版”。具体文章见:UCOSII - STC32G12K128 移植“https://www.stcaimcu.com/forum.p ... 4177&extra=page%3D1”(3)挑战者x51的原型的最大特色是使用了用定时器4硬件中断代替软中断的方法(以后称这种方法为“替代法”)。这种替代法具有明显的两个优点:程序简明易懂和任务切换速度很快。关于挑战者x51的原型的介绍见下面文章:《STC32G单片机RTOS任务切换的“替代法”介绍》“https://www.stcaimcu.com/forum.p ... 4289&extra=page%3D2”(4)产品级移植版与玩家版最大的差别在于RTOS系统的可靠性。一个移植版要实现产品级的可靠性,移植前的顶层设计(通常是对很多选项进行反复地权衡和精心地选择)和移植时对底层代码的反复考量以及移植后对整个系统进行严苛的测试这三个部分缺一不可。(5)笔者计划以系列文章的形式,逐步介绍如何将一个玩家版的可靠性改造提升到产品级的移植版。本文是系列文章的第一篇,介绍对于关闭单片机总中断(EA)的RTOS临界区保护方法的选择和对替代法软中断程序的修改。二、关闭单片机总中断(EA)的RTOS临界区保护方法(6)由于STC32G/F系列单片机(没有开放TRAP指令)和STC8单片机的CPU架构不像其他CPU架构有可屏蔽/不可屏蔽中断、硬件/软件中断等各种中断类型,要想对用户程序中的中断进行统一控制,只有关闭总中断(EA=0)和打开总中断(EA=1)一种方法。由于临界区保护是决定单片机RTOS系统可靠性的最基本和最重要的方法,笔者在论坛中专门开贴与网友交流,这里就不再讨论了,感兴趣的网友可以查看下列文章:《STC单片机uC/OS-II移植记(5):临界区保护方法研究 》“https://www.stcaimcu.com/forum.php?mod=viewthread&tid=2512”《关于STC单片机RTOS中的临界区保护问题 》“https://www.stcaimcu.com/forum.p ... 2132&extra=page%3D1”《STC-RTOS vs STC-DOS:再论临界区保护方法》“https://www.stcaimcu.com/forum.php?mod=viewthread&tid=2216”
(7)挑战者x51的原型的临界区保护方法3,笔者认为一是不完美:在一个函数内无法多次嵌套,二是理念不同:对于多任务的程序,如果在临界区中任务被切换,则配对的临界区退出功能就会被“挂起”,这会导致无法预料的情况发生,导致整个RTOS系统的可靠性下降。因此本次挑战者x51移植版的临界区保护选择方法9,具体的程序介绍见下文中的方法:《uC/OS-II移植记(9):微山x51-uCOSII中的可嵌套临界区保护方法》“https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4018”
三、替代法“软中断”的临界区保护方法(8)一般的单片机或者计算机的RTOS都用专门的“软中断指令”来实现任务的切换。这些软中断指令(比如INT n和TRAP等指令)具有3个鲜明的特点:一是不可屏蔽,也就是说通常的关闭全部中断的指令对它不起作用,即使关闭了全部可被关闭的中断,这种软中断指令可以立即执行,产生一个软中断。二是中断优先级高,一般仅次于不可屏蔽的硬中断和CPU异常内部软中断,因此一旦软中断开始执行,在中断指令(RETI)发布前,不会被其他用户中断打断。三是当软中断指令用于任务切换时,前面两个特点导致了整个任务切换过程连续执行不被打断,相当于任务切换过程处于“指令级的临界区保护之中”。(9)挑战者x51的原型的替代法“软中断”程序分三个部分,由于替代法使用的是STC32G单片机的硬中断,不具备上面提到的真正软中断指令的3个特点,因此存在两个可靠性方面的隐患。前两部分程序见下图:
(10)上面程序的第97行到第111行是第一部分,是将当前任务的寄存器现场保存到系统堆栈中。这部分程序没有任何保护措施,由于进入这个硬中断时总中断是打开的(EA=1),因此这部分程序在执行过程中有可能被其他中断(系统中断或者用户中断)打断,进行其他处理。由于RTOS只是一个系统平台,无法预料用户的中断程序会做什么事,所以存在可靠性隐患。(11)上面程序的第116行到第131行是第二部分,是除了调用任务切换钩子函数外,将决定正在运行的任务是哪个任务的两个变量从“当前任务”改变为“任务优先级最高的任务”,完成了任务切换的第一步切换系统指标变量。在挑战者x51的原型中这个部分用关闭总中断的方法进行了临界区保护。由于前面的第一部分允许其他中断的发生,因此第116行到第123行的临界区进入保护代码考虑了总中断被其他中断的ISR程序关闭的可能,采取了嵌套保护方法。
(12)第三部分的程序如下:
其中第134行到第149行时第三部分程序,从系统堆栈中恢复寄存器现场。这个部分存在严重的可靠性隐患。如果在第三部分程序执行中产生其他中断,并且如果这其他中断导致新的最高优先级任务诞生(比如一个更高优先级任务的休眠期到了,或者被中断唤醒就绪),然后在返回这个第三部分之前进行任务切换,就将导致严重的后果。
(13)借鉴前面指出的软中断指令具有的3个鲜明特点,本文挑战者x51移植版采用一个简单的方法克服原型中的那两个可靠性隐患,具体程序如下:
在任务切换“软中断”的第一行程序执行关闭中断的指令。由于中断ISR的第一条指令肯定会被执行,所以对于STC32G单片机第100行程序会将所有中断关闭,整个任务切换程序将会连续第执行到最后一行才打开中断,使得这个替代法的硬中断模拟了真正的“软中断指令”,对整个任务切换程序实现了最高级别的临界区保护措施。 四、范例程序简介(14)本文的范例的多任务程序采用了下文介绍的测试2程序,测试的结果也与下文一样,这里就不再一一做介绍了:《惊天大BUG之三:关闭总中断的临界区保护方法测试》“https://www.stcaimcu.com/forum.p ... 4336&extra=page%3D1” 本文范例程序:


tzz1983 发表于 2023-10-1 12:48:48

本帖最后由 tzz1983 于 2023-10-1 13:16 编辑

杨老师: 你好, 又来把弄我移植的UCOS了.
刚才通篇看了一下你的贴子, 几个重点总结一下:
1. tzz1983(本人)移植时进入临界区使用模式3, 出临界区时使用代码"#defineOS_EXIT_CRITICAL()   {IE|=cpu_sr;}"这个地方呢确实不支持在同一个函数内多次进入临界区.   对于我代码中确实存在的不足, 我欣然接受老师的指点!
之前我也注意到了这个地方, 但一直懒得去改, 因为一些原因, 我本来也没有打算再去更新我的代码了. 但现在看来, 还是有必要去修正一下, 我发现我的代码还是很受关注的, 即然如此, 那就再费些心思吧. 下午就去把代码修改一下, 很简单的, 把这句话改成函数调用的方式就可以了.
2. 您在切换任务的中断里全程关中断, 这一点我不反对, 这个是理念不同, 你喜欢这样做自然也是无可厚非的. 但是请您不要出误导性言论, 用中断切换任务, 和使用代码切换任务, 许多地方已经有区别, 在这里"只在访问OS核心变量时关中断"是合理的, 不存在隐患. 这是论题在之前的论贴上多次被论证过, 这里就不再详细的再论证一遍了. 并且杨老师你自己也是认同过的, 借用你的话来说"男孩子游泳时, 只要遮住自己的敏感部位就可以了". 您不能一会可以一会又不可以了啊, 像我这种老油条有免疫力了, 不会和您当真. 但新手村那些孩子会被你搞懵逼的哦. 您要建立威望, 不能老玩自己戳自己脊梁骨的事情.
3. 没有3了, 谈谈自己的感想, 自从我推出我的"玩家版UCOSII-STC32G12K128"以后, 本来还是不怎么自信的, 毕竞我在这个地方花的时间很少, 刚弄出来就传上来了. 但是经过我和杨老师和熊仔之间的探讨, 我越发觉得自己移植这东西, 移植的太好了, 特别是杨老师您还给他取了个名字, 叫做"替代法". 和别的版本对比竞然是那么的优秀, 语句简练,逻辑清楚, 堪称经典啊. 所以我越发的自信起来了, 哈哈.
并且进一步的, 你们左挑右挑, 也还是没有挑出什么实质性的BUG啊, 这一点其实是最重要的, 因为多几个人来找BUG, 总比我一个人玩来的强. 所以我现在"都有点膨胀”了.
4. 说点正经的, 等会我去把我的代码更新一下, 就是把1说到的问题修正一下.
本来呢, 这个"玩家版UCOSII-STC32G12K128"的代码我希望STC版主们接手来做以后呢, 我就不管了.
但现实有些出人意料: 熊仔, 他一定要在OS内加一个临界区切换任务的功能, 这个功能我特别不喜欢,我认为是画蛇添足, 可以证明任何时候都不需要在临界区切换任务, 去看看"临界区",的定义就知道, 在在临界区切换任务本身就是一个错误. 不要和我说UCOSII 用代码切换任务时, 切换本身就包含在临界区内, 与其说这是一个特殊, 还不如说这也本身就是一个错误,只是这个错误在后来被移植者用一些其它的方法给修正回来了, 使正常的逻辑仍可以维持运行. 被修正的错误没有危害, 可以正常用, 但"它"本质还是一个错误, 本来面目不能改变的. 但是我们可以给他取个好听的名字, "用代码切换任务".一切就完美了.
杨老师呢, 就在刚才, 也出了一个"出发版" 这一下版本就多起来了哈, 嘿嘿
所以呢, 我决定, "玩家版UCOSII-STC32G12K128" 以后如果发现不足, 继续更新!
毕竞是原汁源味的嘛? 代码简单, 逻辑清楚, 不存在那些 "花里胡哨,华而不实"的东西.或许大多人还是喜欢这样子的.

唉, 最后一声深深的叹息, 这个论坛也太冷清了,玩来玩去也就这么几个人, 没劲, 走了走了, 先去玩会别的

tzz1983 发表于 2023-10-1 15:53:51

本帖最后由 tzz1983 于 2023-10-1 15:55 编辑

借用一下杨老师的道场, 谈论一个话题:在同一个函数多次嵌套进入临界区

在上一贴中说到, 在同一个函数多次嵌套进入临界区这个事情,刚开始我还觉得确有其事, 但是当我去改代码时, 好像哪又不对了?

我在想同一个函数内, 怎么又需要多次嵌套使用临界区了呢? 有些奇怪, 这是不是有点自找麻烦的味道?
于是我努力回忆了一下以前学过的知识, 好像书本上大概是这样说的(我没去查书了,就凭着记忆,说错了就别怪我哦,呵呵):当程序(或系统)越来越复杂时, 比如:在临界区中调用一个函数, 而这个函数又包含了另一个临界区, 所以临界区嵌套了.
个人认为结论是: 临界区是需要支持嵌套的, 否则就像书上说的那样, 一不小心就翻船了.
但是同一个函数体内直接多次嵌套进入临界区(不是调用其它函数那种), 这个....好像有点....怎么说呢? 不是不行, 而是....就是好像有点跟自己过不去!到目前为此, 我没有见过这种用法, 不管是别人的代码, 还是自己的, 都没见过.
UCOSII 的模式3 是可以支持临界区嵌套的, 但不支持这种同一函数体内的多次嵌套.

所以最终, 我改变了想法, 我认为"同一个函数内直接多次嵌套进入临界区(不是调用其它函数那种)" 这种事情, 我还是不要支持了. 管他呢, 喜欢这么玩的人, 他自己会改的, 我没必要为他去改.

LAOXU 发表于 2023-10-3 19:19:12

看了几遍, 总结了一句话, 函数要写成可重入的即可, 所有问题都可解决.

至于进入临界区, 被高级中断打断, 根本就不用当一回事, 因为进入中断不保护好现场, 中断返回不能完美恢复现场, 这家做 编译器 的, 早就要关门倒毙了!

你函数不写成可重入的, 被打断了就有可能恢复不了, (临时变量 被改变了).51/251 类, 因结构关系, 影响 比较明显.

LAOXU 发表于 2023-10-4 08:12:18

分析了进了临界区的相关函数(反汇编源码) , 以及 OS 在 临界区内的相关操作.

得出结论, 在临界区内, 可以改变(破坏) OS 正常运行的, 只有临时变量(中间变量), 进入中断时未能识别和及时保护, 中断返回时不能完美恢复现场.

至于 老杨 描述的 临界区执行中 一大堆 PUSH 或者 POP 指令, 要禁止中断加以保护, 我认为完全没必要, 编译器生成的中断程序, 会自动保护和恢复现场,

无论多少次重复嵌套进出都没关系(除非堆栈设置的长度不够溢出).

至于 tzz1983 不认同的"同一个函数内直接多次嵌套进入临界区(不是调用其它函数那种)" 这种事情, 我认为应该支持, 她不过是函数体内直接多次嵌套进入

的一个特例罢了, 无论多少次重复嵌套进出都应该充许(除非堆栈设置的长度不够溢出).

tzz1983 发表于 2023-10-4 11:24:59

本帖最后由 tzz1983 于 2023-10-4 11:28 编辑

LAOXU 发表于 2023-10-4 08:12
分析了进了临界区的相关函数(反汇编源码) , 以及 OS 在 临界区内的相关操作.

得出结论, 在临界区内, 可以 ...
喜欢在同一函数内多次嵌套临界区的朋友可以参照熊仔的做法, 用函数调用来实现. 自己改一下变动很少.
我是认为同一个函数体内嵌套进入临界区, 是不是脑子有点问题? 明明在临界区了, 还要再进一次?支持嵌套是为了防止代码规模大了以后, 无意中间接了嵌套了(通常是在调用的代码中有包含临界段的代码), 而这种情况, 模式3是直接可以支持的
顺带说一下, 在不影响效率或影响可以忽略不计的情况下,程序逻辑能简单点就简单点, 不要去弄那些花里胡哨的东西, 有时候装逼不成, 反蚀一把米.

LAOXU 发表于 2023-10-4 11:43:46

tzz1983 发表于 2023-10-4 11:24
喜欢在同一函数内多次嵌套临界区的朋友可以参照熊仔的做法, 用函数调用来实现. 自己改一下变动很少.
我是 ...

你的观点基本认同.

你的 OS 代码我仔细看了, 没看出什么实质性的 BUG, 写的非常不错, 很有功底.

提个小建议, 个人认为, 嘀嗒计时器, 要最低优先级这没错(否则会引起混乱).

而SW任务切换函数, 应该使用最高优先级, 具有排外性, 你用置位 T4中断, 触发 SW任务切换.

目前使用的是最低优先级, 如改用最高优先级, 是否可以达到使用 TRAP指令的效果?

那么老杨所说的一切 临界区现像, 都不复存在了?

tzz1983 发表于 2023-10-4 13:41:03

本帖最后由 tzz1983 于 2023-10-4 13:48 编辑

LAOXU 发表于 2023-10-4 11:43
你的观点基本认同.

你的 OS 代码我仔细看了, 没看出什么实质性的 BUG, 写的非常不错, 很有功底.

硬件不一样应该是有些不一样的,别的单片机有SEI, SVC 类似于这种指令, 是没有延迟的, 指令后直接进中断, 不会有写存储器, 然后再中断这个过程, 且优先级高, 不会被打断.这种的逻辑稍为有些区别, 这里不讨论得太宽了.
PendSv中断, 是从ARM_Cortex系列处理器引用过来的, 他的出现本来就是用来做OS的, 用起来比以前那些指令中断更顺手, 否则也不会出现它了.
至于PendSv中断为什么是最低优先级, 你去看看<<Cortex-M3 权威指南 宋岩 译>>这本书, 网上PDF下载免费 , 白话中文, 通俗易懂.
这里只能简单的告诉你, 最低优先级, 逻辑是正确的, 要深入的了解, 只能你自己去看一下书了.
杨老师喜欢拉着我们去旅游,一个问题, 可以从天南扯到地北, 有的东西不必太在意

LAOXU 发表于 2023-10-4 15:27:04

tzz1983 发表于 2023-10-4 13:41
硬件不一样应该是有些不一样的,别的单片机有SEI, SVC 类似于这种指令, 是没有延迟的, 指令后直接进中断, ...

谢谢, 看了

我仔细思考一下

LAOXU 发表于 2023-10-4 15:41:34

疑点:

Cortex-M3的 PendSv中断, 有保护机制, 一但进入, 不会被其他中断打断(硬件保证不发生嵌套).

而用 C251 用 T4模拟的中断, 是常规中断, 优先级如设置最低时, 会被其他高级别的中断打断, 从而引起中断嵌套.

故我想, 只能设置成最高级别时, 才不会被其他中断打断, 从而达到模拟PendSv中断 的效果.

注: T4中断 只有最低级, 实际操作可和 T0互换使用, 用 T0(中断级设置最高)来 模拟 PendSv中断,

页: [1] 2
查看完整版本: STC单片机 uC/OS-II核心技术(7):挑战者x51 uC/OS-II出发版