找回密码
 立即注册
查看: 664|回复: 34

uC/OS-II移植记(9):微山x51-uCOSII中的可嵌套临界区保护方法

[复制链接]

该用户从未签到

63

主题

703

回帖

1万

积分

荣誉版主

积分
10922
发表于 2023-8-29 04:47:40 | 显示全部楼层 |阅读模式
一、前  
通常把单片机RTOS中的核心数据称为最佳“临界资源,比如任务表中每个任务的状态,比如说系统的节拍,这些临界资源的特点是任何一个时刻必须是一个确定的值,其次是这些临界资源发生变化时,需要用程序执行一系列操作,让整个RTOS建立起新的运行状态。
比如当某个任务的休眠节拍达到预定值时,就需要将该任务的状态从休眠变成就绪。由于就绪的任务多了,然后就需要查询就绪任务中优先级最高的任务,如果不是当前任务,还需要进行任务切换。进行这一系列的操作必须是连续的,不能被任何其他试图对这个过程涉及的临界资源进行读写操作的程序打断,因此这些对临界资源的操作也称为“临界过程”。
在操作系统理论中处理这类问题的方法就是建立一个“临界区”,临界区包括对应的临界资源和临界过程,然后选择一个方法来对这个临界区进行保护,使得临界过程不被打断。在单片机程序中,连续执行的程序只可能被中断打断,因此对于单片机RTOS进行临界区保护的最简单方法就是进入临界区时候关闭总中断,不让任何程序来打断临界过程,退出临界区的时候再打开总中断,恢复系统的正常工作状态。
微山x51-uCOSII是笔者在STC8H单片机上移植的uC/OS-II系统,但是原版上的临界区保护方法2和方法3笔者认为存在瑕疵,所以另外选择了加锁解锁方法作为可嵌套的临界区保护方法。
二、临界区保护方法可嵌套的必要性
1)在uC/OS-II中最简单的临界区保护是方法1
#defineOS_ENTER_CRITICAL()     EA=0   // 直接禁止中断              
#defineOS_EXIT_CRITICAL()         EA=1 // 直接允许中断
但是这个方法1是不可嵌套的。
2)对于uC/OS-II程序,假如原来有两段程序被临界区保护:
L1:             OS_ENTER_CRITICAL();
L2:             OS_EXIT_CRITICAL();
其中L1L2之间的程序就是保护着的临界区。然后如果用户扩展这个这个保护区域如下:
L0:             OS_ENTER_CRITICAL();
L1:             OS_ENTER_CRITICAL();
L2:             OS_EXIT_CRITICAL()
L3:             OS_EXIT_CRITICAL()
新的临界区范围设计为从“L0”到“L3”,也就是从“L0”开始关闭中断,到“L3”再打开中断。通过宏展开,上面的程序变为:
L0:             EA=0;
L1:             EA=0;
L2:             EA=1;
L3:             EA=1;
显然中断被在“L2”意外提前打开,“L2”到“L3”之间的临界区没有得到保护,如果这段程序被某个中断服务程序打断,则可能产生意外的后果。
因此可靠的RTOS临界区保护方法应该是可以嵌套的。
三、微山x51-uCOSII的可嵌套临界区保护方法
3)加锁解锁是操作系统最常用的临界区保护方法,用到关闭打开总中断的具体保护方法上,其基本的思路是设置一个计数器变量(也称“锁”),每次进入临界区,计数器加1(也称“加锁”),每次退出临界区,计数器减1(也称“解锁”),第一次进入临界区时关闭总中断,退出临界区时如果计数器减到0,则打开总中断。
4)定义了两种临界区保护方法,缺省的是加锁解锁方法:
Fig_01.jpg
其中进入和退出临界区的函数是用汇编语言写的:
Fig_02_临界区保护.jpg
5)进入临界区保护函数“UCX51_BIOS_ENTER_CRITICAL”的第一条汇编指令“JBC  EA,UCX51_BIOS_ENTER_CRITICAL_02”是一条8051CPU专门为加锁解锁定制的“原子指令”,其定义为:
JBC  BIT, Lab
这条指令相当于下面两条指令:
JB              BIT, Lab
Lab:  CLR BIT
其含义是如果BIT=1,则转移到Lab处执行,并且同时清掉BIT,否则继续执行JBC下面一条指令。之所以被称为原子指令,是因为使用两条指令存在漏洞,以BITEA为例:
JB              EA, Lab
Lab:  CLR  EA
对于这两条指令,在执行完第一条指令,在执行Lab处指令之前,中断并没有被禁止,因此有可能产生硬件中断,加锁程序被中断,俗称“锁没有加牢”。
采用原子指令“JBC”就不一样,这条指令执行时EA同时被关闭了,因此这条指令后不会再发生硬件中断,“锁”就加牢了。换言之原子指令是8051CPU提供的一把“硬件锁”。
6)逻辑上说,进入临界区时如果中断是允许的EA=1,那么肯定是第一次进入临界区,所以第580行和第581行程序进行初始设置。
反之,进入临界区时如果中断是不允许的EA=0,那么就要区分是否是第一次进入,第572行就设置进入临界区时中断标准为0

