学习笔记之Linux内核同步方法
一、程序并发执行的原因
在用户空间:
调度程序完全可能选择另一个高优先级的进程到处理器上执行,所以就有可能一个程序正处于临界区时被非自愿的抢占了,如果新调度的的进程随后也进入到同一个临界区,前后两个进程之间就会产生竞争。
信号是异步发生的也有可能产生竞争条件。
在内核空间:
中断:中断几乎可以在任何时候异步发生,也就可能随时打断当前正在执行的代码。
软中断和tasklet:内核能在任何时候唤醒或调度软中断和tasklet,打断当前正在执行的代码。
内核抢占:因为内核可以抢占,所以内核中的任务可能会被另一任务抢占。
睡眠及用户空间的同步:在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而调度一个新的用户进程执行。
对称多处理:两个或多个处理器可以同时执行代码
二、内核同步方法
原子操作:只能针对atomoc_t类型的数据进行处理,还能进行原子位操作,原子性保证指令执行期间不被打断。原子操作与更复杂的同步方法相比较,给系统带来的开销小,对高速缓存行的影响也小,但当临界区跨越多个函数或者数据结构时候,原子操作就无能为力了,这时后就需要使用更复杂的同步方法——锁。
自旋锁(Spin_lock):自旋锁最多只能被一个可执行现成拥有,一个被争用的自旋锁使得请求它的线程在等待等待重新可用时候自旋,因此浪费处理其时间,但短时间的自选相比让等待现成睡眠进行上下文切换的代码和开销要小得多,所以自旋锁适合于在短时间内进行轻量级加锁。
a)Linux自旋锁是不可递归的。
b)自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它会导致睡眠),在使用自旋锁获取锁之前,首先要禁止本地中断(当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。
c)由于下半部可以抢占进程上下文中的代码,所以当下半部和进程上下文共享数据时,必须对进程上下文中的共享数据进行保护,所以需要加锁的同时还要禁止下半部执行。同样,由于中断处理程序可以抢占下半部,所以如果中断处理程序和下半部共享数据,就必须在获取适当的锁的同时还要禁止中断。
1)同类的tasklet不能同时运行,所以对于同类tasklet中的共享数据不需要保护。但是当数据被两个不同种类的tasklet共享时,就需要在访问下半部的数据前先获得一个普通锁,这里不需要禁止下半部,因为同一个处理器上绝对不会出现tasklet相互抢占的情况。
2)对于软中断,不论是否是同一种类型,如果数据被软中断共享,那么它必须得到锁的保护。这是因为即使是同类型的两个软中断也可以同时运行在一个系统的多个处理器上。但是,同一处理器上的一个软中断绝对不会抢占另外一个软中断,因此没有必要禁止下半部。
信号量(Semaphore):信号量是一种睡眠锁,如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推入一个等待队列,然后让其睡眠,这时处理器能重获自由,从而去执行其他代码。
1) 信号量适用于锁的会被长时间持有的情况。时间较短的时,不适合使用信号量,因为睡眠,维护等待队列以及唤醒所花费的开销可能比锁被占用的全部时间还长。
2) 只能在进程上下文中才能获取信号量锁。
3) 占有信号量的同时不能占用自旋锁。
l 完成量(Completion):如果内核中的一个任务需要发出信号通知另一任务发生了某个特定事件,利用完成量是使两个任务得以同步的简单方法。