极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 16813|回复: 6

变量的完整性访问(原子操作和非原子操作)

[复制链接]
发表于 2014-10-23 00:50:58 | 显示全部楼层 |阅读模式

首先我不是学嵌入式的,也不是软件工程的对这行也不是很了解,如果有说错的地方希望大家留言指正,不能让我一直错下去啊!!!!
什么是原子操作和非原子操作(简要说明摘录自伯乐在线http://blog.jobbole.com/54345/ 感兴趣的可以去看看)
非原子性是由于多CPU指令

假设你有一个64位初始化为0的全局变量。
uint64_t sharedValue = 0;
在某些时刻,你给这个变量赋一个64位的值。


void storeValue()
{
     sharedValue = 0x100000002;
}
当你在32位的x86环境下使用GCC来编译这个函数时,将会生成如下机器码。

$ gcc -O2 -S -masm=intel test.c
$ cat test.s
      ...
      mov DWORD PTR sharedValue, 2
      mov DWORD PTR sharedValue+4, 1
      ret
      ...
这个时候你就会看到,编译器会使用两个单独的机器指令来完成这个64位的赋值。第一条指令设置低32位的0×00000002,第二条指令设置高32位的0×00000001.非常明显,这个赋值操作是非原子的。如果共享变量同时被不同的线程存取,就会出现很多错误:

如果一个线程在两个机器指令的间隙先调用存储变量,将会在内存中留下像0×0000000000000002这样的值——这是一个写撕裂。在这个时候,如果另一个线程读取共享变量,它将会接收到一个完全伪造的、没有人想要存储的值。
更糟糕的是,如果一个线程在两个机器指令的间隙先占用变量,而另一个线程在第一个线程重新获得这个变量之前修改了sharedValue,那将导致一个永久性的写撕裂:一个线程得到高32位,另一个线程得到低32位。
在多核设备上,并不是只有先行占有其中一个线程来导致一个写撕裂。当一个线程调用storeValue时,任何线程在另一个核上可能同时读取一个明显未修改完的sharedValue。


以前看IOS的objective-c的编程时常常看到atomic,nonatomic。就是原子操作和非原子操作,那是没有线程概念只知道有这玩意。有人会问那都是有操作系统的时候才需要考虑的事,其实不然如果你用到了中断或者别的使程序执行突然发生改变的事件时那么原子操作和非原子操作就会变得有必要(我们如果在嵌入式上发展,上操作系统时必然的)。前段时间一直在做自平衡小车,参考的飞思卡尔的代码。里面有很多细节还是很值得学习的(毕竟那是官方代码啊!{:soso_e106:} );
这里附上一段我根据飞思卡尔的代码写的原来代码里面用了很多的全局变量(这点个人认为不大好)
受objective-c的影响中毒太深,喜欢把函数名写的特别的长;
  1. /**
  2. * 角度环控制
  3. */

  4. #define ANGLE_OFFSET 0
  5. #define GYRO_OFFSET 0
  6. #define ANGLE_P 54
  7. #define ANGLE_D 0.42
  8. void AngleSabilityControl(
  9.         float &angleControl,
  10.   const float &angle,
  11.   const float &gyro
  12. ) {
  13.     float value;

  14.     value = (ANGLE_OFFSET - angle) * ANGLE_P +
  15.             (GYRO_OFFSET - gyro) *  ANGLE_D;

  16.     angleControl = value;
  17. }
复制代码

上面的代码是一个角度环控制的代码

float value;用来存放中间值,以前我一般都是直接
        angleControl = (ANGLE_OFFSET - angle) * ANGLE_P +
            (GYRO_OFFSET - gyro) *  ANGLE_D;
一开始看float value;不是很理解这样不浪费寄存器嘛(这是一开始的理解);后来好好想想也有其中的道理,考虑到代码的生命力问题,如果对目标的变量写入时,势必可能分多次写入,而在写入过程中被中断了,然后中断函数还用到了你刚刚写入一半的变量,然后去执行操作那么势必会出问题,如果是自平衡小车那么可能直接就倒了(赛格威老板骑赛格威坠崖身亡,很多年期的段子了,拿来YY下说明严重性)。

最后谈谈自己的一些小想法:
转眼间又到了要找工作的时候了。比较的迷茫不知道要干什么了!上半年有幸去苏州大学参加嵌入式的培训(主讲人是王宜怀),课是没怎听明白,不过听他说了几个名词对于我来说比较新鲜:热复位和冷复位;还有就是变量不要在定义的时候就直接赋值,这样有助于后面处理热复位和冷复位;一直没有明白哈,希望大神能解答下!
回复

使用道具 举报

发表于 2014-10-23 02:35:14 | 显示全部楼层

m_

本帖最后由 Super169 于 2014-10-23 02:38 编辑

樓主很認真的研究, 我也有興趣想多了解一下, 先謝謝樓主的分享.

對於32bit processor 用兩個指令去處理  64bit 的資料, 這方面不難理解.

但之後的例子, 有點疑問.  在以下句式當中,

angleControl = (ANGLE_OFFSET - angle) * ANGLE_P +
            (GYRO_OFFSET - gyro) *  ANGLE_D;

樓主提到 "如果对目标的变量写入时,势必可能分多次写入, ...." 所以先用一個 local variable 處理.
這點好像有問題, 以我所知, 一般的 compiler, 遇上類似的算式, 轉變出來的 machine code, 應該會先用內部的 register 去儲蓄數值, 每次的結果都保留, 最後才把最終的值放到程式中的 variable, 並不會把中途的給果 copy 到目標的記憶體中.  這樣可大大減少數值 copy 的工序 (因為電腦的計算, 是不會在記憶體中直接進行, 而是在內部的 register 中計算的), 對運行速度有很大幫助.

所以, 即使沒有用一個 local variable 先儲起來, 也不會有大分別.  個人覺得, 那個 float value 的存在, 對樓主所說中斷的情況, 應該是沒有幫助的.

不知我的理解是否有錯誤?  因為我已很久沒碰 machine code 了, 現在是否有新一代的 cpu 指令, 可以直接在 記憶體中進行運算, 我還是不太肯定.  但從效能上看, 應該不會有這樣的改變吧.
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-10-23 08:47:41 | 显示全部楼层
下面是我在51上的实验,可以看到当图代码执行1图代码执行2时a的值已经发生了变化(见寄存器1和寄存器2)后面又发生了变化!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2014-10-23 10:39:01 | 显示全部楼层
不好意思, 不太明白你想表達的是什麼?
(1) *a 未有設定, 所以數值為 0
(2) *a = b * 5 + c * 4 = 2 * 5 + 4 * 4 = 26 (即 0x1A)
(3) 因為 d = 0, *a = *a * m = *a * (c - b) = 26 * (4 - 2) = 52 (即 0x34)

程式中把數值改變了, 而數值跟據計算結果而變化, 不是很合理嗎?  如果沒有變化才是大問題呢.
所以, 不明白你想表達的是什麼.

另外, 你現在看的是 c 語言層面的運算, 跟你原本提及的 中斷 在兩行 machine code 中出現是完全不同的.
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-10-23 12:24:00 | 显示全部楼层
Super169 发表于 2014-10-23 10:39
不好意思, 不太明白你想表達的是什麼?
(1) *a 未有設定, 所以數值為 0
(2) *a = b * 5 + c * 4 = 2 * 5 + ...

当中断在2和3之间到达,而中断里面的会用到你a的值那么就出现问题了
回复 支持 反对

使用道具 举报

发表于 2014-10-23 14:12:28 | 显示全部楼层
SproutME 发表于 2014-10-23 12:24
当中断在2和3之间到达,而中断里面的会用到你a的值那么就出现问题了

呢就是另一個問題, 而非 32bit 或 64bit 的問題了.  你最初 CPU指令的例子, 就是另一回事了.

之前在另一個 post 我也有提過, 在 single buffer 的情況下, 會有你說的問題出現.  不單止是這樣的計算才有發生, 串口6050 的例子比較明顯.
由 串口 6050 發出的資料, 是有 11 個 byte 的, 而第一個 byte 既決定資料的分類.

很多例子只用一個 single buffer, 接收跟讀取都同相同的地方, 結果就有可以在讀取時, 讀了首幾個 byte 之後, 剛好有 interrupt 進入, 把資料改變了, 結果那 11 bytes 就變成是兩套資料的組合了.

如果要用 interrupt 接收資料, 最好可以用多個 buffer, 最少也分出一個 temp buffer 作讀取之用, 可以的話再加上一些 control, 決定是否月適合變更 讀取的 buffer.  例如在讀取之前, 先試設 reading = 1;  完成讀取就設回 reading = 0, 當有中斷到來, 如果 reading = 1 就不去更新讀取的 buffer 而 mark pending , 將來再用其他方式把 pending 的資料補回, 又或直接放棄該資料.

不知是否可解決你的問題.
回复 支持 反对

使用道具 举报

发表于 2014-10-23 14:19:46 | 显示全部楼层
補充, 如果你說是 interrupt 中要讀取 *a 的值, 本身跟你之前的例子不同.

你之前的例子是用一句指令完成
  1. angleControl = (ANGLE_OFFSET - angle) * ANGLE_P +
  2.             (GYRO_OFFSET - gyro) *  ANGLE_D;
复制代码
現在 *a 的設定要經過多次不同運算去完成, 當中亦出現中斷改變*a 的值, 完全是兩回事來的, 不要把它們混亂了.

而且, 在 interrupt 中讀取變化中的數值, 本身已存在一定的風險, 因為 interrupt 不知會在什麼時候發生, 就以你的例子, 就算 *a 中途不會出現多次改變, 只是簡單的把 *a 設定成 1, interrupt 發生在設定之前或之後, 已是不同意思了.  這點必須本身有好的計劃, 了解 interrupt 在什麼時間出現, 應該讀取到什麼才是正確, 然後再看看程式上是否有問題.  而非單獨看程式就知道是否正確的.
回复 支持 反对

使用道具 举报

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

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-20 11:09 , Processed in 0.039265 second(s), 19 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

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