Linux内核的同步机制:原子操作
原子操作:UP和SMP的异同
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都 可以认为是"原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令 用于临界资源互斥的原因。但是,在对称多处理器(Symetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以 decl(递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。设想在不同CPU运行的两个进程都在递减某个计数值,可能发生的情况 是:
1. CPU A(上的进程,以下同)从内存单元把当前计数值(2)装载进它的寄存器中;
2. CPU B从内存单元把当前计数值(2)装载进它的寄存器中。
3. CPU A在它的寄存器中将计数值递减为1;
4. CPU B在它的寄存器中将计数值递减为1;
5. CPU A把修改后的计数值(1)写回内存单元。
6. CPU B把修改后的计数值(1)写回内存单元。
我 们看到,内存里的计数值应该是0,然而它却是1。如果该计数值是一个共享资源的引用计数,每个进程都在递减后把该值与0进行比较,从而确定是否需要释放该 共享资源。这时,两个进程都去掉了对该共享资源的引用,但没有一个进程能够释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用。
原子性不可能由软件单独保证--必须需要硬件的支持,因此是和架构相关的。在x86平台上,CPU提供了在指令执行期间对总线加锁的手段。 CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中 的原子性。
Linux内核中的原子操作
Linux 2.4.21中,原子类型的定义和原子操作API都放在内核源码树的include/asm/atomic.h文件中,大部分使用汇编语言实现,因为c语言并不能实现这样的操作。
在x86的原子操作实现代码中,定义了LOCK宏,这个宏可以放在随后的内联汇编指令之前。如果是SMP,LOCK宏被扩展为lock指令;否则被定义为空--单CPU无需防止其它CPU的干扰,锁内存总线完全是在浪费时间。
#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
typedef struct { volatile int counter; } atomic_t;
在所有支持的体系结构上原子类型atomic_t都保存一个int值。在x86的某些处理器上,由于工作方式的原因,原子类型能够保证的可用范围只有24位。volatile是一个类型描述符,要求编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。
#define ATOMIC_INIT(i) { (i) }
用于在定义原子变量时,初始化为指定的值。如:
static atomic_t count = ATOMIC_INIT(1);
原子操作:UP和SMP的异同
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都 可以认为是"原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令 用于临界资源互斥的原因。但是,在对称多处理器(Symetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以 decl(递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。设想在不同CPU运行的两个进程都在递减某个计数值,可能发生的情况 是:
1. CPU A(上的进程,以下同)从内存单元把当前计数值(2)装载进它的寄存器中;
2. CPU B从内存单元把当前计数值(2)装载进它的寄存器中。
3. CPU A在它的寄存器中将计数值递减为1;
4. CPU B在它的寄存器中将计数值递减为1;
5. CPU A把修改后的计数值(1)写回内存单元。
6. CPU B把修改后的计数值(1)写回内存单元。
我 们看到,内存里的计数值应该是0,然而它却是1。如果该计数值是一个共享资源的引用计数,每个进程都在递减后把该值与0进行比较,从而确定是否需要释放该 共享资源。这时,两个进程都去掉了对该共享资源的引用,但没有一个进程能够释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用。
原子性不可能由软件单独保证--必须需要硬件的支持,因此是和架构相关的。在x86平台上,CPU提供了在指令执行期间对总线加锁的手段。 CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中 的原子性。
Linux内核中的原子操作
Linux 2.4.21中,原子类型的定义和原子操作API都放在内核源码树的include/asm/atomic.h文件中,大部分使用汇编语言实现,因为c语言并不能实现这样的操作。
在x86的原子操作实现代码中,定义了LOCK宏,这个宏可以放在随后的内联汇编指令之前。如果是SMP,LOCK宏被扩展为lock指令;否则被定义为空--单CPU无需防止其它CPU的干扰,锁内存总线完全是在浪费时间。
#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
typedef struct { volatile int counter; } atomic_t;
在所有支持的体系结构上原子类型atomic_t都保存一个int值。在x86的某些处理器上,由于工作方式的原因,原子类型能够保证的可用范围只有24位。volatile是一个类型描述符,要求编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。
#define ATOMIC_INIT(i) { (i) }
用于在定义原子变量时,初始化为指定的值。如:
static atomic_t count = ATOMIC_INIT(1);
//原子的读取atomic_t变量的值,v是这个变量的地址
#define atomic_read(v) ((v)->counter)
//原子的设置atomic_t变量的值,v是这个变量的地址,i要设置的新值;
#define atomic_set(v,i) (((v)->counter) = (i))
//原子的增加atomic_t变量的值,i是要增加的数值,v是这个变量的地址
static __inline__ void atomic_add(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "addl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
//原子的减少atomic_t变量的值,i是要减少的数值,v是这个变量的地址;
static __inline__ void atomic_sub(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "subl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
//原子的对atomic_t变量的值进行减少i的操作,并且检测其结果是否为0;若为0,返回true,否则,返回false;
//i是要减少的数值,v是这个变量的地址;
static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "subl %2,%0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
#define atomic_read(v) ((v)->counter)
//原子的设置atomic_t变量的值,v是这个变量的地址,i要设置的新值;
#define atomic_set(v,i) (((v)->counter) = (i))
//原子的增加atomic_t变量的值,i是要增加的数值,v是这个变量的地址
static __inline__ void atomic_add(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "addl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
//原子的减少atomic_t变量的值,i是要减少的数值,v是这个变量的地址;
static __inline__ void atomic_sub(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "subl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
//原子的对atomic_t变量的值进行减少i的操作,并且检测其结果是否为0;若为0,返回true,否则,返回false;
//i是要减少的数值,v是这个变量的地址;
static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "subl %2,%0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
}
//原子的对atomic_t变量的值进行加1的操作,v是这个变量的地址;
static __inline__ void atomic_inc(atomic_t *v)
{
__asm__ __volatile__(
LOCK "incl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
//原子的对atomic_t变量的值进行减1的操作,v是这个变量的地址;
static __inline__ void atomic_dec(atomic_t *v)
{
__asm__ __volatile__(
LOCK "decl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
//原子的对atomic_t变量的值进行减少1的操作,并且检测其结果是否为0;若为0,返回true,否则,返回false;
//v是这个变量的地址;
static __inline__ int atomic_dec_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "decl %0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"m" (v->counter) : "memory");
return c != 0;
}
//原子的对atomic_t变量的值进行加1的操作,并且检测其结果是否为0;若为0,返回true,否则,返回false;
//v是这个变量的地址;
static __inline__ int atomic_inc_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "incl %0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"m" (v->counter) : "memory");
return c != 0;
}
//原子的对atomic_t变量的值进行加i的操作,并且检测其结果是否为负;若为负,返回true,否则,返回false;
//i是要增加的数值,v是这个变量的地址;
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "addl %2,%0; sets %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
}
//原子的对atomic_t变量的值进行加i的操作,并且将所得结果返回;
//i是要增加的数值,v是这个变量的地址;
static __inline__ int atomic_add_return(int i, atomic_t *v)
{
int __i;
//原子的对atomic_t变量的值进行加1的操作,v是这个变量的地址;
static __inline__ void atomic_inc(atomic_t *v)
{
__asm__ __volatile__(
LOCK "incl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
//原子的对atomic_t变量的值进行减1的操作,v是这个变量的地址;
static __inline__ void atomic_dec(atomic_t *v)
{
__asm__ __volatile__(
LOCK "decl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
//原子的对atomic_t变量的值进行减少1的操作,并且检测其结果是否为0;若为0,返回true,否则,返回false;
//v是这个变量的地址;
static __inline__ int atomic_dec_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "decl %0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"m" (v->counter) : "memory");
return c != 0;
}
//原子的对atomic_t变量的值进行加1的操作,并且检测其结果是否为0;若为0,返回true,否则,返回false;
//v是这个变量的地址;
static __inline__ int atomic_inc_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "incl %0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"m" (v->counter) : "memory");
return c != 0;
}
//原子的对atomic_t变量的值进行加i的操作,并且检测其结果是否为负;若为负,返回true,否则,返回false;
//i是要增加的数值,v是这个变量的地址;
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "addl %2,%0; sets %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
}
//原子的对atomic_t变量的值进行加i的操作,并且将所得结果返回;
//i是要增加的数值,v是这个变量的地址;
static __inline__ int atomic_add_return(int i, atomic_t *v)
{
int __i;
#ifdef CONFIG_M386
unsigned long flags;
if(unlikely(boot_cpu_data.x86==3))
goto no_xadd;
#endif
/* Modern 486+ processor */
__i = i;
__asm__ __volatile__(
LOCK "xaddl %0, %1;"
:"=r"(i)
:"m"(v->counter), "0"(i));
return i + __i;
#ifdef CONFIG_M386
no_xadd: /* Legacy 386 processor */
local_irq_save(flags);
__i = atomic_read(v);
atomic_set(v, i + __i);
local_irq_restore(flags);
return i + __i;
#endif
}
//原子的对atomic_t变量的值进行减i的操作,并且将所得结果返回;
//i是要减少的数值,v是这个变量的地址;
static __inline__ int atomic_sub_return(int i, atomic_t *v)
{
return atomic_add_return(-i,v);
}
//原子的比较old与v是否相等,若相等,则把new的值写入到v中,并且返回old的值;
#define atomic_cmpxchg(v, old, new) ((int)cmpxchg(&((v)->counter), old, new))
//原子的比较
#define atomic_xchg(v, new) (xchg(&((v)->counter), new))
/**
* atomic_add_unless - add unless the number is a given value
* @v: pointer of type atomic_t
* @a: the amount to add to v...
* @u: ...unless v is equal to u.
*
* Atomically adds @a to @v, so long as it was not @u.
* Returns non-zero if @v was not @u, and zero otherwise.
*/
//原子的对atomic_t变量的值进行加a的操作,直到v等于u,如果v与u不等,返回非零值;否则返回0;
//a是要增加的数值,v是这个变量的地址,u是要比较的值;
#define atomic_add_unless(v, a, u) /
({ /
int c, old; /
c = atomic_read(v); /
for (;;) { /
if (unlikely(c == (u))) /
break; /
old = atomic_cmpxchg((v), c, c + (a)); /
if (likely(old == c)) /
break; /
c = old; /
} /
c != (u); /
})
#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)
#define atomic_inc_return(v) (atomic_add_return(1,v))
#define atomic_dec_return(v) (atomic_sub_return(1,v))
//掩码
/* These are x86-specific, used by some header files */
#define atomic_clear_mask(mask, addr) /
__asm__ __volatile__(LOCK_PREFIX "andl %0,%1" /
: : "r" (~(mask)),"m" (*addr) : "memory")
#define atomic_set_mask(mask, addr) /
__asm__ __volatile__(LOCK_PREFIX "orl %0,%1" /
: : "r" (mask),"m" (*(addr)) : "memory")
下面举例说明原子操作的用法:
定义一个atomic_c类型的数据很简单,还可以定义时给它设定初值:
(1) atomic_t u; /*定义 u*/
(2) atomic_t v = ATOMIC_INIT(0) /*定义 v 并把它初始化为0*/
对其操作:
(1) atomic_set(&v,4) /* v = 4 ( 原子地)*/
(2) atomic_add(2,&v) /* v = v + 2 = 6 (原子地) */
(3) atomic_inc(&v) /* v = v + 1 =7(原子地)*/
如果需要将atomic_t转换成int型,可以使用atomic_read()来完成:
printk(“%d/n”,atomic_read(&v)); /* 会打印7*/
原子整数操作最常见的用途就是实现计数器。使用复杂的锁机制来保护一个单纯的计数器是很笨拙的,所以,开发者最好使用atomic_inc()和atomic_dec()这两个相对来说轻便一点的操作。
还可以用原子整数操作原子地执行一个操作并检查结果。一个常见的例子是原子的减操作和检查。
int atomic_dec_and_test(atomic_t *v)
这个函数让给定的原子变量减1,如果结果为0,就返回1;否则返回0
原子位操作
操作函数的参数是一个指针和一个位号
第0位是给定地址的最低有效位
原子位操作中没有特殊的数据类型
例如:set_bit(0, &word);
如:标志寄存器EFLSGS的系统标志,用于控制I/O访问,可屏蔽硬件中断。共32位,不同的位代表不同的信息,对其中信息改变都是通过位操作实现的
原子操作中的位操作部分函数如下:
void set_bit(int nr, void *addr) 原子设置addr所指的第nr位
void clear_bit(int nr, void *addr) 原子的清空所指对象的第nr位
void change_bit(nr, void *addr) 原子的翻转addr所指的第nr位
int test_bit(nr, void *addr) 原子的返回addr位所指对象nr位
int test_and_set_bit(nr, void *addr) 原子设置addr所指对象的第nr位,并返回原先的值
int test_and_clear_bit(nr, void *addr) 原子清空addr所指对象的第nr位,并返回原先的值
int test_and_change_bit(nr, void *addr) 原子翻转addr所指对象的第nr位,并返回原先的值
unsigned long flags;
if(unlikely(boot_cpu_data.x86==3))
goto no_xadd;
#endif
/* Modern 486+ processor */
__i = i;
__asm__ __volatile__(
LOCK "xaddl %0, %1;"
:"=r"(i)
:"m"(v->counter), "0"(i));
return i + __i;
#ifdef CONFIG_M386
no_xadd: /* Legacy 386 processor */
local_irq_save(flags);
__i = atomic_read(v);
atomic_set(v, i + __i);
local_irq_restore(flags);
return i + __i;
#endif
}
//原子的对atomic_t变量的值进行减i的操作,并且将所得结果返回;
//i是要减少的数值,v是这个变量的地址;
static __inline__ int atomic_sub_return(int i, atomic_t *v)
{
return atomic_add_return(-i,v);
}
//原子的比较old与v是否相等,若相等,则把new的值写入到v中,并且返回old的值;
#define atomic_cmpxchg(v, old, new) ((int)cmpxchg(&((v)->counter), old, new))
//原子的比较
#define atomic_xchg(v, new) (xchg(&((v)->counter), new))
/**
* atomic_add_unless - add unless the number is a given value
* @v: pointer of type atomic_t
* @a: the amount to add to v...
* @u: ...unless v is equal to u.
*
* Atomically adds @a to @v, so long as it was not @u.
* Returns non-zero if @v was not @u, and zero otherwise.
*/
//原子的对atomic_t变量的值进行加a的操作,直到v等于u,如果v与u不等,返回非零值;否则返回0;
//a是要增加的数值,v是这个变量的地址,u是要比较的值;
#define atomic_add_unless(v, a, u) /
({ /
int c, old; /
c = atomic_read(v); /
for (;;) { /
if (unlikely(c == (u))) /
break; /
old = atomic_cmpxchg((v), c, c + (a)); /
if (likely(old == c)) /
break; /
c = old; /
} /
c != (u); /
})
#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)
#define atomic_inc_return(v) (atomic_add_return(1,v))
#define atomic_dec_return(v) (atomic_sub_return(1,v))
//掩码
/* These are x86-specific, used by some header files */
#define atomic_clear_mask(mask, addr) /
__asm__ __volatile__(LOCK_PREFIX "andl %0,%1" /
: : "r" (~(mask)),"m" (*addr) : "memory")
#define atomic_set_mask(mask, addr) /
__asm__ __volatile__(LOCK_PREFIX "orl %0,%1" /
: : "r" (mask),"m" (*(addr)) : "memory")
下面举例说明原子操作的用法:
定义一个atomic_c类型的数据很简单,还可以定义时给它设定初值:
(1) atomic_t u; /*定义 u*/
(2) atomic_t v = ATOMIC_INIT(0) /*定义 v 并把它初始化为0*/
对其操作:
(1) atomic_set(&v,4) /* v = 4 ( 原子地)*/
(2) atomic_add(2,&v) /* v = v + 2 = 6 (原子地) */
(3) atomic_inc(&v) /* v = v + 1 =7(原子地)*/
如果需要将atomic_t转换成int型,可以使用atomic_read()来完成:
printk(“%d/n”,atomic_read(&v)); /* 会打印7*/
原子整数操作最常见的用途就是实现计数器。使用复杂的锁机制来保护一个单纯的计数器是很笨拙的,所以,开发者最好使用atomic_inc()和atomic_dec()这两个相对来说轻便一点的操作。
还可以用原子整数操作原子地执行一个操作并检查结果。一个常见的例子是原子的减操作和检查。
int atomic_dec_and_test(atomic_t *v)
这个函数让给定的原子变量减1,如果结果为0,就返回1;否则返回0
原子位操作
操作函数的参数是一个指针和一个位号
第0位是给定地址的最低有效位
原子位操作中没有特殊的数据类型
例如:set_bit(0, &word);
如:标志寄存器EFLSGS的系统标志,用于控制I/O访问,可屏蔽硬件中断。共32位,不同的位代表不同的信息,对其中信息改变都是通过位操作实现的
原子操作中的位操作部分函数如下:
void set_bit(int nr, void *addr) 原子设置addr所指的第nr位
void clear_bit(int nr, void *addr) 原子的清空所指对象的第nr位
void change_bit(nr, void *addr) 原子的翻转addr所指的第nr位
int test_bit(nr, void *addr) 原子的返回addr位所指对象nr位
int test_and_set_bit(nr, void *addr) 原子设置addr所指对象的第nr位,并返回原先的值
int test_and_clear_bit(nr, void *addr) 原子清空addr所指对象的第nr位,并返回原先的值
int test_and_change_bit(nr, void *addr) 原子翻转addr所指对象的第nr位,并返回原先的值