Linux kernel 提供的同步机制主要有下面几种:
1.自旋锁 spin lock
一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),这是自旋锁的要点。所以,自旋锁不应该被长时间持有。
事实上,这点也正是使用自旋锁的初衷: 在短时间内进行轻量级加锁。
还可以采取另外的方式(信号量 semaphore)来处理对锁的争用: 让请求线程睡眠,直到锁重新可用时再唤醒它。
这样,处理器就不用循环等待,可以去执行其他代码。这也会带来一定的开销—— 这里有两次明显的上下文切换,被阻塞的线程要换出和换入,与实现自旋锁的少数几行代码相比,上下文切换当然有比较多的代码。因此,持有自旋锁的时间最好小于完成两次上下文切换的耗时。
自旋锁的实现和体系结构密切相关,代码往往通过汇编实现。 这些与体系结构相关的代码定义在文件 <asm/spinlock.h> 中,实际需要用到的接口定义在文件<linux/spinlock.h> 中。 自旋锁的基本使用形式如下:
DEFINE_SPINLOCK(mr_lock);
spin_lock(mr_lock);
/* 临界区... ... */
spin_unlock(mr_lock);
注意:
- 自旋锁是不可以递归的
- 自旋锁可以使用在中断服务程序中(此处不能使用信号量,因为它们会导致睡眠)。 在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在当前处理器上的中断请求),否则,中断处理程序如果打断正在持有锁的内核代码,并去争用这个已经被持有的自旋锁。这样一来,这段中断服务程序就会自旋,等待该锁重新可用。但是,锁的持有者在这个中断处理程序执行完毕之前不可能运行。这就是双重请求死锁。(这一点理解的还不是很好,如果不是在中断服务程序中获得了某个锁,而没有禁止中断,这时被中断服务程序打断了,而这段中断服务程序中同样获取之前那个锁,这样岂不是也会导致自旋???)
内核提供的禁止中断同时请求锁的接口:
DEFINE_SPINLOCK(mr_lock);
unsigned long flags;
spin_lock_irqsave(&mr_lock, flags);
/*临界区 ... ... */
spin_unlock_irqrestore(&mr_lock, flags);
2. 信号量(semaphore)
函数down_interruptible() 试图获取指定的信号量,如果信号量不可用,它将调用进程置成 TASK_INTERRUPTIBLE 状态,进入睡眠状态。
这种状态意味着任务可以被信号唤醒,如果进程在等待获取信号量的时候接收到了信号,那么该进程将会被唤醒。
另外一个函数down() 会让进程在TASK_UNINTERRUPTIBLE 状态下睡眠,这样一来进程在等待信号量的时候就不再响应信号了。
down_trylock() 函数,试图获取指定的信号量,在信号量已经被占用时,它立刻返回非0值。
up() 用于释放给定的信号量。
3. 互斥 (mutex)
互斥就是计数为1 的信号量。
常用的操作互斥的接口
DEFINE_MUTEX(name);
mutex_init(&mutex);
mutex_lock(&mutex);
mutex_unlock(&mutex);