7)笔者认为对于RTOS,系统的可靠性是第一位的,如果有可能对于程序员的疏漏也应该进行保护,因此比起一般的退出临界区保护程序,微山x51增加第586和第587行保护程序。由于加锁计数器“UCX51_BIOS_CRITICAL_ENTERED”是一个8位字节变量,完整退出临界区保护后其值为0。如果没有这个保护,如果程序中由于某种原因先出现了退出临界区保护函数调用,则计数器就会被减为“255”的非0嵌套值,这将会影响下面程序执行,而加了这个保护就避免了这种情况。


回复 送花

使用道具 举报

该用户从未签到

11

主题

331

回帖

886

积分

荣誉版主

积分
886
发表于 2023-8-29 11:21:06 | 显示全部楼层
杨老师实现的方法和我实现的方法思路基本是一致的。



杨老师的版本多了双重保护
1,进入临界区第一条指令JBC  EA,使得每次EA都清0,
2,退出临界区先对UCX51_BIOS_CRITICAL_ENTERED有判断是否0,更加更加保守做法




我之前也有这个考虑,后面想,这个必须成对使用的,问题不大。
当然考虑更周到,需要加上。


下面还有一些笔者的看法
BITEA为例:
JB              EA, Lab
Lab:  CLR  EA
对于这两条指令,在执行完第一条指令,在执行Lab处指令之前,中断并没有被禁止,因此有可能产生硬件中断,加锁程序被中断,俗称“锁没有加牢”。

这个情况会引起系统不良吗?我认为不会的。
即便是发生中断,执行对应的中断服务函数,然后返回还是这个位置,下一步执行CLR  EA。

中断产生是不能预料的。EA清0之前,都有可能发生。如果太纠结的话,程序刚好执行LCALL OS_ENTER_CRITICAL() 跳转到临界区函数是否有可能产生硬件中断
即便是发生中断,执行对应的中断服务函数,然后返回还是这个位置,下一步执行JBC 语句。
对于上面说的2种可能发生的中断,都对系统没有造成不良影响。系统不会崩溃。











点评

这个情况会引起系统不良吗?我认为不会的。即便是发生中断,执行对应的中断服务函数,然后返回还是这个位置,下一步执行CLR EA。中断产生是不能预料的。EA清0之前,都有可能发生。如果太纠结的话,程序刚好执行LCAL  详情 回复 发表于 2023-8-29 13:30
“杨老师实现的方法和我实现的方法思路基本是一致的。” 我不敢贪天之功为己有,这个思路是写在上个世纪操作系统原理的书上的,“JBC”原子操作是Intel 8051设计师设计的,JBC指令用法我是1990年从姚总老师写的书中  详情 回复 发表于 2023-8-29 12:35
回复 支持 反对 送花

使用道具 举报

该用户从未签到

11

主题

331

回帖

886

积分

荣誉版主

积分
886
发表于 2023-8-29 11:42:07 | 显示全部楼层
本帖最后由 熊仔 于 2023-8-29 11:45 编辑

6)逻辑上说,进入临界区时如果中断是允许的EA=1,那么肯定是第一次进入临界区,所以第580行和第581行程序进行初始设置。
反之,进入临界区时如果中断是不允许的EA=0,那么就要区分是否是第一次进入,第572行就设置进入临界区时中断标准为0

