188089942 发表于 2016-12-2 17:30:18

在中断程序内 变量的加加,这种情况是否会出现BUG!

先说明一下   a=a+1这条程序语句的执行步骤:
javascript:;

我在想这种情况:
如果执行到第一步,将a的值读到寄存器R中

执行完这一条后,来了个中断就是 a=a-1 !
等执行完 a=a-1 ,这时候重点来了!

我的CPU会回到 a=a+1 的第二步!
R寄存器里面是不知道 a 已经被改变了,所以他还是按照原先的值 加1!
然后在写回到 a !

这时候就会出现bug了!
这种情况大伙们有考虑过吗?要怎么避免?

leafstamen 发表于 2016-12-2 17:52:03

互斥量
这种既在主程序写入,又在中断写入的用法,什么情况下会用到?

zhenglingo 发表于 2016-12-2 18:02:35

开关中断保护共享资源,有原子指令也可以。

188089942 发表于 2016-12-2 18:57:49

leafstamen 发表于 2016-12-2 17:52
互斥量
这种既在主程序写入,又在中断写入的用法,什么情况下会用到?

串口驱动

188089942 发表于 2016-12-2 19:00:39

zhenglingo 发表于 2016-12-2 18:02
开关中断保护共享资源,有原子指令也可以。

如果不关闭中断呢?
还有其他办法吗?



amigenius 发表于 2016-12-2 19:02:39

放心,编译器会处理好,如果自己写汇编,中断内程序用到的寄存器都得先入栈,这是常识基础{:sweat:}

188089942 发表于 2016-12-2 19:08:45

188089942 发表于 2016-12-2 18:57
串口驱动

好像并不会 一个头指针 一个写指针

188089942 发表于 2016-12-2 19:09:42

amigenius 发表于 2016-12-2 19:02
放心,编译器会处理好,如果自己写汇编,中断内程序用到的寄存器都得先入栈,这是常识基础 ...

您没有认真看 认真思考我的问题!

amigenius 发表于 2016-12-2 19:14:33

188089942 发表于 2016-12-2 19:09
您没有认真看 认真思考我的问题!

大神,请原谅我见识肤浅,您慢慢研究,我还要滚去写代码,后续的产品还要写个几百万行的程序。

amigenius 发表于 2016-12-2 19:17:04

顺便提醒您一句,主循环和中断都有读写的变量,要加Volatile。

armstrong 发表于 2016-12-2 19:37:50

只要有抢占执行出现,就会出现这个问题。这是互斥访问的来由,有些CPU提供原子操作,如果没有,则"关中断->读修改写->开中断"解决。

armstrong 发表于 2016-12-2 19:39:48

楼上的,基础不扎实,多些几百万行代码也枉然;多几百万行垃圾而已,自找麻烦。

redroof 发表于 2016-12-2 20:41:34

这种情况互斥没用。中断又不能阻塞自己。如果没有原子加指令就只能关中断了

15802770321 发表于 2016-12-2 21:03:00

amigenius说的是正确的,如果是c语言,进出中断时,编译器会自动加上保护寄存器的代码,一般要求显示声明某函数是中断函数。
声明int a,如果单片机是16位,你的a也是16位长,如果单片机是32位,你的a也是32位长,a=a+1,就是一条指令执行完,不会被打断。
但如果a为32位,单片机16位,a=a+1,就需要多条语句完成,如果此时中断发生,a值就会被破坏。
还是让188089942多思考吧{:lol:}

188089942 发表于 2016-12-2 21:14:09

15802770321 发表于 2016-12-2 21:03
amigenius说的是正确的,如果是c语言,进出中断时,编译器会自动加上保护寄存器的代码,一般要求显示声明某 ...

这种情况我也知道 这种情况是另外一种bug 现在我们一起先讨论这个bug

188089942 发表于 2016-12-2 21:18:18

15802770321 发表于 2016-12-2 21:03
amigenius说的是正确的,如果是c语言,进出中断时,编译器会自动加上保护寄存器的代码,一般要求显示声明某 ...

保护不了吧 从cpu 指令来看 他怎么保护的呢难道让cpu一定要执行完3条语句才响应中断吗

