多个执行单元同时、并行被执行时,而并发的执行单元对共享资源(硬件资源和软件资源上的全局变量、静态变量等)的访问则很容易导致竞态。访问共享资源的代码区成为临界区,临界区需要以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是linux设备驱动中可采用的互斥途径。
中断屏蔽:可以保证正在执行的内核执行路径不被中断处理程序所抢占。
使用方法:
local_irq_disable()//屏蔽中断
....
critical section//临界区
....
local_irq_enable()//开启中断
我们来看下local_irq_disable()的宏定义:(见include/linux/Irqflags.h):
#define local_irq_disable() / 62 do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
local_irq_disable()的作用是关闭中断,可以从宏中看出,local_irq_disable()调用raw_local_irq_disable()函数,而后者最终调用native_irq_disable()。
static inline void raw_local_irq_disable(void) 75{ 76 native_irq_disable(); 77} 78 79static inline void raw_local_irq_enable(void) 80{ 81 native_irq_enable(); 82} 83 84/* 85 * Used in the idle loop; sti takes one instruction cycle 86 * to complete: 87 */
以native_irq_disable()为例,native_irq_enable同理:(见arch/x86/include/asm/irqflags.h)
static inline void native_irq_disable(void) 38{ 39 asm volatile("cli": : :"memory");
温馨提示:汇编语言中,CLI是清除中断标志位,STI是设置中断标志位。“asm”表示后面的代码为内嵌汇编,“volatile”表示编译器不要优化代码,后面的指令保留原样。__asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分),memory表示在内存中进行操作 。
void trace_hardirqs_off(void) 529{ 530 if (!preempt_trace() && irq_trace()) 531 start_critical_timing(CALLER_ADDR0, CALLER_ADDR1); 532} 533EXPORT_SYMBOL(trace_hardirqs_off);
local_irq_disable()与local_irq_save(flags)除了进行禁止中断的操作以外,还保存目前CPU的中断位信息,local_irq_restore(flags)进行的是与local_irq_save(flags)相反的操作。仅想禁止中断底部,应使用local_bh_disable(),使能被local_bh_disable禁止的底半步调用local_bh_enable()。
信号量 :用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是与自旋锁不同的是,进程不会原地打转而是进入休眠状态。两者区别详见http://blog.csdn.net/mia_go/archive/2010/10/24/5961878.aspx。
内核对信号量 semaphore的描述:
struct semaphore { 17 spinlock_t lock; 18 unsigned int count; 19 struct list_head wait_list; 20};
等待队列wait_list定义为list_head类型,双向链表。spinlock_t是如何定义?见linux/include/linux/spinlock_types.h。
typedef struct spinlock { 65 union { 66 struct raw_spinlock rlock; 67 68#ifdef CONFIG_DEBUG_LOCK_ALLOC 69# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map)) 70 struct { 71 u8 __padding[LOCK_PADSIZE]; 72 struct lockdep_map dep_map; 73 }; 74#endif 75 }; 76} spinlock_t;
信号量使用如下:
1.定义一个名为sem的信号量: struct semaphore sem;
2.初始化信号量:void sema_init (struct semaphore *sem,int val); //该函数初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。
void init_MUTEX(struct semaphore *sem); //该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置成1或0,等同于sema_init(struct semaphore *sem,1或0)。
此外,两个宏定义并初始化信号量的“快捷方式”。
DECLEAR_MUTEX(name) //定义一个名为name的信号量并初始化为1
DECLEAR_MUTEX_LOCKED(name) //定义一个名为name的信号量并初始化为0
3.获得信号量:
void down(struct semaphore *sem); //该函数获得sem会导致失眠,因此不能再中断上下文使用。类似的,int down_interruptible(struct semaphore *sem)/int down_trylock(struct semaphore *sem),这里不再作解释。
4.释放信号量:
void up(struct semaphore *sem); //释放信号量sem,唤醒等待者。
信号量一般使用如下:
DECLEAR_MUTEX(mount_sem); //定义信号量
down(&mount_sem); //获取信号量,保护临界区
... ...
critical section //临界区
... ...
up(&mount_sem); //释放信号量
具体看过来:
void down(struct semaphore *sem) 54{ 55 unsigned long flags; 56 57 spin_lock_irqsave(&sem->lock, flags); 58 if (likely(sem->count > 0)) 59 sem->count--; 60 else 61 __down(sem); 62 spin_unlock_irqrestore(&sem->lock, flags); 63} 64EXPORT_SYMBOL(down); 65
对于down()进一步到底层来看:
static inline void down(struct semaphore * sem) 106{ 107 might_sleep(); 108 __asm__ __volatile__(/*在多个CPU平台下使用lock前缀,锁住内存总线。*/ 109 "# atomic down operation/n/t" 110 LOCK "decl %0/n/t" /* --sem->count */ /*如果sem->count>=0就跳转到107处*/ 111 "js 2f/n" 112 "1:/n" 113 LOCK_SECTION_START("") 114 "2:/tlea %0,%%eax/n/t" 115 "call __down_failed/n/t" 116 "jmp 1b/n" 117 LOCK_SECTION_END 118 :"=m" (sem->count) 119 : 120 :"memory","ax"); 121}
在这个函数中,%0 对应于sem->count,如果sem->count 减1 后,结果不小于0,则说明down 操作成功完成。否则调用__down_failed,它最终将调用__down()把当前进程设置为等待状态,并把当前进程加入到该信号量的等待队列,调度其他进程来运行。当某个进程释放相应的资源时调用up()。
回过头来继续spin_lock_irqsave:
454#ifdef CONFIG_SMP 455#define spin_lock_irqsave(lock, flags) flags = _spin_lock_irqsave(lock) 459#define spin_lock_irqsave(lock, flags) _spin_lock_irqsave(lock, flags)
#define _spin_lock_irqsave(lock, flags) / 296do { / 297 local_irq_save(flags); / 298 preempt_disable(); / 299 _raw_spin_lock(lock); / 300 __acquire(lock); / 301} while (0)
由于中断许可位位于CPU 的标志寄存器中,因此spin_lock_irqsave()在获取自旋锁之前,把标志寄存器的值保存到flags 中,而spin_unlock_irqrestore()在释放自旋锁之后,占,由于中断或系统调用之后,可能会调度其他的进程运行(根据flags 恢复标志寄存器。这是通过下面的两个函数完成的。preempt_disable()的作用是关闭进程抢例如当前进程时间片用完,或者有一个拥有更高优先级的进程已经进入了就绪状态),preempt_disable()关闭调度器的这个功能,从而保证当前进程在执行临界区代码的过程中不会被其他进程干扰。