文档章节

Linux内核同步原子操作

有些服务器
 有些服务器
发布于 2015/10/04 12:36
字数 1753
阅读 84
收藏 6

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

——题记

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

目标平台:ARM体系结构


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


ldr r3, [r3, #0]

add r2, r3, #1

str r2, [r3, #0]

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

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

1、原子整数操作

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


/* linux-2.6.38.8/include/linux/types.h */

typedef struct {

int counter;

} atomic_t;

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


#define ATOMIC_INIT(i)  { (i) }

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


atomic_t a = ATOMIC_INIT(2);



#define atomic_read(v)  (*(volatile int *)&(v)->counter)

#define atomic_set(v,i) (((v)->counter) = (i))

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


static inline int atomic_add_return(int i, atomic_t *v)

{

unsigned long flags;

int val;


raw_local_irq_save(flags);

val = v->counter;

v->counter = val += i;

raw_local_irq_restore(flags);


return val;

}

#define atomic_add(i, v)    (void) atomic_add_return(i, v)

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


/* linux-2.6.38.8/include/linux/irqflags.h */

#define raw_local_irq_save(flags)           \

do {                        \

typecheck(unsigned long, flags);    \

flags = arch_local_irq_save();      \

} while (0)

/* linux-2.6.38.8/arch/arm/include/asm/irqflags.h */

static inline unsigned long arch_local_irq_save(void)

{

unsigned long flags, temp;


asm volatile(

"   mrs %0, cpsr    @ arch_local_irq_save\n"

"   orr %1, %0, #128\n"

"   msr cpsr_c, %1"

: "=r" (flags), "=r" (temp)

:

: "memory", "cc");

return flags;

}

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


static inline int atomic_sub_return(int i, atomic_t *v)

{

unsigned long flags;

int val;


raw_local_irq_save(flags);

val = v->counter;

v->counter = val -= i;

raw_local_irq_restore(flags);


return val;

}

#define atomic_sub(i, v)    (void) atomic_sub_return(i, v)

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


#define atomic_inc(v)       atomic_add(1, v)

#define atomic_dec(v)       atomic_sub(1, v)


#define atomic_inc_and_test(v)  (atomic_add_return(1, v) == 0)

#define atomic_dec_and_test(v)  (atomic_sub_return(1, v) == 0)

#define atomic_inc_return(v)    (atomic_add_return(1, v))

#define atomic_dec_return(v)    (atomic_sub_return(1, v))

#define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)


#define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)

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


static inline int atomic_cmpxchg(atomic_t *v, int old, int new)

{

int ret;

unsigned long flags;


raw_local_irq_save(flags);

ret = v->counter;

if (likely(ret == old))

v->counter = new;

raw_local_irq_restore(flags);


return ret;

}

static inline int atomic_add_unless(atomic_t *v, int a, int u)

{

int c, old;


c = atomic_read(v);

while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)

c = old;

return c != u;

}

#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__都未定义的情况下,原子位操作相关函数的定义如下:


/* linux-2.6.38.8/arch/arm/include/asm/bitops.h */

#define ATOMIC_BITOP_LE(name,nr,p)      \

(__builtin_constant_p(nr) ?     \

____atomic_##name(nr, p) :     \

_##name##_le(nr,p))


#define set_bit(nr,p)           ATOMIC_BITOP_LE(set_bit,nr,p)

#define clear_bit(nr,p)         ATOMIC_BITOP_LE(clear_bit,nr,p)

#define change_bit(nr,p)        ATOMIC_BITOP_LE(change_bit,nr,p)

#define test_and_set_bit(nr,p)      ATOMIC_BITOP_LE(test_and_set_bit,nr,p)

#define test_and_clear_bit(nr,p)    ATOMIC_BITOP_LE(test_and_clear_bit,nr,p)

#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为常量时


/* linux-2.6.38.8/arch/arm/include/asm/bitops.h */

static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)

{

unsigned long flags;

unsigned long mask = 1UL << (bit & 31);


p += bit >> 5;


raw_local_irq_save(flags);

*p |= mask;

raw_local_irq_restore(flags);

}

static inline void ____atomic_clear_bit(unsigned int bit, volatile unsigned long *p)

{

unsigned long flags;

unsigned long mask = 1UL << (bit & 31);


p += bit >> 5;


raw_local_irq_save(flags);

*p &= ~mask;

raw_local_irq_restore(flags);

}

static inline void ____atomic_change_bit(unsigned int bit, volatile unsigned long *p)

{

unsigned long flags;

unsigned long mask = 1UL << (bit & 31);


p += bit >> 5;


raw_local_irq_save(flags);

*p ^= mask;

raw_local_irq_restore(flags);

}

在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时则被清零。


static inline int

____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)

