Linux內核同步原子操做

避免對同一數據的併發訪問(一般由中斷、對稱多處理器、內核搶佔等引發)稱爲同步。linux

——題記併發

內核源碼:linux-2.6.38.8.tar.bz2less

目標平臺:ARM體系結構函數


原子操做確保對同一數據的「讀取-修改-寫入」操做在它的執行期間不會被打斷,要麼所有執行完成,要麼根本不會執行。例如在ARM上對全局變量的++運算至少要經歷如下三步:測試


ldr r3, [r3, #0]ui

add r2, r3, #1atom

str r2, [r3, #0]rest

這就給併發訪問製造了可能,因此在編寫內核代碼時須要給有可能被併發訪問的變量加上原子操做。接口

Linux內核提供了兩組原子操做接口,一組用於整數,一組用於位操做。ip

一、原子整數操做

針對整數的原子操做只能對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類型數據的地址。

二、原子位操做

在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

相關文章
相關標籤/搜索