免费注册 查看新帖 |

Chinaunix

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

[操作系统] ARM 中的原子位操作是如何保证原子性的 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-29 15:46 |只看该作者 |倒序浏览
本帖最后由 lli_njupt 于 2011-12-29 15:47 编辑

  1. linux-2.6.31/arch/arm/include/asm/bitops.h
  2. static inline int
  3. ____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)
  4. {
  5.         unsigned long flags;
  6.         unsigned int res;
  7.         unsigned long mask = 1UL << (bit & 31);

  8.         p += bit >> 5;

  9.         raw_local_irq_save(flags);
  10.         res = *p;
  11.         *p = res | mask;
  12.         raw_local_irq_restore(flags);

  13.         return res & mask;
  14. }
复制代码
如上所示,设置*p的第bit位,并返回原值。在操作中首先禁中断,防止中断打断原子操作,
但是这里并没有使用ARMv6开始提供的锁总线指令ldrex和strex,那么是如何保证原子操作的呢?
而且从CONFIG_SMP宏的配置来看,该版本的内核是明确支持ARM SMP系统的,如果不能保证位
的原子操作,又何谈SMP的支持呢?

论坛徽章:
0
2 [报告]
发表于 2011-12-30 13:39 |只看该作者
你用gcc -S看过assembly吗?你如何确定使用什么instruction的呢?

论坛徽章:
0
3 [报告]
发表于 2011-12-30 13:54 |只看该作者
回复 2# garyv

  1. arch/arm/include/asm/atomic.h
  2. static inline void atomic_set(atomic_t *v, int i)
  3. {
  4.         unsigned long tmp;

  5.         __asm__ __volatile__("@ atomic_set\n"
  6. "1:     ldrex   %0, [%1]\n"
  7. "       strex   %0, %2, [%1]\n"
  8. "       teq     %0, #0\n"
  9. "       bne     1b"
  10.         : "=&r" (tmp)
  11.         : "r" (&v->counter), "r" (i)
  12.         : "cc");
  13. }
复制代码
对整形数据进行原子操作的一个示例,这里没有禁中断,但是通过 ldrex和strex指令锁总线了,
查了查文档,Linux中原子操作的实现并非像教课中所说:不可中断,而是在编码的时候要确保
没有其他的内核路径在临界区中改变它即可。

论坛徽章:
0
4 [报告]
发表于 2011-12-30 14:02 |只看该作者
原来gcc可以分析ISR里面有没有改写临界区里面的变量啊

论坛徽章:
0
5 [报告]
发表于 2011-12-30 15:18 |只看该作者
本帖最后由 lli_njupt 于 2011-12-30 15:20 编辑

回复 4# garyv
这和GCC有何关系?不知楼上何出此言? ISR里改不改变临界区是编码的人决定的,不是GCC!编码的人自己要明白,这是临界区!

论坛徽章:
0
6 [报告]
发表于 2011-12-31 00:07 |只看该作者
回复 5# lli_njupt


    我的意思是说,你的关中断的api最终没有编译成为关中断的指令,可能是因为gcc对临界区变量进行了分析认为没必要关中断!

论坛徽章:
0
7 [报告]
发表于 2011-12-31 00:30 |只看该作者
我稍微说一下我的理解吧。

首先说两点:
第一点:那个atomic_set的define在2.6.35中(也许2.6.31后更早的版本),就已经被替换成了 #define atomic_set(v,i) (((v)->counter) = (i))
而无需借助ldrex,strex来实现。
第二点:ldrex和strex并没有lock总线,而是借助新的monitor机制设置tags来避免锁总线导致的性能损失

而根据armv7(armv6)手册中关于Single-copy atomicity的定义来说,
以下几个操作是atomicity的:
• all byte accesses
• all halfword accesses to halfword-aligned locations
• all word accesses to word-aligned locations
• memory accesses caused by LDREXD and STREXD instructions to doubleword-aligned locations.

atomic_set(atomic_t *v, int i) 的语义是 保证下一级记忆体中会出现 *(&v->counter)==i 这个状态
而____atomic_set_bit(unsigned int bit, volatile unsigned long *p) 的语义是 保证下一级记忆体中会出现  *(&v->counter)的第bit位会为1 这个状态
那么在atomic_set中,如果想在下一级记忆体中出现 *(&v->counter)==i 这个状态,按照atomicity定义以及atomic_t 结构体的define,&v->counter必定会出现在word-aligned的地址上,
那么这个*(&v->counter)= i的操作本身就是被arm所支持的atomicity操作,在c语言级别上直接做赋值操作即可。
____atomic_set_bit的实现同理。

如果搞嵌入式的话,想看懂体系结构相关部分的code,对应芯片的手册也要大致了解一下哟~
欢迎讨论 :)

论坛徽章:
0
8 [报告]
发表于 2011-12-31 19:59 |只看该作者
本帖最后由 lli_njupt 于 2011-12-31 20:00 编辑

回复 7# onlyxuyang