188089942 发表于 2016-12-2 21:19:41

15802770321 发表于 2016-12-2 21:03
amigenius说的是正确的,如果是c语言,进出中断时,编译器会自动加上保护寄存器的代码,一般要求显示声明某 ...

保护不了吧 从cpu 指令来看 他怎么保护的呢难道让cpu一定要执行完3条语句才响应中断吗

saccapanna 发表于 2016-12-2 21:33:38

我的哥,全局变量属于临界资源,访问要做临界保护。

saccapanna 发表于 2016-12-2 21:34:51

amigenius 发表于 2016-12-2 19:02
放心,编译器会处理好,如果自己写汇编,中断内程序用到的寄存器都得先入栈,这是常识基础 ...

这个不能指望编译器!

15802770321 发表于 2016-12-2 22:13:23

188089942 发表于 2016-12-2 21:19
保护不了吧 从cpu 指令来看 他怎么保护的呢难道让cpu一定要执行完3条语句才响应中断吗 ...

_interrupt void isr(void)
{
    你的代码;
}


asm:
isr
{

关中断
寄存器入栈

你的代码

寄存器出栈
开中断
}

hefq 发表于 2016-12-3 01:25:32

但是我觉得直接赋值前关串口中断不就好了

yuntian 发表于 2016-12-3 06:03:19

6楼是对的,中断时会先保护现场入栈,中断结束出栈

myxiaonia 发表于 2016-12-3 07:29:35

redroof 发表于 2016-12-2 20:41
这种情况互斥没用。中断又不能阻塞自己。如果没有原子加指令就只能关中断了 ...

确实可以有互斥访问,cortex的ldrex和strex就是专为解决这个问题提供的指令

saccapanna 发表于 2016-12-3 07:31:18

amigenius 发表于 2016-12-2 19:02
放心,编译器会处理好,如果自己写汇编,中断内程序用到的寄存器都得先入栈,这是常识基础 ...

中断用到的寄存器入栈,与楼主的问题没关系。楼主说的是全局变量在前后台都被访问,如果前台访问不是原子操作,而是读改写,则可能存在问题。楼主的担心是对的,并且也是很多嵌入式程序员不重视的地方。Volatile 关键字,只是告诉编译器,变量是“易挥发”的,要求每次使用都从RAM中取值,而不是从MCU的寄存器中取上次操作的值。Volatile 可以解决部分问题,比如只读访问,但是像楼主这样,前后台都运算赋值的,那么前后台的赋值可能会与预期相冲突。

mcu5i51 发表于 2016-12-3 08:21:14

如果不想关中断就用临时变量验算一下就知道正确不了

zhchmi10000 发表于 2016-12-3 08:38:50

单核心直接关中断再操作,多核心有互斥访问汇编指令。

lixin91985 发表于 2016-12-3 08:57:11

LZ想多了。。
中断来的时候 R会 入栈

redroof 发表于 2016-12-3 09:12:53

本帖最后由 redroof 于 2016-12-3 09:27 编辑

myxiaonia 发表于 2016-12-3 07:29
确实可以有互斥访问,cortex的ldrex和strex就是专为解决这个问题提供的指令

这种独占写也是原子操作的一种(ex后缀是exclusive独占)。保证一个会成功,另一个会失败请自己重试。供操作系统来构造原子加减函数。
而且这主要是对多cpu使用的。旧arm没有多cpu的就没有这些指令,因为只要关中断再做正常的加减就行了。

楼主写常规的程序,基本没办法在每个地方直接手写这种汇编指令。你如果写正常的“变量++”,就得不到这样的效果。
如果编译器有自带的原子操作,你需要调用“原子操作加”这样的库函数,如果没有那么你得自己封装一个这样的原子操作。
总之,在应用层上,这种东西始终是被叫做“原子操作”的,windows上叫InterlockedXX操作,不叫“互斥”{:titter:}

takashiki 发表于 2016-12-3 09:25:07