{

unsigned long flags;

unsigned int res;

unsigned long mask = 1UL << (bit & 31);


p += bit >> 5;


raw_local_irq_save(flags);

res = *p;

*p = res | mask;

raw_local_irq_restore(flags);


return (res & mask) != 0;

}

static inline int

____atomic_test_and_clear_bit(unsigned int bit, volatile unsigned long *p)

{

unsigned long flags;

unsigned int res;

unsigned long mask = 1UL << (bit & 31);


p += bit >> 5;


raw_local_irq_save(flags);

res = *p;

*p = res & ~mask;

raw_local_irq_restore(flags);


return (res & mask) != 0;

}

static inline int

____atomic_test_and_change_bit(unsigned int bit, volatile unsigned long *p)

{

unsigned long flags;

unsigned int res;

unsigned long mask = 1UL << (bit & 31);


p += bit >> 5;


raw_local_irq_save(flags);

res = *p;

*p = res ^ mask;

raw_local_irq_restore(flags);


return (res & mask) != 0;

}

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

(2)、nr不为常量时

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


/* linux-2.6.38.8/arch/arm/include/asm/bitops.h */

extern void _set_bit_le(int nr, volatile unsigned long * p);

extern void _clear_bit_le(int nr, volatile unsigned long * p);

extern void _change_bit_le(int nr, volatile unsigned long * p);

extern int _test_and_set_bit_le(int nr, volatile unsigned long * p);

extern int _test_and_clear_bit_le(int nr, volatile unsigned long * p);

extern int _test_and_change_bit_le(int nr, volatile unsigned long * p);

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


/* linux-2.6.38.8/arch/arm/lib/setbit.S */

ENTRY(_set_bit_le)

bitop   orr

ENDPROC(_set_bit_le)


/* linux-2.6.38.8/arch/arm/lib/clearbit.S */

ENTRY(_clear_bit_le)

bitop   bic

ENDPROC(_clear_bit_le)


/* linux-2.6.38.8/arch/arm/lib/changebit.S */

ENTRY(_change_bit_le)

bitop   eor

ENDPROC(_change_bit_le)


/* linux-2.6.38.8/arch/arm/lib/testsetbit.S */

ENTRY(_test_and_set_bit_le)

testop  orreq, streqb

ENDPROC(_test_and_set_bit_le)


/* linux-2.6.38.8/arch/arm/lib/testclearbit.S */

ENTRY(_test_and_clear_bit_le)

testop  bicne, strneb

ENDPROC(_test_and_clear_bit_le)


/* linux-2.6.38.8/arch/arm/lib/testchangebit.S */

ENTRY(_test_and_change_bit_le)

testop  eor, strb

ENDPROC(_test_and_change_bit_le)

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


/* linux-2.6.38.8/include/linux/linkage.h */

#define ENTRY(name) \

.globl name; \

ALIGN; \

name:


#define ALIGN __ALIGN


#define END(name) \

.size name, .-name


/* linux-2.6.38.8/arch/arm/include/asm/linkage.h */

#define __ALIGN .align 0


#define ENDPROC(name) \

.type name, %function; \