仔细看了下Linux对原子位操作代码的实现,无论单CPU还是SMP系统,都是通过宏对ATOMIC_BITOP的扩展,
而对ATOMIC_BITOP的定义由是否配置CONFIG_SMP决定:

  1. 2.6.39.4版本为例
  2. arch/arm/include/asm/bitops.h
  3. #ifndef CONFIG_SMP
  4. /*
  5. * The __* form of bitops are non-atomic and may be reordered.
  6. */
  7. #define ATOMIC_BITOP(name,nr,p)                 \
  8.         (__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
  9. #else
  10. #define ATOMIC_BITOP(name,nr,p)         _##name(nr,p)
  11. #endif
复制代码
以上的定义说明在单CPU系统上ATOMIC_BITOP的操作分两类:
1.在nr是常量时,调用____atomic_XXX系列函数,比如:
  1. ____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)
  2. {
  3.         unsigned long flags;
  4.         unsigned int res;
  5.         unsigned long mask = 1UL << (bit & 31);
  6. /* Point 1 */
  7.         p += bit >> 5;
  8. /* Point 2 */
  9.         raw_local_irq_save(flags);
  10. /* Point 3 */
  11.         res = *p;
  12.         *p = res | mask;
  13. /* Point 4 */
  14.         raw_local_irq_restore(flags);
  15.         return res & mask;
  16. }
复制代码
为何是在常量的时候才用这一系列函数呢?常量的bit可以使mask赋值在编译时完成,p += bit>>5 可以化简为一条add指令。
由于____atomic_test_and_set_bit整个函数都要保证原子操作,
那么其中不应该发生调度以及中断。显然只有在3和4之间的代码才能满足这个要求,从1到2之间的指令完全有可能被中断,
有意思的是这条用来实现把指针对齐到32的操作,会被编译器优化掉,不生成任何代码,这也要求bit是常量。
2.当bit不为常量时调用了与SMP相同的处理函数
显然此时调用_##name系列函数,比如_test_and_set_bit,

  1. arch/arm/lib/testsetbit.S
  2. ENTRY(_test_and_set_bit)
  3.         testop  orreq, streq
  4. ENDPROC(_test_and_set_bit)
复制代码
一些列的原子位操作都会调用通过汇编语言写成的testop来实现。它定义如下:

  1. arch/arm/lib/bitops.h
  2. #if __LINUX_ARM_ARCH__ >= 6
  3.         .macro  testop, instr, store
  4.         ands    ip, r1, #3
  5.         strneb  r1, [ip]                @ assert word-aligned
  6.         mov     r2, #1
  7.         and     r3, r0, #31             @ Get bit offset
  8.         mov     r0, r0, lsr #5
  9.         add     r1, r1, r0, lsl #2      @ Get word offset
  10.         mov     r3, r2, lsl r3          @ create mask
  11.         smp_dmb
  12. 1:      ldrex   r2, [r1]
  13.         ands    r0, r2, r3              @ save old value of bit
  14.         \instr  r2, r2, r3              @ toggle bit
  15.         strex   ip, r2, [r1]
  16.         cmp     ip, #0
  17.         bne     1b
  18.         smp_dmb
  19.         cmp     r0, #0
  20.         movne   r0, #1
  21. 2:      bx      lr
  22.         .endm
复制代码
这里只摘了ARMv6和以上指令集的实现,现在很清楚的可以看到ldrex 和 strex,另外还有硬件内存屏障smp_dmb的使用了。

论坛徽章:
0
9 [报告]
发表于 2011-12-31 20:42 |只看该作者
回复 8# lli_njupt

嗯哼~ 开始没注意到

OK,小小总结一下

以thread 0 先到为例:
atomic操作是主要是为了避免“交错读”的问题,
简单的说,避免

step 0:
thread 0: mov r0,[addr]
              .......
step 1:
thread 1: mov r1,[addr]
              .......
step 2:
thread 0: str r0,[addr]

也就是 类似 “读取----->写回“ 这种操作的这种问题
也就是在coder的想法中,要么thread 1应该要么读到 step 0之前的值,要么读到step 2以后的值

so,step 0 和 step 2中间必须有某种锁机制存在

single core上,关中断就好
而dual core上,就必须 ldrex ----> strex组合来保证thread 1会读到step 2以后的值
而由于write buffer的存在,必须有smb的存在来强制将这个写操作对thread 1可观察,不然thread 1就得多等会儿

而只是赋值操作的话,因为本身armv6,armv7对word aligned的地址的word写操作就是atomic的,所以不需要任何锁机制。


   

论坛徽章:
0
10 [报告]
发表于 2012-01-02 18:09 |只看该作者
回复 9# onlyxuyang
还有一事不明,想请教楼上,如atomic_add_return的实现:
static inline int atomic_add_return(int i, atomic_t *v)
{
        unsigned long tmp;
        int result;

        __asm__ __volatile__("@ atomic_add_return\n"
"1:     ldrex   %0, [%2]\n"
"       add     %0, %0, %3\n"
"       strex   %1, %0, [%2]\n"
"       teq     %1, #0\n"
"       bne     1b"
        : "=&r" (result), "=&r" (tmp)
        : "r" (&v->counter), "Ir" (i)
        : "cc");

        return result;
}
[/code]
那么在ldrex和strex之间会发生中断吗? 如果可以发生中断,
那么中断中是不是要禁止使用这些内核提供的原子操作宏的?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP