原子操作
原子操作可以保证指令以原子的方式被执行,执行过程不会被打断。Linux内核提供了一个专门的atomic_t类型(一个原子访问计数器),其定义如下:
typedef struct{
int counter;
}atomic_t;
atomic_t类型定义在
/include/linux/types.h
中, 这样定义的原因是可以让GCC在编译的时候加以更加严格的类型检查,防止原子类型变量被误操作。随着64位体系结构的普及,64位原子操作被定义为atomic64_t。typedef struct{ long counter; }atomic64_t;
对于atomic_t类型的变量,linux内核提供了一些专门的函数和宏来进行原子操作,如下表:
原子整数操作 | 说明 |
---|---|
ATOMIC_INIT(int i) | 声明atomic_t变量,并初始化为i |
atomic_read(v) | 原子操作读取整数变量v,返回*v |
atomic_set(v,i) | 原子操作把*v置成i |
atomic_add(i,v) | 原子操作*v加i |
atomic_add_return(i,v) | 原子操作v加i,返回v |
atomic_add_negative(i,v) | 原子操作*v加i,为负返1,否则返0 |
atomic_sub(i,v) | 原子操作*v减i |
atomic_sub_return(i,v) | 原子操作v减i,返回v |
atomic_sub_and_test(i,v) | 原子操作*v减i,为0返1,否则返0 |
atomic_inc(v) | 原子操作*v自加1 |
atomic_inc_and_test(v) | 原子操作*v自加1,为0返1,为1返0 |
atomic_dec(v) | 原子操作*v自减1 |
atomic_dec_and_test(v) | 原子操作*v自减1,为0返1,为1返0 |
原子操作用法举例
\\定义原子变量
atomic_t u;//定义原子变量u
atomic_t v= ATIMIC_INIT(0);//定义原子变量v,并初始化为0
\\操作原子变量
atomic_set(&v,10);\\原子操作v=10
atomic_add(5,&v);\\原子操作v=v+5=15
atomic_dec(2,&v);\\原子操作v=v-2=13
atomic_sub_and_text(13,&v);\\原子操作v=v-13=0,并返回1
printk("v=%d\n",atomic_read(&v));\\会打印出v的值,即打印v=0
在内核实现计数器时,用锁机制来保护显得有些笨拙,而轻量级地保护内核计数器就可以使用原子操作。
Linux内核除了原子整数操作外,也提供了一组针对位原子操作的函数,如下表所示,它被定义在/include/asm-generic/bitops/atomic.h
中,它常被应用于内存管理、设备驱动。
原子位操作 | 说明 |
---|---|
set_bit(nr,addr) | 原子操作设置地址为addr的第nr位 |
clear_bit(nr,addr) | 原子操作清除地址为addr的第nr位 |
change_bit(nr,addr) | 原子操作,取反地址为addr的第nr位 |
test_and_set_bit(nr,addr) | 原子操作设置地址为addr的第nr位,并返回原先的值 |
test_and_clean_bit(nr,addr) | 原子操作清除地址为addr的第nr位,并返回原先的值 |
test_and_clean_bit(nr,addr) | 原子操作取反地址为addr的第nr位,并返回原先的值 |
test_bit(nr,addr) | 原子操作,返回地址为addr的第nr位 |
find_first_zero_bit(addr,size) | addr为内存的起始地址,size为要查找的最大长度,返回第一个为0的位号 |
find_next_zero_bit(addr,size,offset) | addr为内存的起始地址,size为要查找的最大长度,offset |
为开始搜索的起始位号,返回第一个为0的位号 |
注:
clear_bit(nr,addr)
是原子操作,但不具备加锁功能,若要用于加锁目的,应当调用smp_mb__before_clear_bit 或smp_mb__after_clear_bit函数,以确保任何改变在其他的处理器上是可见的。test_bit(nr,addr)
定义如下#define test_bit(nr, addr) \ (__builtin_constant_p((nr)) \ ? constant_test_bit((nr), (addr)) \ : variable_test_bit((nr), (addr)))
根据nr是否为编译时常数来调用不同的函数
- 若编译时为常数,则调用constant_test_bit
static __always_inline int constant_test_bit(unsigned int nr, const volatile unsigned long *addr)
- 若编译时为非常数,则调用variable_test_bit
static inline int variable_test_bit(int nr, volatile const unsigned long *addr)