15802770321 发表于 2016-12-2 22:13
_interrupt void isr(void)
{
    你的代码;


如果有这样的编译器,那一定是大bug

myxiaonia 发表于 2016-12-3 09:30:31

redroof 发表于 2016-12-3 09:12
这种互斥写也是原子操作的一种。保证一个会成功,另一个会失败请自己重试。供操作系统来构造原子加减函数 ...

其实是这样 互斥的范围更大互斥操作包括原子操作但是互斥操作却不一定是原子操作原子操作是指不可打断的操作例如自增指令没有自增指令的情况下自增操作就没有原子操作办法 所以才有关中断这样的互斥方法

redroof 发表于 2016-12-3 09:41:03

myxiaonia 发表于 2016-12-3 09:30
其实是这样 互斥的范围更大互斥操作包括原子操作但是互斥操作却不一定是原子操作原子操作是指不可 ...

被打断后只要能知道,主动重试也是可以的。从语义上,这也是原子操作。不管你怎么实现,是锁总线还是打断重试。
对应用程序来说,你能调用的就是这一个完整操作。它的名字一般就叫原子操作。
当然你理论上可以用arm的独占写来保护完整的一个大函数,但正常来说不该这么干。一是麻烦,这样的汇编不好写,二是这样就不能移植到锁总线做原子操做的系统了。

helislayer 发表于 2016-12-3 09:43:52

哇,我很震惊啊。这么多人都搞不对。
说什么编译器会自动处理,R 会入栈还有硬件 Bug 的都是错的。
加volatile 也是不够用的。
一定要屏蔽中断或者原子操作。

我举个栗子:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
        irq_count ++;
}

编译出来的代码,我中文注释一下。
080033a0 <HAL_ADC_ConvCpltCallback>:
80033a0:       4a02            ldr   r2,     ; (80033ac <HAL_ADC_ConvCpltCallback+0xc>)
# r2 装入 irq_count 的地址,存放在 pc + 8,就是 80033ac;

80033a2:       6813            ldr   r3,
// r2 内容,就是 irq_count 的内容装入 r3.

80033a4:       3301            adds    r3, #1
// r3 = r3 + 1

80033a6:       6013            str   r3,
// r2 = r3, 把 r3 内容写回 irq_count

80033a8:       4770            bx      lr
80033aa:       bf00            nop
80033ac:       2000050c      .word   0x2000050c

所以80033a4 地方要是来中断修改了 irq_count, 到 80033a6
就会被直接写入 irq_count 而忽略了原来的修改内容。中断保存了
r2 r3 也没有用。

LZ 说的bug 非常存在,一定要禁止中断或者原子操作。

helislayer 发表于 2016-12-3 09:59:56

redroof 发表于 2016-12-3 09:41
被打断后只要能知道,主动重试也是可以的。从语义上,这也是原子操作。不管你怎么实现,是锁总线还是打断 ...

ARM 上面用的原子操作的机制和 X86 不一样。X86 用锁总线。
ARM 是用 ldrex 和 strex 不是锁总线是重试。
这两个都是原子操作的其中一种实现方式。

redroof 发表于 2016-12-3 10:04:05

helislayer 发表于 2016-12-3 09:59
ARM 上面用的原子操作的机制和 X86 不一样。X86 用锁总线。
ARM 是用 ldrex 和 strex 不是锁总线是重试。 ...

没错,就是这样。
实际上对多CPU来说,重试比锁总线要先进一些。{:titter:}
因为锁总线的指令始终是真正的锁了总线,这一条指令之内别的CPU都不能访问内存,不管想访问什么地址(99%以上的情况别的CPU并不是要访问这个冲突的地址)
而失败重试通常情况下是没有开销的,你只是独占了这个地址。别的CPU只要不去读写这个地址,读写别的地方都不会造成任何冲突重试。

qzh 发表于 2016-12-3 17:03:06

长知识了,不同CPU和编译器处理方式不一样

823032003 发表于 2016-12-3 18:00:49

a++这种运算在有些单片机确实可以单指令完成。

helislayer 发表于 2016-12-3 19:44:15

823032003 发表于 2016-12-3 18:00
a++这种运算在有些单片机确实可以单指令完成。

