本文不打算详细探究spin_lock的详细实现机制,只是最近对raw_spin_lock的出现比较困扰,搞不清楚什么时候用spin_lock,什么时候用raw_spin_lock,因此有了这篇文章。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
1. 临界区(Critical Section)
- (1) 在可以抢占(preemption)的系统中,两个线程同时访问同一个对象;
- (2) 线程和中断同时访问同一个对象;
- (3) 在多核系统中(SMP),可能两个CPU可能同时访问同一个对象;
2. 自旋锁(spin_lock)
- preempt_disable();
- .....
- // 访问共享对象的代码
- ......
- preempt_enable();
同样地,针对单处理器系统,第二种情况,只要临时关闭中断即可,我们可以使用以下方法:
- local_irq_disable();
- ......
- // 访问共享对象的代码
- ......
- local_irq_enable();
那么,针对多处理器的系统,以上的方法还成立么?答案很显然:不成立。
对于第一种情况,虽然抢占被禁止了,可是另一个CPU上还有线程在运行,如果这个线程也正好要访问该共享对象,上面的代码段显然是无能为力了。
对于第二种情况,虽然本地CPU的中断被禁止了,可是另一个CPU依然可以产生中断,如果他的中断服务程序也正好要访问该共享对象,上面的代码段也一样无法对共享对象进行保护。
- spin_lock(spinlock_t *lock);
- spin_unlock(spinlock_t *lock);
- spin_lock_irq(spinlock_t *lock);
- spin_unlock_irq(spinlock_t *lock);
- spin_lock_irqsave(lock, flags);
- spin_lock_irqrestore(lock, flags);
- 如果只是在普通线程之间同时访问共享对象,使用spin_lock()/spin_unlock();
- 如果是在中断和普通线程之间同时访问共享对象,并且确信退出临界区后要打开中断,使用spin_lock_irq()/spin_unlock_irq();
- 如果是在中断和普通线程之间同时访问共享对象,并且退出临界区后要保持中断的状态,使用spin_lock_irqsave()/spin_unlock_irqrestore();
3. raw_spin_lock
- 把原来的raw_spin_lock改为arch_spin_lock;
- 把原来的spin_lock改为raw_spin_lock;
- 实现一个新的spin_lock;
- 尽可能使用spin_lock;
- 绝对不允许被抢占和休眠的地方,使用raw_spin_lock,否则使用spin_lock;
- 如果你的临界区足够小,使用raw_spin_lock;
spin_lock VS spin_lock_irqsave
他们两者只有一个差别:是否调用local_irq_disable()函数, 即是否禁止本地中断。在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。举个例子:进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock)试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置TASK_INTERRUPT状态,中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就无法再调度进程A运行,这样就导致了死锁!但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。 因为在不同的CPU上出现中断不会导致进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会获得CPU,执行并退出临界区。所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。 spin_lock_irq这个宏会关中断,关掉的是单个cpu的中断,这个宏preempt_disable()禁掉的是当前cpu的抢占。spin_lock可重入和FIFO。不具备任务优先级。在多核竞争负担重。test_and_settest_and_set指令取出内存某一单元(位)的值,然后再给该单元(位)赋一个新值,关于为何这两条指令能实现互斥我们不在赘述,读者可以了解其算法) 这些指令涉及对同一存储单元的两次或两次以上操作,这些操作将在几个指令周期内完成,但由于中断只能发生在两条机器指令之间,而同一指令内的多个指令周期不可中断,从而保证swap指令或test_and_set指令的执行不会交叉进行.
MUTEX VS SPINLOCK
无论是mutex还是spinlock,如果一个thread去给一个已经被其他thread占用的锁上锁,那么从此刻起到其他thread对此锁解锁的时间长短将会导致mutex和spinlock出现下面的问题。mutex的问题是,它一旦上锁失败就会进入sleep,让其他thread运行,这就需要内核将thread切换到sleep状态,如果mutex又在很短的时间内被释放掉了,那么又需要将此thread再次唤醒,这需要消耗许多CPU指令和时间,这种消耗还不如让thread去轮讯。也就是说,其他thread解锁时间很短的话会导致CPU的资源浪费。
spinlock的问题是,和上面正好相反,如果其他thread解锁的时间很长的话,这种spinlock进行轮讯的方式将会浪费很多CPU资源。
PER-CPU
per-CPU 变量是一个有趣的 2.6 内核特性,定义在 <linux/percpu.h> 中。当创建一个per-CPU变量,系统中每个处理器都会获得该变量的副本。其优点是对per-CPU变量的访问(几乎)不需要加锁,因为每个处理器都使 用自己的副本。per-CPU 变量也可存在于它们各自的处理器缓存中,这就在频繁更新时带来了更好性能。
|
|
当要访问另一个处理器的变量副本时, 使用:
|
|
per-CPU变量可以导出给模块, 但必须使用一个特殊的宏版本:
|
资源竞争中的一些问题。
高速缓存一致性问题,每一个CPU有一个本地的CACHE拷贝。通过snoop机制,对系统总线进行监控,如果内存有写的动作,并且cache中有操作目标,就放弃原cache line.memory barrier 机制,对于逻辑上已经完成,物理上没有完成的操作,一次性的完成。锁总线的操作都会引起memory barrier。MP和多线程安全问题。中断和线程并发问题。对硬件的同时访问。访问关键区时来中断。重入的实现,不能更新全局资源的值,不能返回全局资源的值,只能使用调用者的数据。不能调用不能重入的函数。不上锁只初始化一次的实例。死锁条件(1) 互斥条件:一个资源每次只能被一个进程使用。(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
void down(struct semaphore *sem);//down减少信号量的值,并在必要时一直等待
int down_interruptible(struct semaphore *sem);//down_interruptible完成相同工作,它允许等待在某个信号量上的用户空间进程可被用户中断
int down_trylock(struct semaphore *sem);//down_trylock不会休眠,如果信号量在调用时不可获得,会立即返回一个非零值
他的返回值有三种 1. “0” 2. “-ETIME” 3.“-EINTR”
0 代表正常返回
-ETIME 等待超时
-EINTR 中断
67 * down_interruptible - acquire the semaphore unless interrupted
68 * @sem: the semaphore to be acquired
69 *
70 * Attempts to acquire the semaphore. If no more tasks are allowed to
71 * acquire the semaphore, calling this function will put the task to sleep.
72 * If the sleep is interrupted by a signal, this function will return -EINTR.
73 * If the semaphore is successfully acquired, this function returns 0.
74 */