Gorgon_Meducer 发表于 2008-1-17 10:56:19

[讨论]实战嵌入式系统中的临界、死锁和原子操作

本文摘抄自《深入浅出AVR单片机——从ATMega48/88/168开始》,保留所有权利,不得转载
-------------------------------------------------------------------------------------------------------
第二篇 第二章 对不起 接个电话

……
(前文略)

2.13 前后台与原子操作

    开始本节的讲解之前,我们首先来看一个中断处理的例子:假设单片机有一个硬件资源,允许我们从外界获取某一信息。信息的采集需要时间,即当我们发送开始采集指令一段时间以后,才能获得所需的信息。单片机可以在此时发生一个信息采集完成中断。
    我们不妨设开始采集指令为START_SAMPLING;信息采集完成中断处理程序为INFO_Sampling_CPL_ISR()。由于系统除了信息采集、加工以外还需要完成其他工作,为了提高工作效率,我们在后台利用中断采集信息并设置标志,在前台主循环中检测这一标志——当标志被设置时处理信息并将标志复位。这实际上是一种典型的前后台处理策略,即后台采集信息,前台检测标志并处理信息的模式。根据上面的描述,一个可能的代码如下:

//一个前后台的例子

BOOL g_bIfGetINFO = FALSE;                            //采集完成标志
uint16 g_wINFO = 0;                                 //保存最新的采样信息

//采集完成中断处理程序
void INFO_Sampling_CPL_ISR(void)               
{
    GET_INFO(g_wINFO);                              //从硬件中获取采集到的信息
    g_bIfGetINFO = TRUE;                              //设置采集完成标志
}

void main(void)
{
   START_SAMPLING;                                  //开始第一次采样
   ……
   while(1)                                       //主循环
   {
        ……
        if (g_bIfGetINFO)
        {
          /*这里添加对信息的处理代码*/
          PROCESS_INFO(g_wINFO);               //处理信息
             ……
               START_SAMPLING;                        //启动下一次采集
               g_bIfGetINFO = FALSE;                  //复位采集完成标志
        }
      }
}

    上面的代码在执行的大部分时刻不会出现问题,只是偶尔信息采样会莫名其妙的被终止。在单片机任务负担不重时,这种偶发的故障不易被察觉,即便发生也常常被当作单片机不稳定或者跑飞来对待。随着程序规模的扩大、单片机中可能同时开启了多个中断资源。同时,为了防止中断的丢失,我们常常会在中断处理程序中开启全局中断响应(系统进入中断处理程序时会默认关闭全局中断响应,此时可以通过语句SEI();人为开启这一响应)。此时,信息采样以外终止的现象就愈发频繁起来。问题究竟出在什么地方呢?难道真是单片机硬件不稳定么?

http://cache.amobbs.com/bbs_upload782111/files_9/ourdev_202212.gif


    我们说,随着程序规模的扩大,某一故障愈发严重,虽然不能完全断定这就是软件故障,但至少说明现象的发生与程序有不可忽视的渊源。通过对图2.2.16的观察我们发现:程序运行的某一时刻,在我们执行指令START_SAMPLING之后、执行g_bIfGetINFO = FALSE;之前,主程序流程被中断——转到某一个其它的中断处理程序中去执行。由于我们在此前下达了START_SAMPLING指令,系统已经开始信息的采集;在中断处理程序执行期间,系统可能有足够的时间完成了信息采样。如果我们在中断处理程序中开启了全局中断响应,信息采样完成中断发生时就会立即得到响应——一个中断嵌套发生了。和普通情形一样,信息采样完成中断处理程序会将标志变量g_bIfGetINFO置为TRUE,以告知主程序信息采样完成。然而,当系统完成了所有的中断处理工作,再次回到主流程时,首先遇到的就是对标志变量g_bIfGetINFO的置FALSE操作——虽然在中断处理程序中,我们正确地设置了标致,然而,这一意想不到的流程却错误地修改了标志变量的内容。对照前面的代码,可知只要标致g_bIfGetINFO不为TRUE,就不会触发下一次的信息采样——这就等于告诉我们,当发生上述情形时,信息采样工作一定会被打断,而这种情形的发生及乎是一定的,只不过概率在程序使用的中断资源较少时概率较小罢了。
    知道了故障的原因,如何处理呢?有人说,直接将启动采集和设置标志位的操作顺序交换一下不就行了?的确,在启动某一操作之前首先完成相关的初始化和环境设置工作,是避免此类故障的必要条件,我们应该养成这种好的习惯。但是,并非所有的标志设置都可以在操作启动之前完成。还以前面的讨论为例,假设我们需要一个标志变量来表示信息采集工作已经启动(用变量g_bIfSamplingStarted来表示)。显然,对这一标志的设置必须在START_SAMPLING 以后完成——也就是说,前文提到的现象有可能再次发生。面对这种情形,又叫我们如何是好呢?
    在20世纪初,人们一度认为原子是最小的物质单位,即“原子不可再分”,这种观点虽然后来被证明是错误的——物质无限可分,但在这里,我们需要借用这一概念。习惯上,我们把一些彼此相关联,执行时不允许外界打断的操作称之为原子操作。例如,前面文中启动采样和设置标志就应该被定义为原子操作——如果执行这一操作时禁止了外界中断的打扰,那么就不会导致前面提到的错误。