我比较孤陋寡闻,你举例一下什么单片机的指令可以缺省状态保证原子修改?
我知道的,AVR, 8051,MIPS, PIC, ARM, x86, SPARC
都不可以。
例如在 x86 虽然是一个指令可以完成 read modify write back, 但是没有
lock 的前缀指令仍然不是原子的,其他的 CPU 可以在指令中间写。
甚至在 x86 内部是把一条指令分解成更小的 micro code 来执行的。
所以其实是不是一条指令是没有什么差别的。


jm2011 发表于 2016-12-3 21:02:55

在中断里面肯定不敢用互斥锁的,推荐自旋锁;

但是个人也不推荐在中断里面加锁,我一般都是在中断里面发EVENT,另外再单开线程等待,两个线程之间保护;

楼上,MIPS的LL/SC指令就是原子的,ARM和SPARC估计也有;8051和AVR不清楚哈

823032003 发表于 2016-12-3 21:25:29

本帖最后由 823032003 于 2016-12-3 21:29 编辑

helislayer 发表于 2016-12-3 19:44
我比较孤陋寡闻,你举例一下什么单片机的指令可以缺省状态保证原子修改?
我知道的,AVR, 8051,MIPS,...

{:shutup:}
我记错了,是除去load store外,中间add 那一个操作是单指令完成。。。。。。。

helislayer 发表于 2016-12-3 22:03:00

jm2011 发表于 2016-12-3 21:02
在中断里面肯定不敢用互斥锁的,推荐自旋锁;

但是个人也不推荐在中断里面加锁,我一般都是在中断里面发EV ...

中断里面不能用带 spin 版本的 spinlock 的。只能 trylock。
如果中断里面 spin 的话,被中断的如果拿了 lock 就歇菜了。

而且中断里面用 spinlock 又拿不到 lock 就很麻烦,要
推迟处理这些。

MIPS 的 LL 那个和 ARM 的 LdrEx 是一个原理,
都不是一个指令来作 Load-Modify-Writeback, 前面说的
单个指令原子Load-Modify-Writeback 不是一回事 。

lxl_lxl 发表于 2016-12-4 00:18:54

这点有点像脏数据,底层上面都说了那么多就不重复了,也没必要,寄存器保护原子处理了神马的,楼主好像是要最后显示的脏数据问题。

应用层上面,看任务需求,能够在最后一步检查该数据是否有效。
例如
函数入口,检测值为真,读取最新值。
函数运行,期间中断
函数末尾,验证检测值,是否重新计算,或者丢包形式。[数据非要展示那只能楼上提到的关中断处理,真多看程序的设计问题。]
要是非要说最后一步显示数据的时候来中断..只能说...这谁设计的需求,到底是要什么数据。。,加上原子时钟好了比赛数据谁先到。。

参考数据库的脏数据问题,例如银行的存取款,多线程读写,互斥锁,跟楼主提的问题都相似。

helislayer 发表于 2016-12-4 09:33:51

lxl_lxl 发表于 2016-12-4 00:18
这点有点像脏数据,底层上面都说了那么多就不重复了,也没必要,寄存器保护原子处理了神马的,楼主好像是要 ...

不是脏数据,是防止数据变脏的问题。如果已经变脏了,
那损失就已经造成了。数据库的脏数据是比较大型的系统
不一致造成的,不是一回事。

jm2011 发表于 2016-12-4 14:05:45

helislayer 发表于 2016-12-3 22:03
中断里面不能用带 spin 版本的 spinlock 的。只能 trylock。
如果中断里面 spin 的话,被中断的如果拿了...

同意啊,所以说不要在中断里面来处理数据,中断就是发出事件,通知有数据来了;

具体的响应和数据处理在其他的地方做,这个应该是标准做法

Scarlette 发表于 2016-12-4 14:13:24

helislayer 发表于 2016-12-4 09:33
不是脏数据,是防止数据变脏的问题。如果已经变脏了,
那损失就已经造成了。数据库的脏数据是比较大型的 ...

终于见到个思维非常清醒的人了,不容易啊,兄弟{:titter:}
当年我组里一个小屁孩就是,觉得加了个volatile编译器就会搞定一切。教育了好几次,仍然死不肯用自旋锁,理由是测试运行中没看到出错。最后组里所有人都忍无可忍了,只能请他滚蛋{:lol:}