END(name)

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


/* linux-2.6.38.8/arch/arm/lib/bitops.h */

.macro  bitop, instr

and r2, r0, #7

mov r3, #1

mov r3, r3, lsl r2

save_and_disable_irqs ip

ldrb    r2, [r1, r0, lsr #3]

\instr  r2, r2, r3

strb    r2, [r1, r0, lsr #3]

restore_irqs ip

mov pc, lr

.endm


.macro  testop, instr, store

add r1, r1, r0, lsr #3

and r3, r0, #7

mov r0, #1

save_and_disable_irqs ip

ldrb    r2, [r1]

tst r2, r0, lsl r3

\instr  r2, r2, r0, lsl r3

\store  r2, [r1]

moveq   r0, #0

restore_irqs ip

mov pc, lr

.endm


© 著作权归作者所有

有些服务器
粉丝 4
博文 49
码字总数 86607
作品 0
南昌
私信 提问
内核同步机制

2.3 操作系统中,同时可能有多个进程(也包括内核进程)在执行,因此在内核中也需要考虑进程的同步和互斥机制,用来同步或互斥各进程对共享数据的访问.由于多个处理器系统是计算机系统的一个重要发...

xwisen
2014/07/07
0
0
漫画 | Linux 并发和竞态问题究竟是什么?

作者 | 写代码的篮球球痴 责编 | 郭芮 学习Linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可以华山论剑了,又突然冒出一个新的知识点,我看到新知...

CSDN资讯
02/25
0
0
linux内核原子操作

1、基本概念 原子操作可以保证指令以原子的方式执行,执行过程不被打断。它通过把读取和修改变量的行为包含在一个单步中执行,从而防止了竞争的发生,保证操作结果总是一致的。 例如: int ...

So_care_about_y
2017/08/08
0
0
嵌入式Linux学习基础规划篇

嵌入式的学习是需要日积月累的,是通过一点一滴的积累才能成为大神。下面来介绍一下嵌入式linux学习基础规划,目标是达到适应嵌入式应用软件开发、嵌入式系统开发或嵌入式驱动开发的基本素质...

创客学院
2018/04/10
0
0
linux设备驱动系列:如何处理竞态关系

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时、并行...

东辉在线
2015/04/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux 性能分析利器 -火焰图 flame graph

简述 Perf 命令(performance的简写)是 Linux 系统原生提供的性能分析工具,返回 CPU 正在执行的函数名以及调用栈(stack)。 通常,它的执行频率是 99Hz(每秒99次),如果99次都返回同一个函数...

呼呼南风
20分钟前
4
0
 好程序员大数据知识点精讲 大数据之Linux

好程序员大数据知识点精讲 大数据之Linux -Linux是什么? Linux是一套作业系统,不是应用程序Linux的基本思想有两点:第一,一切都是文件;第二,每个软件都有确定的用途。 Shell——命令行解...

好程序员IT
24分钟前
1
0
mysql 多行结合

select a1.email as email ,a1.bg ,IFNULL(a1.bg, a2.bg) from ( select * from test01 where sdate = '2019-09-11' ) a1 LEFT join (select * from test01 where sdate = '2019-09-10') a2 ......

昏鸦
26分钟前
2
0
Netflix Eureka 续约 & 更新注册表信息

Eureka Client 要定期的向 Eureka Server 发送心跳请求以保持续约的状态。 也需要定期的从 Eureka Server 获取服务注册表数据,并将服务注册表数据缓存在客户端实例内。 Eureka Client 续约 ...

BryceLoski
29分钟前
18
0
IT兄弟连 Java语法教程 Java开发环境 JVM、JRE、JDK

要想开发Java程序,就需要知道什么是JVM、JRE以及JDK。JVM是运行Java程序的核心,JRE是支持Java程序运行的环境,而JDK是Java开发的核心,下面我们分别具体介绍它们以及它们之间的关系。 1.J...

老码农的一亩三分地
38分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部