//原子操作的例子
CLI();                                     //暂时关闭全局中断响应
   START_SAMPLING;                     //启动采样
   g_bIfSamplingStarted = TRUE;          //设置标志
SEI();                                     //恢复全局中断响应

虽然原子操作的实现远非简单禁止中断那么简单,但在简单的前后台系统中,暂时禁止中断响应至少是实现原子操作的途径之一。因此,我们不妨定义一个原子宏,以方便以后的使用:

//原子宏
# define SAFE_CODE_PERFORMANCE(Code)        \
                                           CLI();\
                                           Code;\
                                           SEI();

//应用实例
SAFE_CODE_PERFORMANCE
(                                                         //注意这里不是花括号
                START_SAMPLING;
                g_bIfSamplingStarted = TRUE;
)                                                         //这里也不是花括号,可以省略分号

    最后,我们可以为大家把视野再扩展一些:但凡自己使用时绝不允许别人使用的区域,我们称之为临界区域。显然,用原子操作描述的区域就是一个临界区域。对临界区域的定义,可以通过设定标志两的方法来实现,例如CLI();就是设置了一个临界标志,而SEI();则是清除了这一标志。与之相关的知识还有死锁等——由于篇幅限制,这里就不再继续展开;有兴趣的朋友可以参考操作系统的相关内容。
    原子问题有趣而又富有挑战,前面的叙述只能算是为大家展现了冰山一角,随着学习的深入,大家会越来越多地接触到与之相关的现象。细心总结、不钻牛角尖是学习和处理这类问题的捷径。我们在这里抛砖引玉,只希望能给大家提供一种解决同类问题的思路。

Recoochang 发表于 2008-1-17 12:01:25

支持一下~!

PS:楼主用什么输入法?还可以接电话啊?(*^__^*)

Gorgon_Meducer 发表于 2008-1-17 12:10:29

……这是这一章的名字……呵呵……这个章节是中断章节,开篇用回忆中被电话打断的实例作为引子。
高手们就不要见笑了,主要是让初学者觉得比较形象罢了……

passerby 发表于 2008-1-17 12:48:07

支持一下,
傻孩子写的书,一定不差的,我要去买一本.

eagle1979 发表于 2008-1-17 15:11:46

什么时候出版?一定要买一本

ATmega32 发表于 2008-1-17 15:17:42

cli();
         …………
         sei();
不好。如果在原子操作之前全局中断是关闭的,会误操作地把全局中断打开。



原子操作更好的办法是

    uint8 flags=SREG;
      cli();
   ………………
   SREG=flags;

Gorgon_Meducer 发表于 2008-1-17 15:18:28

5楼的方法是正解!

ATmega32 发表于 2008-1-17 15:59:39

临界断我自己分成两种,系统级临界断和硬件级临界断。

进系统级临界断只关闭系统管理中断;
进硬件级临界断关闭总中断。

与之对应,中断也分为两种:系统级中断 和 硬件级中断(硬件级中断不调用任何OS内核函数)

系统级中断:受系统级临界断和硬件级临界断影响;
硬件级中断:不受系统级临界断影响,只受硬件级临界断影响。


以上为理想状态,
实际操作要实现系统级中断和硬件级中断分开非常困难。

ecat 发表于 2008-1-17 16:58:31

题目很象是搞核物理的啊!

shaoshunda 发表于 2008-1-18 09:43:10

楼主高人

armok 发表于 2008-1-18 12:19:25

好贴,COOL!

avruser 发表于 2008-1-18 12:24:51

楼主是该书作者吗?
傻孩子:我当然是该书的作者阿。

heroxue 发表于 2008-7-30 16:41:25

标记一下

rbctt 发表于 2008-9-14 11:11:29

傻孩子出的书!我也买了本!好书

tujiangwei0808 发表于 2008-11-4 18:59:34

 请问为什么不把“启动下一次采集"的代码也放进中断里面,这样可以保证中断循环进行;

而主程序只负责检查到标志位置位后处理数据 

Gorgon_Meducer 发表于 2008-11-4 23:21:41

to 【14楼】 tujiangwei0808 

    这样会导致程序AD采样部分疯跑……占用大量系统资源……

    同样的例子还有,在SPI的接收完成中断里面加入SPI缓冲区的发送程序,也会导致SPI处理部分占用

大量系统资源。

ba_wang_mao 发表于 2008-11-24 08:39:53

中断服务程序要“越短越好”这。例如:定时中断服务程序中,只把跟时间紧密的代码放在定时中断服务程序中。

watercat 发表于 2008-11-24 09:10:45

挑个刺:物质是以普朗克长度为单位有限可分的……纯哲学的YY毕竟不能代替物理学研究……

