前言
内核版本:linux 3.10.0 硬件平台:x86_64
除了Linux应用层需要保护共享资源,内核也需要保护共享资源,以避免并发访问。防止多个任务之间相互覆盖共享资源,造成访问共享资源不一致的状态。
临界区:访问和操作共享数据,内核全局变量的代码段。 多个任务有可能在同一时刻访问同一临界区(竞争条件,race conditions),因此我们要避免多个任务在临界区并发访问。保证这些代码原子的执行,即:整个临界区的访问不可被打断,是一个不可分割的指令。
内核产生竞争条件的原因:
(1)多处理器:目前处理器都支持对称多处理器,这就导致运行在不同处理器的内核代码完全可能在用一时刻里并发的访问数据。
(2)内核抢占:Linux 2.6以后已经允许内核抢占,这意味着调度程序可以在任何时候抢占正在运行的内核代码,重新调度其它进程运行。这就导致进程与抢占它的进程访问共享资源。
(3)中断(包括软中断):中断几乎可以在任何时候异步发生,这也就意味着可能随时打断正在运行的进程,如果中断服务程序访问进程正在访问的资源,这样也会发生竞争。
一、原子操作
发生了上述竞争条件后该怎样解决?这篇文章我们主要介绍第一种内核同步的方法:原子操作
C语言无法保证原子操作。原子操作可以保证指令以原子的方式执行,即指令执行过程不会被打断。 原子行确保指令执行期间不被打断,要么指令全部执行完,要么指令根本不执行。
原子整数操作经常用于计数器(单个整数变量,一个简单的共享资源),使用锁来实现计数器过于复杂(Useful for resource counting)。 在编写代码时,能使用原子操作地情况下,尽量不要使用锁机制,系统开销更小,对高速缓存行(cache-line)地影响也小。
原子操作通常是内联函数,通常用内联汇编指令来实现。
备注:原子操作主要用于保护一个整数变量这种简单的临界资源。
Linux内核提供一系列函数来实现内核的原子操作,主要提供了两组原子操作接口:原子整数操作和原子位操作。 我主要介绍原子整数操作系列的函数。
二、atomic
2.1 atomic_t 类型定义
atomic_t 类型应定义为有符号整数。 此外,它应该是不透明的,这样任何类型的转换为正常的 C 整数类型都会失败。 比如:
atomic_t value = 0 ;
(int)value;
typedef struct {
int counter;
} atomic_t;
#ifdef CONFIG_64BIT
typedef struct {
long counter;
} atomic64_t;
#endif
atomic_t是不透明类型数据类型,隐藏了其内部格式或者结构。为了确保这些数据只在特殊的有关原子操作的函数中才会使用。不要将该类型转化为其对应的C标准类型,并且不要假设该类型的长度。
2.2 atomic 初始化
#define ATOMIC_INIT(i) { (i) }
atomic_t value = ATOMIC_INIT(0);
2.3 atomic read and set
static inline int atomic_read(const atomic_t *v)
{
return (*(volatile int *)&(v)->counter);
}
static inline void atomic_set(atomic_t *v, int i)
{
v->counter = i;
}
比如:
atomic_read(&v)
atomic_set(&v, 1)
备注:对于读原子变量,读取地时候加了volatile修饰,保证每次都是去从变量的内存地址中读取原子变量,而不是从缓存中读取变量,保证读操作不会被编译器优化,从而每次读取到正确的值。 volatile不能保证原子操作(不具有保护临界资源的作用),只是避免编译器优化而已。
2.4 atomic add and subtract
static inline void atomic_add(int i, atomic_t *v)
{
asm volatile(LOCK_PREFIX "addl %1,%0"
: "+m" (v->counter)
: "ir" (i));
}
static inline void atomic_sub(int i, atomic_t *v)
{
asm volatile(LOCK_PREFIX "subl %1,%0"
: "+m" (v->counter)
: "ir" (i));
}
比如:
atomic_add(2, &v);
atomic_sub(3, &v);
2.5 atomic_sub_and_test
static inline int atomic_sub_and_test(int i, atomic_t *v)
{
unsigned char c;
asm volatile(LOCK_PREFIX "subl %2,%0; sete %1"
: "+m" (v->counter), "=qm" (c)
: "ir" (i) : "memory");
return c;
}
比如: 将原子变量减2,如果等于0,返回真;否则返回假。
enum {
false = 0,
true = 1
};
if(atomic_sub_and_test(2, &v))
return true;
else
return false;
2.6 atomic increment and decrement
static inline void atomic_inc(atomic_t *v)
{
asm volatile(LOCK_PREFIX "incl %0"
: "+m" (v->counter));
}
static inline void atomic_dec(atomic_t *v)
{
asm volatile(LOCK_PREFIX "decl %0"
: "+m" (v->counter));
}
比如:
atomic_inc(&v)
atomic_dec(&v)
2.7 atomic inc or dec and test
static inline int atomic_dec_and_test(atomic_t *v)
{
unsigned char c;
asm volatile(LOCK_PREFIX "decl %0; sete %1"
: "+m" (v->counter), "=qm" (c)
: : "memory");
return c != 0;
}
static inline int atomic_inc_and_test(atomic_t *v)
{
unsigned char c;
asm volatile(LOCK_PREFIX "incl %0; sete %1"
: "+m" (v->counter), "=qm" (c)
: : "memory");
return c != 0;
}
if(atomic_dec_and_test(&v))
return true;
else
return false;
if( atomic_inc_and_test(&v))
return true;
else
return false;
2.8 atomic add or sub and return
static inline int atomic_add_return(int i, atomic_t *v)
{
return i + xadd(&v->counter, i);
}
static inline int atomic_sub_return(int i, atomic_t *v)
{
return atomic_add_return(-i, v);
}
#define atomic_inc_return(v) (atomic_add_return(1, v))
#define atomic_dec_return(v) (atomic_sub_return(1, v))
三、api演示
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <asm/atomic.h>
static int __init lkm_init(void)
{
atomic_t v = ATOMIC_INIT(2);
printk("v = %d\n", atomic_read(&v));
if(atomic_sub_and_test(2, &v))
printk("true\n");
else
printk("false\n");
printk("v = %d\n", atomic_read(&v));
atomic_set(&v, 3);
printk("v = %d\n", atomic_read(&v));
atomic_inc(&v);
printk("v = %d\n", atomic_read(&v));
atomic_dec(&v);
printk("v = %d\n", atomic_read(&v));
atomic_add(2, &v);
printk("v = %d\n", atomic_read(&v));
atomic_sub(3, &v);
printk("v = %d\n", atomic_read(&v));
atomic_set(&v, 1);
printk("v = %d\n", atomic_read(&v));
if(atomic_dec_and_test(&v))
printk("true\n");
else
printk("false\n");
printk("v = %d\n", atomic_read(&v));
atomic_set(&v, -1);
printk("v = %d\n", atomic_read(&v));
if( atomic_inc_and_test(&v))
printk("true\n");
else
printk("false\n");
printk("v = %d\n", atomic_read(&v));
atomic_set(&v, 2);
printk("v = %d\n", atomic_read(&v));
printk("ret = %d\n", atomic_add_return(3, &v));
printk("v = %d\n", atomic_read(&v));
printk("ret = %d\n", atomic_sub_return(2, &v));
printk("v = %d\n", atomic_read(&v));
printk("v = %d\n", atomic_inc_return(&v));
printk("v = %d\n", atomic_dec_return(&v));
return -1;
}
module_init(lkm_init);
MODULE_LICENSE("GPL");
Makefile
obj-m:=atomic.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
查看结果:
总结
该文章主要介绍原子整数操作一系列API的使用。
参考资料
Linux内核源码 3.10.0 Linux内核设计与实现
|