McuPlayer 发表于 2016-12-4 14:59:14

这是典型的临界区问题,某一时刻只能一个访问者,甚至不允许一个读和一个写同时进行、
解决问题的原则就是原子操作,最简单的实现方式就是关中断,但他有额外性能损失
所以很多32位的cpu提供一些硬件上的辅助机制来实现

188089942 发表于 2016-12-5 08:47:58

saccapanna 发表于 2016-12-3 07:31
中断用到的寄存器入栈,与楼主的问题没关系。楼主说的是全局变量在前后台都被访问,如果前台访问不是原子 ...

是的 好多坛友不把问题点搞混了什么会入栈 和 volatile 都没用

188089942 发表于 2016-12-5 08:55:03

823032003 发表于 2016-12-3 18:00
a++这种运算在有些单片机确实可以单指令完成。

例如呢?
变量的放在内存里的,是怎么知道内存原来的值呢,必须有个读的操作吧!
除非这个变量是放在CPU 的寄存器里面吧?

188089942 发表于 2016-12-5 09:09:37

Scarlette 发表于 2016-12-4 14:13
终于见到个思维非常清醒的人了,不容易啊,兄弟
当年我组里一个小屁孩就是,觉得加了个volati ...

死不肯改是因为他学的不够精,不理解为什么会有这样的bug(甚至他自己觉得根本就没这个bug),加上小屁孩的性格倔!

像这样的问题,很多人不肯去钻,不肯花时间去学,以为能写代码能跑起来就可以了。
譬如这个问题,不理解这个CPU++操作是怎么个回事,当然不清楚问题点在哪里,所以这个小屁孩根本就理解不了这个问题!

zengyi 发表于 2016-12-5 09:09:41

如果这个a是标志量的话,一定需要互斥来实现原子操作。

lcofjp 发表于 2016-12-5 10:13:32

楼主说的情况是存在的,这种情况很常见,楼上有些不懂的B瞎BB,楼主别信他们的,在中断外面写变量的时候,一定要保证不要进中断。或许其他高级处理器没问题,但是STM32肯定存在这样的问题。

yoursnemo 发表于 2016-12-5 10:17:14

长期用8位的低档MCU,看到这帖子,涨了好多知识。

saccapanna 发表于 2016-12-5 10:30:35

楼主提到的问题虽然存在,但是发生的概率很小,这种问题基本无法在测试中体现出来。但是批量后,如果一旦遇到问题,可能很致命。不懂原理,也是死活查不出问题在哪里的。这类问题全是比较基础的,但是很多人都没概念,于是垃圾代码不断地产生中……

laoshuhunya 发表于 2016-12-5 11:39:47

lixin91985 发表于 2016-12-3 08:57
LZ想多了。。
中断来的时候 R会 入栈

可能你理解错了,楼主位的意思是:中断所做的修改丢失了。
                                                                              

laoshuhunya 发表于 2016-12-6 22:45:57

yoursnemo 发表于 2016-12-5 10:17
长期用8位的低档MCU,看到这帖子,涨了好多知识。

8位机也是一样的问题。
这个帖子生动地说明了搞嵌入式不了解汇编是行不通的{:titter:}
                                       

redroof 发表于 2016-12-6 23:19:42

laoshuhunya 发表于 2016-12-6 22:45
8位机也是一样的问题。
这个帖子生动地说明了搞嵌入式不了解汇编是行不通的
               ...

8位机才更严重。
如果是32位arm,至少所有的整数读和写无需保护,每次总能完整的完成。对8位机,就算主程序只写中断只读,对大于8位的数据都是不行的。有可能见到一半新一半旧!

落叶知秋 发表于 2016-12-7 09:07:17

以前调程序就遇到过这样的BUG,是这样的:

//中断里:
write();
count++;

//后台里:
if(count)
{
read();
count--;//如果这里中断来了就出问题了,count的值不会不变,而会-1,总数不对
             //中断越频繁,越大几率出现这个BUG
}

后来是加了锁,特殊处理一下才没问题。

片羽之神 发表于 2016-12-7 09:13:06

这个学习了~mark~~~

