并发基础 -- Linux 内核同步机制

来源:互联网 发布:淘宝网店首页图片 编辑:程序博客网 时间:2024/05/16 09:49

1 Linux 原子操作

通常用于linux内核的同步机制。所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。 在单处理系统中(UniProcessor)中,能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间;在多处理系统中, 由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。原子操作不可能由软件单独完成,需要硬件的支持,因此是架构相关的。其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。 

原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下: 

#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif

在x86的原子操作实现代码中,定义了LOCK宏,这个宏可以放在随后的内联汇编指令之前。如果是SMP,LOCK宏被扩展为lock指令;否则被定义为空--单CPU无需防止其它CPU的干扰,锁内存总线完全是在浪费时间。


typedef struct { volatile int counter; } atomic_t;
在所有支持的体系结构上原子类型atomic_t都保存一个int值。在x86的某些处理器上,由于工作方式的原因,原子类型能够保证的可用范围只有24位。volatile是一个类型描述符,要求编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。

 

几个原子操作的utiltiy函数:

(1) #define ATOMIC_INIT(i) { (i) } // 用于在定义原子变量时,初始化为指定的值

(2) #define atomic_read(v)  ((v)->counter) // 读取v指向的原子变量的值。由于该操作本身就是原子的,只需要一次内存访问就能完成,因此定义为一个宏,并用C代码实现。

(3) #define atomic_set(v,i)  (((v)->counter) = (i)) //设置v指向的原子变量为i

(4) static __inline__ void atomic_add(int i, atomic_t *v) //将v指向的原子变量加上i。
{
 __asm__ __volatile__(
  ATOMIC_SMP_LOCK "addl %1,%0"
  :"=m" (v->counter)
  :"ir" (i), "m" (v->counter));
}

 

实现中,使用了带有C/C++表达式的内联汇编代码,格式如下(参考《AT&T ASM Syntax》):
__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
__asm__ __volatile__指示编译器原封不动保留表达式中的汇编指令系列,不要考虑优化处理。涉及的约束还包括:
1) 等号约束(=):只能用于输出操作表达式约束,说明括号内的左值表达式v->counter是write-only的。
2) 内存约束(m):表示使用不需要借助寄存器,直接使用内存方式进行输入或输出。
3) 立即数约束(i):表示输入表达式是一个立即数(整数),不需要借助任何寄存器。
4) 寄存器约束(r):表示使用一个通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl和%edx/%dx/%dl中选取一个合适的。

(5) static __inline__ void atomic_sub(int i, atomic_t *v)
{
 __asm__ __volatile__(
  ATOMIC_SMP_LOCK "subl %1,%0"
  :"=m" (v->counter)
  :"ir" (i), "m" (v->counter));
}

(6) static __inline__ int atomic_inc_and_test(atomic_t *v)
{
 unsigned char c;

 __asm__ __volatile__(
  ATOMIC_SMP_LOCK "incl %0; sete %1"
  :"=m" (v->counter), "=qm" (c)
  :"m" (v->counter) : "memory");
 return c != 0;
}

 

(7) static __inline__ int atomic_add_negative(int i, atomic_t *v)
将v指向的原子变量加上i,并测试结果是否为负。若为负,返回真,否则返回假。这个操作用于实现semaphore。  

 

自定义函数:
#define CAS(_a, _o, _n)                                    /
({ __typeof__(_o) __o = _o;                                /
   __asm__ __volatile__(                                   /
       "lock cmpxchg %3,%1"                                /
       : "=a" (__o), "=m" (*(volatile unsigned int *)(_a)) /
       :  "0" (__o), "r" (_n) );                           /
   __o;                                                    /
})

 

cmpxchg : 将al,ax,eax,rax中的值与首操作数比较,如果相等,第2操作数的直装载到首操作数,zf置1。如果不等, 首操作数的值装载到al,ax,eax,rax并将zf清0。 

 

应用场景:

原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。  

当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。 

 

2 信号量(semaphore) 

原创粉丝点击