现在的位置: 首页 > 综合 > 正文

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

2013年06月12日 ⁄ 综合 ⁄ 共 7047字 ⁄ 字号 评论关闭

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

——题记

    内核源码: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的数据类型为unsigned long,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

抱歉!评论已关闭.