yoursnemo 发表于 2016-12-7 10:09:12

laoshuhunya 发表于 2016-12-6 22:45
8位机也是一样的问题。
这个帖子生动地说明了搞嵌入式不了解汇编是行不通的
               ...

嗯嗯,汇编还是相当重要的。看到下面redroof的回复,也意识到8位机也有这种问题的存在。单单靠现场保护也不够。

jm2011 发表于 2016-12-7 11:00:07

这个问题是基本功哈,LZ提出的问题是在中断中,其实不光在中断,在多线程里,异步函数里面都有这样的问题;区别主要是中断不能被锁死(锁死了机器就挂了);

另外,许多人对LZ提出的问题不太理解, 看看 a++ 产生的指令就知道:
:
lw $8,
add $8, $8, 1
sw $8,

至少三条指令,这三条指令的序列被打断后对地址进行修改都会造成错误结果;对8位机需要多个读-修改-写操作,但是本质还是一样的;

zhugean 发表于 2016-12-7 12:04:31

如果指令系统不支持原子操作的话,就要手动处理

knight_sh 发表于 2016-12-7 12:30:14

落叶知秋 发表于 2016-12-7 09:07
以前调程序就遇到过这样的BUG,是这样的:




建议用生产消费模型,也就是中断内维护一个wr,任务里维护一个rd,if(wr != rd)表明有新的事件

落叶知秋 发表于 2016-12-7 13:34:55

本帖最后由 落叶知秋 于 2016-12-7 13:38 编辑

knight_sh 发表于 2016-12-7 12:30
建议用生产消费模型,也就是中断内维护一个wr,任务里维护一个rd,if(wr != rd)表明有新的事件 ...


//中断
write();
wr++;

//任务
if(wr != rd)
{
count = wr - rd;
read(count);
rd += count;
}
不知道你用的模型是不是这样处理的?

redroof 发表于 2016-12-7 21:18:48

落叶知秋 发表于 2016-12-7 13:34
不知道你用的模型是不是这样处理的?

你这样是个正确的无锁队列,不论任何一方被打断都是安全的。

helislayer 发表于 2016-12-8 01:31:18

redroof 发表于 2016-12-7 21:18
你这样是个正确的无锁队列,不论任何一方被打断都是安全的。

兄台这个大多数是正确的,但是这个话不可以说
这么满。

首先这个假定是只有一个消费者。如果有多个
消费者线程那结果就不对了。

第二,假定这个是只有一个producer,如果有
两个 producer 线程或者有多级中断都有可能
写入,这个代码也就不对,和 LZ 提到的问题
本质一样。

第三,就算只有一个producer 只有一个消费者。
这个代码仍然有可能不对,因为 wr 有可能会溢出。
如果write 一次写太多,wr 是个 8 位的 int, 那么
累计写超过 256 没有去 read 那结果也是不对的。

如果 wr 是 32 位会相对难溢出一点,但是这个情况
也要有考虑。这个是可以在代码里面有体现的。
我就留给有心的程序员来补全了。

redroof 发表于 2016-12-8 07:51:09

helislayer 发表于 2016-12-8 01:31
兄台这个大多数是正确的,但是这个话不可以说
这么满。



简单的无锁队列只有一个读一个写。我认为这是不需要说的。用的人都知道。还有,计数器不能超过你cpu位数。不然无法原子读写。
中断里写没检查是否满,但问题不大,应该靠设计保证,因为中断又不能阻塞自己。队列满了你只能丢数据了。

redroof 发表于 2016-12-8 09:04:38

本帖最后由 redroof 于 2016-12-8 09:08 编辑

helislayer 发表于 2016-12-8 01:31
兄台这个大多数是正确的,但是这个话不可以说
这么满。



我贴一个完整的无锁队列来终结讨论吧。
这是真正完整的代码。模板参数buflen是队列缓存的长度,T是队列里装的数据类型。
读的一方使用GetData,写的一方使用PutData。限制:只能有一个读者和一个写者。读者不能写,写者不能读。
为了防止队列空的时候去读或者队列满了去写,读写之前都应该用isFull和Len来检查。这两个函数任何地方都可以用。
不管是用于中断和应用程序或者多个线程之间传递数据,都可以随便用。
你看看还有没有什么问题{:titter:}