Gorgon_Meducer 发表于 2008-11-24 11:22:36

to 【17楼】 watercat 

    难得水猫有空来挖俺的古董贴……^_^不胜荣幸……只能说现有的观测水准是以普朗克常数为单位

对现有认识的物质进行描述,并不代表没有更小的单位……很期待这次的超级原子对撞能撞出颠覆普朗克

常数的东西……

watercat 发表于 2008-11-24 12:07:18

可能性很小,那个对撞机按照理论上的最好情况,最多也只能模拟到暴涨后或者暴涨后期的状态,至于暴涨前……显然对撞机的能级还远远不够……



另外,根据较新的宇宙(cosmos)理论,其实普朗克尺度之下,便是一个不同于现有的时空区,或者,直接地说,就是一个不同的 Universe,彼处的永恒,只是此处的无间,试图进入其中,本身就意味着失去现有的一切,因此,除非有了颠覆性的新理论诞生,否则,普朗克尺度之下的时空,就不必讨论了



当然,也许数十亿年乃至上百亿年之后,此宇宙的智慧体会为了逃避因果律的必然结局而选择栖身于某个普朗克长度也说不定,不过,那就不是现在的我们所应该考虑的了……



最后,不得不说,老帖子果然很坑人……

Gorgon_Meducer 发表于 2008-11-24 12:47:02

to 【19楼】 watercat

    不同于此处的宇宙……感觉更接近哲学范畴……呵呵,玄幻了,玄幻了

    

mtxmxt 发表于 2008-11-24 13:27:43

分析的很到位,受启发了,以前还真未考虑到。

把“启动下一次采集"的代码也放进中断里面,一般来说这段程序并不长,应该还是可行的。

robinyuan 发表于 2008-12-24 00:18:46

可能性很小,那个对撞机按照理论上的最好情况,最多也只能模拟到暴涨后或者暴涨后期的状态,至于暴涨前……显然对撞机的能级还远远不够…… 



另外,根据较新的宇宙(cosmos)理论,其实普朗克尺度之下,便是一个不同于现有的时空区,或者,直接地说,就是一个不同的 Universe,彼处的永恒,只是此处的无间,试图进入其中,本身就意味着失去现有的一切,因此,除非有了颠覆性的新理论诞生,否则,普朗克尺度之下的时空,就不必讨论了 



当然,也许数十亿年乃至上百亿年之后,此宇宙的智慧体会为了逃避因果律的必然结局而选择栖身于某个普朗克长度也说不定,不过,那就不是现在的我们所应该考虑的了…… 



最后,不得不说,老帖子果然很坑人…… 

***********************************************



水猫也对物理挺关注的啊

现代物理学家玩弄这些名词也不能代表什么东西,毕竟我们周围客观世界已经是完全符合物理学第二定律的了

要想逃避因果律,等人类能够将千万个像太阳这样的核火球来个大对撞创造一个大虫洞的时候吧,我辈在天国等着看热闹呢

imjacob 发表于 2011-2-7 22:04:22

mark

melody520 发表于 2012-3-3 09:14:03

回复【楼主位】Gorgon_Meducer傻孩子
-----------------------------------------------------------------------

mark……前后台与原子操作(Gorgon_Meducer)……很好

ycwjl728 发表于 2012-3-3 09:15:30

Mark!

bluelool 发表于 2012-3-3 10:13:16

mark

at90s 发表于 2012-3-3 11:19:44

mark

fanmingming 发表于 2012-3-7 18:50:11

好贴呀!!!对操作系统的理解有很大帮助

study2440 发表于 2012-3-7 20:57:25

mark   正需要

wele 发表于 2012-3-27 12:09:39

对偶尔发生的奇怪现象 有了解了一个新原因。

Vampireyifeng 发表于 2015-8-14 22:49:50

mark一下,实战嵌入式系统中的临界、死锁和原子操作

水煮鱼 发表于 2015-8-15 09:12:16

mark         

天浪 发表于 2015-8-16 21:01:31

很好的经验,谢谢分享

yick 发表于 2015-8-18 07:05:34

谢谢楼主了

负西弱 发表于 2016-6-25 09:50:48

嗯,学习了,不过在这种简单的情况下,对调一下主循环标志位设置的顺序更方便快捷
if (g_bIfGetINFO)
      {
            /*这里添加对信息的处理代码*/
            PROCESS_INFO(g_wINFO);               //处理信息
             ……
               g_bIfGetINFO = FALSE;                  //复位采集完成标志
               START_SAMPLING;                        //启动下一次采集
      }

int 发表于 2017-1-4 15:43:05

ATmega32 发表于 2008-1-17 15:17
cli();
         …………
         sei();


这个办法好!完全解决了错误打开中断的问题。

bigwei 发表于 2017-6-13 11:39:17

cool 好贴!

bigwei 发表于 2018-4-28 14:14:13

懂了 多谢分享

TKZXJ 发表于 2018-12-18 10:04:30

多谢分享!
页: [1]
查看完整版本: [讨论]实战嵌入式系统中的临界、死锁和原子操作