既然要考虑全面,当用户使用不符合逻辑呢?
比如用户在临界区开启中断,这个不正常的操作就出问题了。

下面是我的解决方案
先对CriticalNesting判断是否为0,这样肯定是第一次进入。


点评

(1)“既然要考虑全面,当用户使用不符合逻辑呢?比如用户在临界区开启中断,这个不正常的操作就出问题了。” 你的这种假设是抬杠!没有意思。 比如对你的设计: “用户万一在使用临界区进入前就对你的CriticalNest  详情 回复 发表于 2023-8-29 12:11
回复 支持 反对 送花

使用道具 举报

该用户从未签到

63

主题

703

回帖

1万

积分

荣誉版主

积分
10922
 楼主| 发表于 2023-8-29 12:11:12 | 显示全部楼层
熊仔 发表于 2023-8-29 11:42
(6)逻辑上说,进入临界区时如果中断是允许的EA=1,那么肯定是第一次进入临界区,所以第580行和第581行程 ...

(1)“既然要考虑全面,当用户使用不符合逻辑呢?比如用户在临界区开启中断,这个不正常的操作就出问题了。” 你的这种假设是抬杠!没有意思。

比如对你的设计:
“用户万一在使用临界区进入前就对你的CriticalNesting变量赋于非0值呢”
“用户万一在进入临界区后就对你的CriticalNesting变量赋于0值呢
而且对于你的设计也可以同样假设:

“用户万一在进入临界区后就写一个EA=1;语句”,你的程序能防吗?

万一有用户要踩着油门去杀人,请问你怎么设计汽车的油门和刹车?
(2)你看,上面的假设是多么的没有意思,对吗?
(3)另外,在讨论科学和技术问题时,也要想想某些质疑对自己是否也适用。
(4)俗话说做评论员容易,做创造者难,就是这个意思。


点评

我的解决方案:先对CriticalNesting判断是否为0,这样肯定是第一次进入。 这样就防止抬杠做法。 当然用户不能修改系统函数,修改了就是更加抬杠了。  详情 回复 发表于 2023-8-29 12:23
回复 支持 反对 送花

使用道具 举报

该用户从未签到

11

主题

331

回帖

886

积分

荣誉版主

积分
886
发表于 2023-8-29 12:18:12 | 显示全部楼层
杨老师的版本 第一次进入临界区使用的标志位声明为data变量,最后退出的时候,判断变量是1或0,进行分支跳转,开关中断。



这个是通用的做法。




笔者的版本,第一次进入临界区使用的标志位声明为bit变量,退出的时候C语言操作直接对EA赋值。




对应汇编就是:


2T时钟完成了开关中断。
针对51单片机,bit变量更优。



回复 支持 反对 送花

使用道具 举报

该用户从未签到

11

主题

331

回帖

886

积分

荣誉版主

积分
886
发表于 2023-8-29 12:23:22 | 显示全部楼层
杨为民 发表于 2023-8-29 12:11
(1)“既然要考虑全面,当用户使用不符合逻辑呢?比如用户在临界区开启中断,这个不正常的操作就出问题了 ...

我的解决方案:先对CriticalNesting判断是否为0,这样肯定是第一次进入。
这样就防止抬杠做法。

当然用户不能修改系统函数,修改了就是更加抬杠了。

点评

“当然用户不能修改系统函数,修改了就是更加抬杠了。” 请问用户修改了是谁在更加抬杠了?我?你?用户?  详情 回复 发表于 2023-8-29 13:08
你说“我的解决方案:先对CriticalNesting判断是否为0,这样肯定是第一次进入。这样就防止抬杠做法。” 是吗?请你回答以下的问题: “比如对你的设计: “用户万一在使用临界区进入前就对你的CriticalNesting变量  详情 回复 发表于 2023-8-29 12:48
回复 支持 反对 送花

使用道具 举报

该用户从未签到

63

主题

703

回帖

1万

积分

荣誉版主

积分
10922
 楼主| 发表于 2023-8-29 12:35:28 | 显示全部楼层
熊仔 发表于 2023-8-29 11:21
杨老师实现的方法和我实现的方法思路基本是一致的。

杨老师实现的方法和我实现的方法思路基本是一致的。

