多个执行单元同时、并行被执行时,而并发的执行单元对共享资源(硬件资源和软件资源上的全局变量、静态变量等)的访问则很容易导致竞态。访问共享资源的代码区成为临界区,临界区需要以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是linux设备驱动中可采用的互斥途径。
中断屏蔽:可以保证正在执行的内核执行路径不被中断处理程序所抢占。
使用方法:
local_irq_disable()//屏蔽中断
....
critical section//临界区
....
local_irq_enable()//开启中断
我们来看下local_irq_disable()的宏定义:(见include/linux/Irqflags.h):
local_irq_disable()的作用是关闭中断,可以从宏中看出,local_irq_disable()调用raw_local_irq_disable()函数,而后者最终调用native_irq_disable()。
以native_irq_disable()为例,native_irq_enable同理:(见arch/x86/include/asm/irqflags.h)
温馨提示:汇编语言中,CLI是清除中断标志位,STI是设置中断标志位。“asm”表示后面的代码为内嵌汇编,“volatile”表示编译器不要优化代码,后面的指令保留原样。__asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分),memory表示在内存中进行操作 。
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.youkuaiyun.com/mia_go/archive/2010/10/24/5961878.aspx。
内核对信号量 semaphore的描述:
等待队列wait_list定义为list_head类型,双向链表。spinlock_t是如何定义?见linux/include/linux/spinlock_types.h。
信号量使用如下:
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); //释放信号量
具体看过来:
对于down()进一步到底层来看:
在这个函数中,%0 对应于sem->count,如果sem->count 减1 后,结果不小于0,则说明down 操作成功完成。否则调用__down_failed,它最终将调用__down()把当前进程设置为等待状态,并把当前进程加入到该信号量的等待队列,调度其他进程来运行。当某个进程释放相应的资源时调用up()。
回过头来继续spin_lock_irqsave:
由于中断许可位位于CPU 的标志寄存器中,因此spin_lock_irqsave()在获取自旋锁之前,把标志寄存器的值保存到flags 中,而spin_unlock_irqrestore()在释放自旋锁之后,占,由于中断或系统调用之后,可能会调度其他的进程运行(根据flags 恢复标志寄存器。这是通过下面的两个函数完成的。preempt_disable()的作用是关闭进程抢例如当前进程时间片用完,或者有一个拥有更高优先级的进程已经进入了就绪状态),preempt_disable()关闭调度器的这个功能,从而保证当前进程在执行临界区代码的过程中不会被其他进程干扰。