template<int buflen, typename T>
        class TLocklessQueue
{
protected:
        T BufData;
        int PutIdx;
        int GetIdx;

public:
        int MaxSize() {
                return buflen;
        }

        int Len() {
                return (PutIdx - GetIdx + buflen) % buflen;
        }

        bool isFull() {
                return Len() == buflen - 1;
        }

        bool isEmpty() {
                return PutIdx == GetIdx;
        }

        void PutData(const T &aput) {
                int aa = PutIdx + 1;
                aa %= buflen;
                BufData = aput;
                PutIdx = aa;
        }

        T GetData() {
                int aa = GetIdx + 1;
                aa %= buflen;
                T aget = BufData;
                GetIdx = aa;
                return aget;
        }
};

knight_sh 发表于 2016-12-8 09:18:49

helislayer 发表于 2016-12-8 01:31
兄台这个大多数是正确的,但是这个话不可以说
这么满。



这里只讨论临界区的数据安全问题,你提到的数据被覆盖,这个非常正常:要么覆盖要么丢弃!
这个需要在系统设计的时候就应该考虑的问题,从系统层面去解决

knight_sh 发表于 2016-12-8 09:27:57

redroof 发表于 2016-12-8 09:04
我贴一个完整的无锁队列来终结讨论吧。
这是真正完整的代码。模板参数buflen是队列缓存的长度,T是队列里 ...

代码没仔细看,但是看到%操作就非常不爽了,:)
一般环形缓冲区的大小len设置成2的N次幂,那么mask = len - 1,读写指针的位置 index = wr(或rd) & mask;
另外在有些系统中需要通过内存屏障来保障数据完整性(读写指针偏移一定是数据已写入/读取后才操作的)。

redroof 发表于 2016-12-8 09:34:28

knight_sh 发表于 2016-12-8 09:27
代码没仔细看,但是看到%操作就非常不爽了,:)
一般环形缓冲区的大小len设置成2的N次幂,那么mask = le ...

编译器不笨的。
模板参数是编译时常量,如果是2的整数次方会自动优化。其实除以其它的小整数也都会有对应的优化,你不必操心这种小事。我这么写了很多年了。
因为这是库,也不能硬性指定必须是2的整数次方。别人要4K个缓存如果还欠一点,难道必须加到8K?太浪费了。

redroof 发表于 2016-12-8 09:54:37

knight_sh 发表于 2016-12-8 09:27
代码没仔细看,但是看到%操作就非常不爽了,:)
一般环形缓冲区的大小len设置成2的N次幂,那么mask = le ...

另,这个队列对指针的操作是有讲究的,进函数的时候读它,然后用这个指针去读/写数据,然后把指针加一,再写回。
这个顺序是精确考虑过的。指针写回是最后一步,这个时候数据已经读/写完毕。别处见到指针的新值就表示这个数据项已经完整了。
因此无需额外的内存屏障指令。

hdxet 发表于 2016-12-8 10:28:56

讨论的很热闹,受教不少

helislayer 发表于 2016-12-8 21:56:56

knight_sh 发表于 2016-12-8 09:18
这里只讨论临界区的数据安全问题,你提到的数据被覆盖,这个非常正常:要么覆盖要么丢弃!
这个需要在系 ...

我很同意你说的要么覆盖要么丢弃。我比较倾向丢弃。
覆盖的内容让别人读到错误数据,这个是比较不好的。
丢弃的话至少保证已经写进去的是正确的。
覆盖的另外一个名字就是 data corruption。

另外我不知道你说的系统层面上是什么意思。
这个代码层面就应该解决。例如添加的时候要检查
有多少剩余空间,写的东西不要超过剩余的空间。
这个是很基本的程序员修养问题。
程序本身可以解决的,不需要其他的什么系统约定。



helislayer 发表于 2016-12-8 22:03:57

redroof 发表于 2016-12-8 07:51
简单的无锁队列只有一个读一个写。我认为这是不需要说的。用的人都知道。还有,计数器不能超过你cpu位数 ...