我不敢贪天之功为己有,这个思路是写在上个世纪操作系统原理的书上的,“JBC”原子操作是Intel 8051设计师设计的,JBC指令用法我是1990年从姚总老师写的书中学习到的。
8051.jpg

回复 支持 反对 送花

使用道具 举报

  • TA的每日心情
    奋斗
    2 小时前
  • 签到天数: 177 天

    [LV.7]常住居民III

    5

    主题

    581

    回帖

    2385

    积分

    荣誉版主

    积分
    2385
    发表于 2023-8-29 12:38:54 | 显示全部楼层
    看了杨老师和熊仔的临界区保护代码,我受益匪浅,从中所学甚多。
    CosyOS在这方面确有不足,日后仍需改善。
    我的总结是:
    1、杨老师和熊仔都考虑到了在第一次进入临界区时,需要保存中断EA的原状态至 UCX51_BIOS_CRITICAL_INT_FLAG / _bEA,当最后一次退出临界区时再恢复EA的原状态。
    2、杨老师和熊仔都使用了原子操作指令JBC。
    3、杨老师是假定了用户不会在临界区中开启中断。
    4、熊仔是考虑了用户可能会在临界区中开启中断,所以每一次进入临界区时,如果EA为1,都会清零。
    对此我不发表更多评论,主要是向两位版主学习,总之都非常好。




    点评

    “杨老师是假定了用户不会在临界区中开启中断。” 应该改为“杨老师是假定了正常用户不会在临界区中开启中断。” 我写和移植RTOS的时候总是假设我的用户是正常的,然后尽可能去防止用户由于疏忽产生的问题。 但是如  详情 回复 发表于 2023-8-29 13:02
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    63

    主题

    703

    回帖

    1万

    积分

    荣誉版主

    积分
    10922
     楼主| 发表于 2023-8-29 12:48:42 | 显示全部楼层
    本帖最后由 杨为民 于 2023-8-29 14:22 编辑
    熊仔 发表于 2023-8-29 12:23
    我的解决方案:先对CriticalNesting判断是否为0,这样肯定是第一次进入。
    这样就防止抬杠做法。


    你说“我的解决方案:先对CriticalNesting判断是否为0,这样肯定是第一次进入。这样就防止抬杠做法。”
    是吗?请你回答以下的问题:
    比如对你的设计:
    “用户万一在使用临界区进入前就对你的CriticalNesting变量赋于非0值呢”
    “用户万一在进入临界区后就对你的CriticalNesting变量赋于0值呢
    而且对于你的设计也可以同样假设:
    “用户万一在进入临界区后就写一个EA=1;语句”,你的程序能防吗?



    “在中断里面关闭EA,然后退出中断。 这种也是抬杠的做法。”
    熊仔,你没有看出来我是在模仿你抬杠吗?请你看看这段话在前面回复里的语境
    真是无语了


    点评

    系统代码肯定是不能修改的,这个就是真的非常非常抬杠了。我们可以验证一下,用您分享的那个工程里面,第一层嵌套,开启了EA。看一下系统会不会崩溃。   发表于 2023-8-29 13:24
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    63

    主题

    703

    回帖

    1万

    积分

    荣誉版主

    积分
    10922
     楼主| 发表于 2023-8-29 13:02:46 | 显示全部楼层
    CosyOS 发表于 2023-8-29 12:38
    看了杨老师和熊仔的临界区保护代码,我受益匪浅,从中所学甚多。
    CosyOS在这方面确有不足,日后仍需改善。
    ...

    杨老师是假定了用户不会在临界区中开启中断。

    应该改为“杨老师是假定了正常用户不会在临界区中开启中断。
    我写和移植RTOS的时候总是假设我的用户是正常的,然后尽可能去防止用户由于疏忽产生的问题。
    但是如果用户真的在临界区中开启了中断,无论是谁RTOS也没有办法制止的,我的不能,你的也不能,熊仔自己的也不能。
    我们三个中任何一个用自己都不能实现功能去挑别人的刺,这种行为就太双标,太抬杠了

    点评

    防止用户不成对使用保护方法,这种奇葩操作。所以在退出的时候做了一个判断,计数器是否大于0。 确实也有在临界区内打开EA的,奇葩操作。  发表于 2023-8-29 13:19
    回复 支持 反对 送花

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-5-21 15:15 , Processed in 0.198811 second(s), 77 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表