转自:http://blog.csdn.net/npy_lp/article/details/7262388

避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。

——题记

内核源码:linux-2.6.38.8.tar.bz2

目标平台:ARM体系结构

原子操作确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,要么全部执行完成,要么根本不会执行。例如在ARM上对全局变量的++运算至少要经历以下三步:

  1. ldr r3, [r3, #0]
  2. add r2, r3, #1
  3. str r2, [r3, #0]

这就给并发访问制造了可能,所以在编写内核代码时需要给有可能被并发访问的变量加上原子操作。

Linux内核提供了两组原子操作接口,一组用于整数,一组用于位操作。

1、原子整数操作

针对整数的原子操作只能对atomic_t类型的数据进行处理。atomic_t定义如下:

  1. /* linux-2.6.38.8/include/linux/types.h */
  2. typedef struct {
  3. int counter;
  4. } atomic_t;

下面将详细分析所有原子整数操作函数在ARMv6之前各种CPU上的实现(定义在linux-2.6.38.8/arch/arm/include/asm/atomic.h文件中)。

  1. #define ATOMIC_INIT(i)  { (i) }

ATOMIC_INIT用于把atomic_t变量初始化为i,如下例把a初始化为2:

  1. atomic_t a = ATOMIC_INIT(2);
  1. #define atomic_read(v)  (*(volatile int *)&(v)->counter)
  2. #define atomic_set(v,i) (((v)->counter) = (i))

atomic_read(v)用于读取atomic_t变量*v的值,而atomic_set(v,i)用于把atomic_t变量*v设置为i。

  1. static inline int atomic_add_return(int i, atomic_t *v)
  2. {
  3. unsigned long flags;
  4. int val;
  5. raw_local_irq_save(flags);
  6. val = v->counter;
  7. v->counter = val += i;
  8. raw_local_irq_restore(flags);
  9. return val;
  10. }
  11. #define atomic_add(i, v)    (void) atomic_add_return(i, v)

atomic_add(i, v)用于把atomic_t变量*v加i。这里的原子性实现只是简单地通过raw_local_irq_save函数来禁止中断。

  1. /* linux-2.6.38.8/include/linux/irqflags.h */
  2. #define raw_local_irq_save(flags)           \
  3. do {                        \
  4. typecheck(unsigned long, flags);    \
  5. flags = arch_local_irq_save();      \
  6. } while (0)
  7. /* linux-2.6.38.8/arch/arm/include/asm/irqflags.h */
  8. static inline unsigned long arch_local_irq_save(void)
  9. {
  10. unsigned long flags, temp;
  11. asm volatile(
  12. "   mrs %0, cpsr    @ arch_local_irq_save\n"
  13. "   orr %1, %0, #128\n"
  14. "   msr cpsr_c, %1"
  15. : "=r" (flags), "=r" (temp)
  16. :
  17. : "memory", "cc");
  18. return flags;
  19. }

typecheck函数用来确保参数flags的数据类型为unsigned long,arch_local_irq_save函数通过内嵌汇编设置当前程序状态寄存器的I位为1,从而禁止IRQ。待加操作完成后,再通过raw_local_irq_restore函数使能中断。

  1. static inline int atomic_sub_return(int i, atomic_t *v)
  2. {
  3. unsigned long flags;
  4. int val;
  5. raw_local_irq_save(flags);
  6. val = v->counter;
  7. v->counter = val -= i;
  8. raw_local_irq_restore(flags);
  9. return val;
  10. }
  11. #define atomic_sub(i, v)    (void) atomic_sub_return(i, v)

atomic_sub(i, v)用于把atomic_t变量*v减i。

  1. #define atomic_inc(v)       atomic_add(1, v)
  2. #define atomic_dec(v)       atomic_sub(1, v)
  3. #define atomic_inc_and_test(v)  (atomic_add_return(1, v) == 0)
  4. #define atomic_dec_and_test(v)  (atomic_sub_return(1, v) == 0)
  5. #define atomic_inc_return(v)    (atomic_add_return(1, v))
  6. #define atomic_dec_return(v)    (atomic_sub_return(1, v))
  7. #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)
  8. #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)

这些函数只是上面所讲函数的封装。

  1. static inline int atomic_cmpxchg(atomic_t *v, int old, int new)
  2. {
  3. int ret;
  4. unsigned long flags;
  5. raw_local_irq_save(flags);
  6. ret = v->counter;
  7. if (likely(ret == old))
  8. v->counter = new;
  9. raw_local_irq_restore(flags);
  10. return ret;
  11. }
  12. static inline int atomic_add_unless(atomic_t *v, int a, int u)
  13. {
  14. int c, old;
  15. c = atomic_read(v);
  16. while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)
  17. c = old;
  18. return c != u;
  19. }
  20. #define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)

atomic_inc_not_zero(v)用于将atomic_t变量*v加1,并测试加1后的*v是否不为零,如果不为零则返回真。

注意,这里所讲函数的参数v都是atomic_t类型数据的地址。

2、原子位操作

在CONFIG_SMP和__ARMEB__都未定义的情况下,原子位操作相关函数的定义如下:

  1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */
  2. #define ATOMIC_BITOP_LE(name,nr,p)      \
  3. (__builtin_constant_p(nr) ?     \
  4. ____atomic_##name(nr, p) :     \
  5. _##name##_le(nr,p))
  6. #define set_bit(nr,p)           ATOMIC_BITOP_LE(set_bit,nr,p)
  7. #define clear_bit(nr,p)         ATOMIC_BITOP_LE(clear_bit,nr,p)
  8. #define change_bit(nr,p)        ATOMIC_BITOP_LE(change_bit,nr,p)
  9. #define test_and_set_bit(nr,p)      ATOMIC_BITOP_LE(test_and_set_bit,nr,p)
  10. #define test_and_clear_bit(nr,p)    ATOMIC_BITOP_LE(test_and_clear_bit,nr,p)
  11. #define test_and_change_bit(nr,p)   ATOMIC_BITOP_LE(test_and_change_bit,nr,p)

相对应的非原子位操作函数定义在linux-2.6.38.8/include/asm-generic/bitops/non-atomic.h文件中,它们的名字前都多带有两个下划线,如set_bit的非原子操作函数为__set_bit。

原子位操作函数根据参数nr是否为常量分为两种实现方式:

(1)、nr为常量时

  1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */
  2. static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
  3. {
  4. unsigned long flags;
  5. unsigned long mask = 1UL << (bit & 31);
  6. p += bit >> 5;
  7. raw_local_irq_save(flags);
  8. *p |= mask;
  9. raw_local_irq_restore(flags);
  10. }
  11. static inline void ____atomic_clear_bit(unsigned int bit, volatile unsigned long *p)
  12. {
  13. unsigned long flags;
  14. unsigned long mask = 1UL << (bit & 31);
  15. p += bit >> 5;
  16. raw_local_irq_save(flags);
  17. *p &= ~mask;
  18. raw_local_irq_restore(flags);
  19. }
  20. static inline void ____atomic_change_bit(unsigned int bit, volatile unsigned long *p)
  21. {
  22. unsigned long flags;
  23. unsigned long mask = 1UL << (bit & 31);
  24. p += bit >> 5;
  25. raw_local_irq_save(flags);
  26. *p ^= mask;
  27. raw_local_irq_restore(flags);
  28. }

在32位机上,参数bit的理想取值为0到31,例如取值为1,就表示对*p的bit[1]进行操作。

当bit取值大于31时,函数操作的就不是你想要操作的那个变量*p了(通过p += bit>> 5;语句实现),所以在实际的应用中应该要确保bit的取值在合理的范围内。

*p |= mask;语句实现bit位的置位。

*p &= ~mask;语句实现bit位的清零。

*p ^= mask;语句实现bit位的翻转,即*p的bit位为0时被置位,为1时则被清零。

  1. static inline int
  2. ____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)
  3. {
  4. unsigned long flags;
  5. unsigned int res;
  6. unsigned long mask = 1UL << (bit & 31);
  7. p += bit >> 5;
  8. raw_local_irq_save(flags);
  9. res = *p;
  10. *p = res | mask;
  11. raw_local_irq_restore(flags);
  12. return (res & mask) != 0;
  13. }
  14. static inline int
  15. ____atomic_test_and_clear_bit(unsigned int bit, volatile unsigned long *p)
  16. {
  17. unsigned long flags;
  18. unsigned int res;
  19. unsigned long mask = 1UL << (bit & 31);
  20. p += bit >> 5;
  21. raw_local_irq_save(flags);
  22. res = *p;
  23. *p = res & ~mask;
  24. raw_local_irq_restore(flags);
  25. return (res & mask) != 0;
  26. }
  27. static inline int
  28. ____atomic_test_and_change_bit(unsigned int bit, volatile unsigned long *p)
  29. {
  30. unsigned long flags;
  31. unsigned int res;
  32. unsigned long mask = 1UL << (bit & 31);
  33. p += bit >> 5;
  34. raw_local_irq_save(flags);
  35. res = *p;
  36. *p = res ^ mask;
  37. raw_local_irq_restore(flags);
  38. return (res & mask) != 0;
  39. }

这三个函数增加了return (res & mask) != 0;语句,用来判断*p的bit位原值是否为1,如果为1则函数返回1,否则返回0。

(2)、nr不为常量时

当nr不为常量时,原子位操作函数的定义如下:

  1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */
  2. extern void _set_bit_le(int nr, volatile unsigned long * p);
  3. extern void _clear_bit_le(int nr, volatile unsigned long * p);
  4. extern void _change_bit_le(int nr, volatile unsigned long * p);
  5. extern int _test_and_set_bit_le(int nr, volatile unsigned long * p);
  6. extern int _test_and_clear_bit_le(int nr, volatile unsigned long * p);
  7. extern int _test_and_change_bit_le(int nr, volatile unsigned long * p);

它们都是通过汇编语言来实现的,定义如下:

  1. /* linux-2.6.38.8/arch/arm/lib/setbit.S */
  2. ENTRY(_set_bit_le)
  3. bitop   orr
  4. ENDPROC(_set_bit_le)
  5. /* linux-2.6.38.8/arch/arm/lib/clearbit.S */
  6. ENTRY(_clear_bit_le)
  7. bitop   bic
  8. ENDPROC(_clear_bit_le)
  9. /* linux-2.6.38.8/arch/arm/lib/changebit.S */
  10. ENTRY(_change_bit_le)
  11. bitop   eor
  12. ENDPROC(_change_bit_le)
  13. /* linux-2.6.38.8/arch/arm/lib/testsetbit.S */
  14. ENTRY(_test_and_set_bit_le)
  15. testop  orreq, streqb
  16. ENDPROC(_test_and_set_bit_le)
  17. /* linux-2.6.38.8/arch/arm/lib/testclearbit.S */
  18. ENTRY(_test_and_clear_bit_le)
  19. testop  bicne, strneb
  20. ENDPROC(_test_and_clear_bit_le)
  21. /* linux-2.6.38.8/arch/arm/lib/testchangebit.S */
  22. ENTRY(_test_and_change_bit_le)
  23. testop  eor, strb
  24. ENDPROC(_test_and_change_bit_le)

使用ENTRY和ENDPROC两个宏来定义一个名为name的函数,如下所示:

  1. /* linux-2.6.38.8/include/linux/linkage.h */
  2. #define ENTRY(name) \
  3. .globl name; \
  4. ALIGN; \
  5. name:
  6. #define ALIGN __ALIGN
  7. #define END(name) \
  8. .size name, .-name
  9. /* linux-2.6.38.8/arch/arm/include/asm/linkage.h */
  10. #define __ALIGN .align 0
  11. #define ENDPROC(name) \
  12. .type name, %function; \
  13. END(name)

而汇编代码实现的宏bitop和testop被相应函数所调用,并传递给它们相应的参数,代码如下所示:

  1. /* linux-2.6.38.8/arch/arm/lib/bitops.h */
  2. .macro  bitop, instr
  3. and r2, r0, #7
  4. mov r3, #1
  5. mov r3, r3, lsl r2
  6. save_and_disable_irqs ip
  7. ldrb    r2, [r1, r0, lsr #3]
  8. \instr  r2, r2, r3
  9. strb    r2, [r1, r0, lsr #3]
  10. restore_irqs ip
  11. mov pc, lr
  12. .endm
  13. .macro  testop, instr, store
  14. add r1, r1, r0, lsr #3
  15. and r3, r0, #7
  16. mov r0, #1
  17. save_and_disable_irqs ip
  18. ldrb    r2, [r1]
  19. tst r2, r0, lsl r3
  20. \instr  r2, r2, r0, lsl r3
  21. \store  r2, [r1]
  22. moveq   r0, #0
  23. restore_irqs ip
  24. mov pc, lr
  25. .endm

Linux内核同步原语之原子操作【转】的更多相关文章

  1. Linux内核同步原语之原子操作

    避免对同一数据的并发访问(通常由中断.对称多处理器.内核抢占等引起)称为同步. ——题记 内核源码:Linux-2.6.38.8.tar.bz2 目标平台:ARM体系结构 原子操作确保对同一数据的“读 ...

  2. Linux内核同步机制之原子操作

    1.前言 原子操作指的是该操作不会在执行完毕之前被任何其它任务或事件打断,它是最小的执行单位,不会有比它更小的执行单位,原子实际上使用了物理学中物质微粒的概念,在Linux内核中,原子操作需要硬件的支 ...

  3. Linux内核同步机制--转发自蜗窝科技

    Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...

  4. Linux内核同步

    Linux内核剖析 之 内核同步 主要内容 1.内核请求何时以交错(interleave)的方式执行以及交错程度如何. 2.内核所实现的基本同步机制. 3.通常情况下如何使用内核提供的同步机制. 内核 ...

  5. Linux内核同步 - spin_lock

    一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机 ...

  6. Linux内核同步机制之(四):spin lock【转】

    转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...

  7. [内核同步]浅析Linux内核同步机制

    转自:http://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral ...

  8. Linux内核同步机制

    http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...

  9. 浅析Linux内核同步机制

    非常早之前就接触过同步这个概念了,可是一直都非常模糊.没有深入地学习了解过,最近有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这 ...

随机推荐

  1. hbase windows安装

    下载目前最新版本 http://mirrors.hust.edu.cn/apache/hbase/stable/ 最新版本 hbase-1.2.6 1. 解压到D:\software\hbase-1. ...

  2. bzoj3622-已经没有什么好害怕的的了

    题意 给出两个长度为 \(n\) 的数列 \(a,b\) ,\(2n\) 个数都互不相同,求有多少种对应方式使得 \(a_i>b_i\) 的个数比 \(a_i<b_i\) 的个数恰好多 \ ...

  3. HDU4822-Tri-War

    题目 给出一颗树,\(m\)次询问树上不相同的三个点\(A,B,C\).我们称一个点\(x\)被\(A\)占领当且仅当\(dist(A,x)>dist(B,x),dist(A,x)>dis ...

  4. Java内存区域介绍

    Java虚拟机把内存划分成几个区域,每个区域都有各自的职责.下面将逐一分析每个区域. 有助于我们了解,每个方法,变量,对象等都去哪儿了! 程序计数器: 它占用一块很小的内存空间,可以看作是当前线程所执 ...

  5. Ubuntu 18.04开发环境部署流程

    部署流程 安装系统 安装Eclipse和jre 配置系统 安装辅助工具 安装系统 用安装盘安装即可. 一般boot 1G,swap按内存大小,home 20G,根剩余. 安装Eclipse和jre 解 ...

  6. static变量的特点 - 只会有一份成员对象

    1.   public class HasStatic{ 2.     private static int x=100; 3.     public static void main(String ...

  7. BZOJ3295:[CQOI2011]动态逆序对——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=3295 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数 ...

  8. Codeforces Round #405 (rated, Div. 2, based on VK Cup 2017 Round 1)

    A 模拟 B 发现对于每个连通块,只有为完全图才成立,然后就dfs C 构造 想了20分钟才会,一开始想偏了,以为要利用相邻NO YES的关系再枚举,其实不难.. 考虑对于顺序枚举每一个NO/YES, ...

  9. python之选择排序

    选择排序:比如在一个长度为N的无序数组中,在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换......第N-1趟遍历剩下的2个 ...

  10. POJ1275 Cashier Employment 【二分 + 差分约束】

    题目链接 POJ1275 题解 显然可以差分约束 我们记\(W[i]\)为\(i\)时刻可以开始工作的人数 令\(s[i]\)为前\(i\)个时刻开始工作的人数的前缀和 每个时刻的要求\(r[i]\) ...