免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 7144 | 回复: 31
打印 上一主题 下一主题

[内核同步] 有关原子操作和 cache 一致性问题 [复制链接]

论坛徽章:
12
寅虎
日期:2013-12-04 20:37:4915-16赛季CBA联赛之广东
日期:2017-08-22 19:23:1215-16赛季CBA联赛之上海
日期:2016-06-18 23:05:05操作系统版块每日发帖之星
日期:2016-06-06 06:20:00操作系统版块每日发帖之星
日期:2016-06-05 06:20:00操作系统版块每日发帖之星
日期:2016-06-03 06:20:002015年辞旧岁徽章
日期:2015-03-03 16:54:152015年亚洲杯之巴勒斯坦
日期:2015-02-10 21:38:08卯兔
日期:2014-10-31 20:42:23申猴
日期:2014-06-11 17:15:10处女座
日期:2014-05-22 09:00:1815-16赛季CBA联赛之广夏
日期:2017-09-25 23:37:46
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-01-05 23:42 |只看该作者 |倒序浏览
本帖最后由 wait_rabbit 于 2013-01-06 12:58 编辑

今天建了两个模型,被结果彻底弄迷糊了,特向大家请教。

============================ 我是美丽的分割线 =================================

模型1:

开启 3 个内核线程,分别绑定在 cpu0 、cpu1、cpu2 上。thread 0 作一个计数器,thread 1 每隔 5 秒将 cnt 备份为 cnt_bk,然后清零 cnt,thread 2 每隔 5 秒打印 cnt_bk。
  1. int cnt;
  2. int cnt_bk;

  3. (cpu0)                 

  4. while(1) {      
  5.         xxoo;      
  6.         cnt++;
  7.         ooxx;                                                
  8. }
  9.                                        

  10. (cpu1)   

  11. while(1) {
  12.         msleep(5000);
  13.         cnt_bk = cnt;
  14.         cnt = 0;
  15. }

  16. (cpu2)

  17. while(1) {
  18.         msleep(5000);
  19.         printk("%d\n", cnt_bk);
  20. }
复制代码
分析:在第一个 5 秒之前, 只有 cpu 0 的 cache 0 中保存有 cnt 的值,根据 MESI 协议,其状态应当为“shared”。当第一个 5 秒 到来之际,cpu 1 的 cache 1 中第一次存入 cnt,并将其存入 cnt_bk 中,然后对 cnt 清零,这时 cpu 1 使用一个“read invalidate”消息。于是 cpu 0 上 cache 0 中 cnt 缓存变得无效,cpu 1 将 0 写入 cache 1中后,cache 1 状态转换为“modified”。然后 cpu 0 继续累加,使用 “read invalidate” 从 cpu 1 的 cache 1 窥视到新的 cnt 值 0,紧接着使 cache 1 中的新值 0 无效,于是 cpu 0 的 cache 状态转换为 “modified”。最后 cpu 0 将 cnt++ 后的值 1 通过“writeback” 写入内存。当下一个 5 秒到来之际, cpu 1 应该从内存中获取最新的 cnt 值。

预测: 假设 cnt 在 5 秒内的正常增长值为 1 亿,那么每次打印出来的 cnt_bk 也应该为 1 亿。

结果: 每次打印出来的 cnt_bk ,大多数为 1 亿,有少量的 2 亿,偶尔还会出现 3 亿。

猜测原因: 从现象上看,是 cpu 1 对 cnt 的清零动作并没有产生效果,似乎 cache 0 和 cache 1 之间的信号延时了?但这个延时达到了 5 秒,甚至是 10 秒之久。这就让人觉得不可思议了。不知道 cpu 1 到底是如何读出 2亿 这个不正常的值?


============================ 我是美丽的分割线 =================================


于是我更改模型,将全部数据使用原子操作。这回就一切 ok 了,打印值都为 1 亿。

  1. automic64_t cnt;
  2. automic64_t cnt_bk;

  3. cpu0

  4. while(1) {
  5.         xxoo;
  6.         automic64_inc(&cnt);
  7.         ooxx;
  8. }

  9. (cpu1)   

  10. while(1) {
  11.         msleep(5000);
  12.         automic64_set(&cnt_bk, automic64_read(&cnt));
  13.         automic64_set(&cnt, 0);
  14. }

  15. (cpu2)

  16. while(1) {
  17.         msleep(5000);
  18.         printk("%d\n", automic64_read(&cnt_bk));
  19. }
复制代码
分析: 第 5 秒到来时,cpu 1 会先发出一个 ”read invalidate“ 信号,cpu 0 收到之后便会使自己的 cache 无效,然后 cpu 1 锁住总线,再将 cnt 清零,并写入自己的 cache 1,然后解除总线锁定。cpu 0 由于“read invalidate”,于是从 cpu 1 的 cache 1 获取新的 cnt 值 0,然后进行下一步操作。所以一切正常。


============================ 我是美丽的分割线 =================================

评论:最后寡人悲催地发现,除了模型 2 会锁总线之外,上边两个模型的分析流程简直就是一模一样的。在 MESI 协议下,锁总线的意义究竟何在呢?模型 1 到底哪里出了问题?还是我的分析过程完全就是错的?还请大家不吝赐教。

论坛徽章:
4
天秤座
日期:2013-10-18 13:58:33金牛座
日期:2013-11-28 16:17:01辰龙
日期:2014-01-14 09:54:32戌狗
日期:2014-01-24 09:23:27
2 [报告]
发表于 2013-01-06 10:07 |只看该作者
本帖最后由 liuiang 于 2013-01-06 14:17 编辑

本段代码并不能从根本上解决这个问题,所以是错误的,小朋友不要参考~~~~,留下来程序是作为反面教材用的~~~~~~~
  1. #include <stdio.h>
  2. #include <pthread.h>

  3. unsigned int cnt;
  4. unsigned int cnt_bk;

  5. void * cpu0(void * arg)
  6. {
  7.     while(1) {
  8.         __asm__ __volatile__(
  9.             "mfence\n\t"
  10.             "incl %0\n\t"
  11.             "mfence"
  12.             : "=m" (cnt)
  13.             : "m" (cnt)
  14.             :"memory");
  15.     }
  16. }
  17. void * cpu1(void * arg)
  18. {
  19.     while(1) {
  20.         sleep(5);
  21.         __asm__ __volatile__("mfence":::"memory");
  22.         cnt_bk = cnt;
  23.         cnt = 0;
  24.         __asm__ __volatile__("mfence":::"memory");
  25.     }
  26. }
  27. void * cpu2(void * arg)
  28. {
  29.     while(1) {
  30.         sleep(5);
  31.         printf("cnt_bk = 0x%08x\n", cnt_bk);
  32.     }
  33. }

  34. int main(int argc, char * argv[])
  35. {
  36.     pthread_t taskId1, taskId2, taskId3;

  37.     pthread_create(&taskId1, NULL, cpu0, NULL);
  38.     pthread_create(&taskId2, NULL, cpu1, NULL);
  39.     pthread_create(&taskId3, NULL, cpu2, NULL);

  40.     void * tret;
  41.     pthread_join(taskId1, &tret);
  42.     pthread_join(taskId2, &tret);
  43.     pthread_join(taskId3, &tret);

  44.     return 0;
  45. }
复制代码

论坛徽章:
12
寅虎
日期:2013-12-04 20:37:4915-16赛季CBA联赛之广东
日期:2017-08-22 19:23:1215-16赛季CBA联赛之上海
日期:2016-06-18 23:05:05操作系统版块每日发帖之星
日期:2016-06-06 06:20:00操作系统版块每日发帖之星
日期:2016-06-05 06:20:00操作系统版块每日发帖之星
日期:2016-06-03 06:20:002015年辞旧岁徽章
日期:2015-03-03 16:54:152015年亚洲杯之巴勒斯坦
日期:2015-02-10 21:38:08卯兔
日期:2014-10-31 20:42:23申猴
日期:2014-06-11 17:15:10处女座
日期:2014-05-22 09:00:1815-16赛季CBA联赛之广夏
日期:2017-09-25 23:37:46
3 [报告]
发表于 2013-01-06 10:43 |只看该作者
回复 2# liuiang

大贤,您直接用内存屏障应该是没问题的。

我想不明白的是,第一个模型中,我分析的那个 MESI 协议为神马不起作用。似乎这里不关乱序的事情吧。如果能够确保 cache一致性,读取应该都能得到正确的值。




   

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
4 [报告]
发表于 2013-01-06 12:06 |只看该作者
本帖最后由 zylthinking 于 2013-01-06 12:22 编辑

mark 一把, 我也想不明白;
不会是 cpu0 刚刚变成invalidate, 然后马上 ++, 导致 cpu1 刚刚获得的值立马invalidate, 然后这两个就这样拉锯, 导致cnt = 0 总是无法写出?
有脚趾头想似乎也能知道太不可能了。。。。。。。。。, 好歹也是 5秒。。。。。。。

而且, 一直不是很清楚这类打架情况 MESI  会怎么处理, 在 CPU1 read invalidate 获得最新值, 立马被 CPU 0 的 invalidate 给将值抢回去, 那么 CPU 1 在继续执行前, 会再次发出invalidate吗?

论坛徽章:
4
天秤座
日期:2013-10-18 13:58:33金牛座
日期:2013-11-28 16:17:01辰龙
日期:2014-01-14 09:54:32戌狗
日期:2014-01-24 09:23:27
5 [报告]
发表于 2013-01-06 12:17 |只看该作者
本帖最后由 liuiang 于 2013-01-06 16:06 编辑

问题不是出在cache一致性协议上,而是出在write buffer上。


编辑:前半句没问题,后半句的描述不全面,事实上write buffer只是问题的一个方面,仅牵扯到incl的读部分。

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
6 [报告]
发表于 2013-01-06 12:23 |只看该作者
本帖最后由 zylthinking 于 2013-01-06 12:32 编辑
liuiang 发表于 2013-01-06 12:17
问题不是出在cache一致性协议上,而是出在write buffer上。

跟 write buffer 有什么关系?cpu 1 的invalidate 似乎必须是cpu 0 将值更新到内存而不仅仅是write buffer 就算完的; 不对, 我也不确定会不会更新到内存, 但如果 CPU 0侦测到 read invalid request 是肯定返回的最新值的, 相同,  CPU 1 也会返回其最新值; 也就是 0, 和 0 更新没更新到 内存 没太多关系

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
7 [报告]
发表于 2013-01-06 13:13 |只看该作者
本帖最后由 zylthinking 于 2013-01-06 13:22 编辑

貌似明白了, 这个其实和cache一致性, 和write buffer 都没有任何关系, 而是和寄存器有关系

首先揭示一个错误, 照这个帖子的理解, 其实是可以得出 MESI 能够解决任何多线程同步问题的, 因为任意不同步, 都会被 MESI 的 read probe, read probe before write 给同步, 这显然是不正确的
剩下的问题就简单了, 就是 cache 一致性在什么时候起作用, 自然是 read 的时候, 往细了说, 恐怕牵扯的就太多了, 我也不了解, 往粗了说应该可以说是 CPU 将值load 进寄存器的那一瞬间, 在这个时候, 可以保证是一致的(自然, 在更细粒度看来, 估计是错误的说法)

那么再看汇编层面, 照现象来看, cnt 应该没有被优化成寄存器, CPU 0 的操作其实可以分解为
load cnt to r1
set r2 = r1 + 1
mov r2 to cnt
这三个操作,  cache 一致性保证了第一个语句时是一致的; 但在执行第二条语句时, 很可能 CPU 1 已经将 cnt 变成 0  了, 但 r1 是寄存器, 和 cnt 在 CPU 0 看来, 根本没有关系,  和 其有关系的是  L1 缓存中的entry, 那么 CPU 0做的操作就是将L1缓存失效, 但不影响 r1, 自然最终结果是不影响 r2, 最后 store r2 到 cnt 时, CPU 0的 read probe before write将 L1 entry 中的对应值更新为 0, 但有什么关系呢, r2还是会覆盖这个值, 导致的结果就是 CPU 1的 cnt = 0 操作被丢弃。

如果这个分析是正确的, 2楼的内存屏障代码, 很可能达不到目的, 甚至就内存屏障原理而言, 用在这个情景下, 似乎没觉得有什么理由, 毕竟, 想把内存屏障当作锁来使用, 根本就是一个错误

论坛徽章:
4
天秤座
日期:2013-10-18 13:58:33金牛座
日期:2013-11-28 16:17:01辰龙
日期:2014-01-14 09:54:32戌狗
日期:2014-01-24 09:23:27
8 [报告]
发表于 2013-01-06 14:22 |只看该作者
本帖最后由 liuiang 于 2013-01-06 16:07 编辑

二楼程序无法从根本上解决楼主问题,根本问题还是write buffer中的数据不能强行flush出。x86貌似有个WBINVD的指令吧,不过只能工作在内核态。

另外CPUID这类串行指令不确定是否能解决该问题,你们有时间可以试试看,木有多少理论根据的。


编辑:此楼也不靠谱,write buffer只是一个方面。另外WBINVD和CPUID都不能解决这个问题。

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
9 [报告]
发表于 2013-01-06 14:54 |只看该作者
本帖最后由 zylthinking 于 2013-01-06 15:00 编辑
liuiang 发表于 2013-01-06 14:22
二楼程序无法从根本上解决楼主问题,根本问题还是write buffer中的数据不能强行flush出。x86貌似有个WBINVD ...


就我目前理解而言, 你的代码根本就是错误的; 你的代码达不到目的, 如果出 2 亿 3 亿的, 还会照样出; 而且, 你将内存屏障当作临界区来用, 更是根本性的错误
严格来说, 你将 mfence 从你代码中去掉, 和你加上几乎没有区别; 你代码中真正起了一点作用的其实是incl 了一个内存地址, 想借助汇编语句单条语句的原子性; 但实际上也未必能如愿, 实际的情况是, 在多核下, 单条汇编指令的原子性已经不能保证了, 就算不是所有的汇编指令, 至少你这个 incl memory 应该包含在其中

论坛徽章:
4
天秤座
日期:2013-10-18 13:58:33金牛座
日期:2013-11-28 16:17:01辰龙
日期:2014-01-14 09:54:32戌狗
日期:2014-01-24 09:23:27
10 [报告]
发表于 2013-01-06 15:17 |只看该作者
貌似此题不用lock前缀,也不用锁,是无法解决的啊~~~~~

即使WBINVD貌似也不行。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP