本帖最后由 杨为民 于 2024-3-23 22:57 编辑
一、最直观的解决方案 (1)对于开始的简单题目,LAOXU的方法是“最简单的方法,在底层(主程序)中, 调用前先关中断, 执行调用 strncpy , 调用后再打开中断”。 写成程序是这样的:
void main(void)
{ do{
EA=0;
strncpy (cc, a2, 8);
EA=1;
}while(1);
} 点评:这个方法真的是最直观、最简单可行和最实用的方法。 (2)对于LAOXU自己的后面的高难度题,LAOXU承认除了重新编写函数使其成为可重入的之外自己没有其他方法:对, 我没有好的办法, 请你老指教.事实上很多时候, 如杨老师所言: "其实解决方法很简单", 只是, 他知道你, 你不认识他. 点评:对于LAOXU的回答,我很意外。他既然找到了简单题目的最简单解决方法,为什么不举一反三给出自己的后面的高难度题的最简单解决方法呢?
(4)最直观的解决方法就是把上面的方法重复5次: void int0(void )interrupt 0 // 优先级 0
{
EA=0;
strncpy (bb0, a0, 8);
EA=1;
}
void int1(void )interrupt 1 // 优先级 1
{
EA=0;
strncpy (bb1, a1, 8);
EA=1;
}
void int2(void )interrupt 2 // 优先级 2
{
EA=0;
strncpy (bb2, a2, 8);
EA=1;
}
void int3(void )interrupt 3 // 优先级 3
{
EA=0;
strncpy (bb3, a3, 8);
EA=1;
}
void main(void)
{ do{
EA=0;
strncpy (cc, a2, 8);
EA=1;
}while(1);
}
二、给新手的教学方案 (1)我去请教在大学教计算机基础课的老师,如何教懂新手解决这种在两个地方同时调用不可重入函数的问题,他打了给比方: 不可重入函数就像不能同时在两个地方炒菜的厨师一样,如果正在一个地方炒菜没有结束,就被叫到另外一个地方炒菜,那么等那边后菜做好再回来,前面的菜早就糊了。 解决的方法是在家里炒菜的时候关掉手机,不接收出门炒菜的请求就行,等把家里的菜炒完再打开电话,然后接到请求出门去炒菜,或者留在家里继续炒菜。 这个方案写成程序如下: void int1(void ) interrupt 1
{
出门炒菜:--> strncpy(bb, a1, 8);
}
void main(void)
{ do{
关闭电话: --> EA=0;
在家炒菜:--> strncpy(cc, a2, 8);
打开电话: --> EA=1;
}while(1);
}
(2)我再问对于高难度的题目,如何教懂新手。他说按照“专心做完一件事情后再做另一件事情”的原则办就行了。 就像把熊猫装进冰箱一样,分三步:关闭手机、做菜、打开手机。 void int0(void ) interrupt 0
{
关闭电话: --> EA=0;
出门炒菜:--> strncpy(bb0, a0, 8);
打开电话: --> EA=1;
} void int1(void ) interrupt 1
{
关闭电话: --> EA=0;
出门炒菜:--> strncpy(bb1, a1, 8);
打开电话: --> EA=1;
}
void int2(void ) interrupt 2
{
关闭电话: --> EA=0;
出门炒菜:--> strncpy(bb2, a2, 8);
打开电话: --> EA=1;
}
void int3(void ) interrupt 3
{
关闭电话: --> EA=0;
出门炒菜:--> strncpy(bb3, a3, 8);
打开电话: --> EA=1;
}
void main(void)
{ do{
关闭电话: --> EA=0;
在家炒菜:--> strncpy(cc, a2, 8);
打开电话: --> EA=1;
}while(1);
}
==== 第2部分 ================================================= 三、可嵌套的关闭总中断的临界区保护方法 (1)上面的解决方法从OS的角度看是不完美的,因为这种方法是不可嵌套的。对于像题目中的解决某些库函数不可重入问题是没有问题的,但是对于解决用户自己编写的大型程序却是不够的,需要找到一些通用的可以嵌套的方法来。 (2)对于上面的程序例子,用计算机操作系统术语来说,从“main”函数开始的程序被称为“后台任务”,每一个中断服务程序ISR被称为一个“前台任务”。因此裸机编程只要包含中断程序,本身就是属于“前后台多任务OS”,所以本文的题目也可以用操作系统OS的方法来解决。 (3)像上面提到的不可重入函数,意思是如果两个任务同时调用该函数,则会产生冲突。因此这种不可重入函数属于OS理论中的“临界区”的一种。按照OS理论,要使用临界区,则必须进行“临界区保护”。 (4)具体地说,进入临界区之前,必须执行一段“进入临界区保护”的程序,这段程序的作用是禁止其他程序打断下面临界区程序的执行。比如上面程序中的“关闭总中断EA=0”就是一种“进入临界区保护”的程序,执行这句程序后,总中断被关闭(除不可屏蔽中断外)后,不会产生中断,后面的不可重入函数也也就不会被重入了,换言之,临界区得到保护了。 同样的,在临界区代码执行完后,也必须执行一段“退出临界区保护”的程序,这段程序的作用是解除保护措施,提供其他中断程序执行的机会。比如上面程序中的“打开总中断EA=1”就是一种“退出临界区保护”的程序,执行这句程序后,总中断被打开后,其他中断就能正常发生和执行了。 (5)如果按照uC/OS-II RTOS的规则,用函数“OS_ENTER_CRITICAL()”来表示“进入临界区保护”的程序,用函数“OS_EXIT_CRITICAL()”来表示“退出临界区保护”的程序,那么上面最直观的解决方法可以写成下面形式: void int0(void )interrupt 0 // 优先级 0
{
OS_ENTER_CRITICAL(); //进入临界区保护
strncpy (bb0, a0, 8);
OS_EXIT_CRITICAL (); //退出临界区保护
}
void int1(void ) interrupt 1 //优先级 1
{
OS_ENTER_CRITICAL(); //进入临界区保护
strncpy (bb1, a1, 8);
OS_EXIT_CRITICAL (); //退出临界区保护
}
void int2(void )interrupt 2 // 优先级 2
{
OS_ENTER_CRITICAL(); //进入临界区保护
strncpy (bb2, a2, 8);
OS_EXIT_CRITICAL (); //退出临界区保护
}
void int3(void )interrupt 3 // 优先级 3
{
OS_ENTER_CRITICAL(); //进入临界区保护
strncpy (bb3, a3, 8);
OS_EXIT_CRITICAL (); //退出临界区保护
}
void main(void)
{ do{
OS_ENTER_CRITICAL(); //进入临界区保护
strncpy (cc, a2, 8);
OS_EXIT_CRITICAL (); //退出临界区保护
}while(1);
}
(6)下面我以网友“tzz1983”移植的uC/OS-II为例来介绍“可嵌套的临界区保护方法”。下面的程序来源于《【uGFX/GUI + uC/OS-II】@STC32G;uGFX/GUI@STC32G裸机》(https://www.stcaimcu.com/forum.php?mod=viewthread&tid=7130&extra=page%3D1),有兴趣的网友可以去学习,如果有不清楚的地方,也请到这个主贴去提问,由主家亲自为你解答。 (7)uC/OS-II中的临界区保护方法程序见下面: //进入临界段的方法(推荐3) #define OS_CRITICAL_METHOD 3 //OS_CRITICAL_METHOD= 1 :直接使用处理器的开关中断指令来实现 (使用限制: 不支持嵌套, 调用OS服务前, 要保证EA=1) #ifOS_CRITICAL_METHOD == 1 /* 调用OS服务, 可能会使EA意外的被打开, 所以, 调用OS服务前一刻, 要保证EA=1 */ #define OS_ENTER_CRITICAL() EA=0 /* 使用限制: 不支持嵌套, 进入任务前,保证不开所有分中断, 即使EA被意外打开也不会实际有可响应的中断 */ #define OS_EXIT_CRITICAL() EA=1 #define OS_TASK_SW() {_bIE__=IE|0X80; OSCtxSw(); return;} #endif //OS_CRITICAL_METHOD= 2 :利用全局变量保存和恢复CPU的状态,最多8次嵌套 #ifOS_CRITICAL_METHOD == 2 voidOS_CPU_SR_Save2(void); voidOS_CPU_SR_Restore2(void); extern INT8U _bEA_8BIT_; //临界区模式2全局保存8位EA #define OS_ENTER_CRITICAL() OS_CPU_SR_Save2() /* 使用时可省去定义变量, 用着方便. 最多嵌套8次, 效率略低于模式3, 支持函数内嵌套 */ #define OS_EXIT_CRITICAL() OS_CPU_SR_Restore2() #define OS_TASK_SW() {if(_bEA_8BIT_&0x80){_bEA_8BIT_<<=1;_bIE__=IE|0X80;}else{_bEA_8BIT_<<=1;_bIE__=IE;}OSCtxSw(); return;} #endif //OS_CRITICAL_METHOD= 3 :状态字保存在局部变量cpu_sr #ifOS_CRITICAL_METHOD == 3 #define OS_ENTER_CRITICAL() {cpu_sr=((!_testbit_(EA))?0X00:0X80);} /* 直接用宏插入C语言, 省去调用过程. 可嵌套, 但不支持函数内嵌套 */ #define OS_EXIT_CRITICAL() {IE|=cpu_sr;} #define OS_TASK_SW() {_bIE__=IE|cpu_sr; OSCtxSw();return;} #endif (8)其中临界区保护方法“OS_CRITICAL_METHOD”如果选择1,就是前面的直接关闭和打开总中断的方法。 #ifOS_CRITICAL_METHOD == 1 /* 调用OS服务, 可能会使EA意外的被打开, 所以, 调用OS服务前一刻, 要保证EA=1 */ #define OS_ENTER_CRITICAL() EA=0 /* 使用限制: 不支持嵌套, 进入任务前,保证不开所有分中断, 即使EA被意外打开也不会实际有可响应的中断 */ #define OS_EXIT_CRITICAL() EA=1 (9)其中临界区保护方法“OS_CRITICAL_METHOD”如果选择2,则是一种最大可以嵌套8层的方法,具体的方法可以去主贴看说明和讨论。 (10)其中临界区保护方法“OS_CRITICAL_METHOD”如果选择3,则是一种利用局部变量保存总中断状态、嵌套次数不受限制的方法,具体的方法可以去主贴看说明和讨论。 值得一提的是在STM32F单片机上的常见的RTOS包括FreeRTOS、uC/OS-III和RT-THREAD等,缺省的都采用这种方法。 (11)特别说明:这种临界区保护方法程序不依赖于RTOS,可以独立出来,直接应用到裸机编程中。有兴趣的读者可以从主贴中下载程序,然后把这部分孤立出来在自己的程序中试试。
==== 第3部分 ================================================= 四、不关闭总中断的临界区保护方法 (1)前面介绍的关闭总中断的临界区保护方法有一个明显的弊病,就是一竿子打死一船人:少数几个中断出现临界区保护(比如题目中的库函数不可重入问题)需求,就关闭所有的中断,这不合理。尤其是在低优先级中断中或者在后台任务“main”函数中关闭总中断,则高优先级的、甚至是不涉及临界区的中断就被屏蔽了,要等低优先级临界区完成后才被允许中断,对单片机系统的中断实时响应影响十分严重。 (2)那么不关闭总中断,只关闭涉及到的中断(系统的或者用户的中断)来进行临界区保护行不行?网友CosyOS给出了这样一个成功例子《全局不关总中断的RTOS / CosyOS-II for STC MCU》(https://www.stcaimcu.com/forum.php?mod=viewthread&tid=1807&extra=page%3D1)。 在CosyOS中,在进入临界区保护后,只关闭系统中断,不关闭总中断,这样就不会影响到其他中断的实时响应了。下面给出的只是一个简化的裸机编程例子,网友有涉及到此项技术的在RTOS中如何实现和应用的具体细节的问题可以去他的主贴提问,由主家亲自为你解答。 (3)由于本文的题目只涉及到中断0到中断3,按照CosyOS的思路,那么我们可以这样来编写临界区保护函数: void OS_ENTER_CRITICAL(void){ EX0=0; ET0=0; EX1=0; ET1=0; } void OS_EXIT_CRITICAL (void){ EX0=1; ET0=1; EX1=1; ET1=1; } (4)将这两个进入和退出临界区保护的函数带到前面的程序中,就可以解决库函数不可重入的问题。因为进入临界区保护后这四个中断就不会再发生了,题目中的库函数就不会被重入了。 (5)总结:本文介绍了从简单的关闭总中断,到可嵌套的关闭总中断,到上面这个不关闭总中断只关闭涉及的局部中断方法,每个方法各有优越性,适不同的应用场合。 上面的关闭局部中断的方法仍然是不可嵌套的,要实现可嵌套的只关闭局部中断的临界区保护方法,只需要参照上面关闭总中断的方法继续编写程序就可以实现了。 本题目在中断中正常使用不可重入函数的方法属于典型的OS临界区保护范畴,除了上面介绍的三种方法,OS理论库中还有其他的解决方法,只是实现程序较复杂而已。
|