这个前提还是要仔细考量和强调的。
例如这个如果是个串口的 write。你如果在两个级别的中断
里面分别有两个打印串口,就会出现不小心引入多个producer
的情况。或者主程序和中断分别有写串口。这些都很容易犯
的错误。出错起来经常很莫名其妙,所以要份外小心。

溢出是一个现实问题,和计数器超过和 cpu 位数没有关系。
这个关键是代码没有检查剩余空间就写入是很不好的。

helislayer 发表于 2016-12-8 22:12:35

redroof 发表于 2016-12-8 09:04
我贴一个完整的无锁队列来终结讨论吧。
这是真正完整的代码。模板参数buflen是队列缓存的长度,T是队列里 ...

这个首先是不太应该用 C++ template,特别是在中断里面。
C++ class 里面很容易有隐含的 malloc,在中断里面容易出很隐晦的问题。
其他那个 % 是完全不必要的,可以用 ? : 来代替。
另外 aa 是个很糟糕的名字。
其他的问题还没有仔细看。

tangnyzl 发表于 2016-12-9 07:11:18

用个函数,加个锁才搞,不然肯定要出问题

jaky80000 发表于 2017-1-4 22:38:18

留个记号,学习一下

int 发表于 2017-1-5 08:31:40

关中断。然后关中断的时间要尽最大可能的短。如果有操作系统可以试一下别的临界区保护的方法。

redroof 发表于 2017-1-5 08:39:10

helislayer 发表于 2016-12-8 22:12
这个首先是不太应该用 C++ template,特别是在中断里面。
C++ class 里面很容易有隐含的 malloc,在中断 ...

请明示哪里有隐含malloc?
我用这个代码好多年了,从没发现需要动态内存分配…

redroof 发表于 2017-1-5 08:47:19

helislayer 发表于 2016-12-8 22:12
这个首先是不太应该用 C++ template,特别是在中断里面。
C++ class 里面很容易有隐含的 malloc,在中断 ...

如果你非要在这个队列里装那种内部自带有动态分配内存的对像,那也不算是我的这个代码在分配内存,对吧!要注意的是你装进去的对像自身的代码。
只要正确使用,这个队列是完全安全的,不用怀疑。
C++的模板从来不会隐含什么,跟你手工对每种特定类型各写一份代码一样高效。

redroof 发表于 2017-1-5 08:52:50

helislayer 发表于 2016-12-8 22:12
这个首先是不太应该用 C++ template,特别是在中断里面。
C++ class 里面很容易有隐含的 malloc,在中断 ...

另,除一个小常数跟做条件判断哪个快哪个慢是不定的。
因为条件判断要打断流水线,越高频的cpu对这个越敏感。一次跳转预测不中,一二十个周期就没了!

Ray______ 发表于 2017-1-9 00:35:11

看了每一层楼,大致有个自己的想法,想提出个问题证实下。56楼的代码,假设中断内容不变,循环内容所做的只是清0,是否会有这个问题。我感觉这问题有点类似中断和程序里用了共同的函数,容易重入发生错误相类似,不知想法是否正确。

szxszx 发表于 2017-1-9 08:17:02

中断中只设标志位,主循环根据标志位状态做运算,避免前后台对同一个变量进行写操作

654705188 发表于 2017-5-23 10:03:41

saccapanna 发表于 2016-12-3 07:31
中断用到的寄存器入栈,与楼主的问题没关系。楼主说的是全局变量在前后台都被访问,如果前台访问不是原子 ...

正解,做过RTOS的对这个体会应该比较深刻。

擦鞋匠 发表于 2017-5-23 14:42:51

额,为什么大家纠结于此...
既然是在sram区域,可以定义在位带操作区然后直接使用位带操作呀,立马变成原子操作...

1066950103 发表于 2023-7-26 13:28:27

好东西 继续聊下去

waymcu 发表于 2023-7-27 00:29:39

关注一下!

tang_qianfeng 发表于 2023-7-27 08:18:18

arm没有对某个内存地址加1的原子操作指令吗?
页: [1]
查看完整版本: 在中断程序内 变量的加加,这种情况是否会出现BUG!