概念
锁一般都是用来保护一个临界资源(大抵是全局静态变量这种)的,防止一个在访问时,另外一个程序也来访问
一般使用于运行和等待时间较短的
内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:一个是原地等待 一个是挂起当前进程,调度
其他进程执行(睡眠)。
Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的,即:
一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(仍然占用CPU,并不是休眠:忙等待)。
所以自旋锁不会导致线程状态的切换,一直处于用户态 即线程一直都是 active 的。不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
(上下文切换:当程序休眠时,CPU会切换到其他程序处,然后再切换回来)
注意事项
进程拥有自旋锁的时候,该 cpu 上是禁止抢占的;只要进入自旋,就一直占据CPU;
一般用于多 cpu 之间的资源竞争;
由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制,自旋锁不应该被长时间的持有(消耗 CPU资源),一般应用在中断上下文。
定义和使用
动态的:
spinlock_t lock;
spin_lock_init (&lock);
静态的:
DEFINE_SPINLOCK(lock);
加锁
spin_lock(&lock);
解锁
spin_unlock(&lock);
1、我们要访问临界资源需要首先申请自旋锁;
2、获取不到锁就自旋,如果能获得锁就进入临界区;
3、当自旋锁释放后,自旋在这个锁的任务即可获得锁并进入临界区,退出临界区的任务必须释放自旋锁。
static spinlock_t lock;
static int flag = 1;
static int hello_open (struct inode *inode, struct file *filep)
{
printk("hello_open()\n");
spin_lock(&lock);
if(flage != 1)
{
spin_unlock(&lock);
return -EBUSY;
}
flag = 0;
spin_unlock(&lock);
return 0;
}
static int hello_release (struct inode *inode, struct file *filep)
{
printk("hello_release()\n");
flage = 1;
return 0;.
}
自旋锁保护的是flag,当flag为1的时候可以继续进行;
注意:当上锁之后因为任何情况而退出时,都要释放锁
死锁
1、拥有自旋锁的进程 A 在内核态阻塞了,内核调度 B 进程,碰巧 B 进程也要获得自旋锁,此时 B 只能自旋转 。 而此时抢占已经关闭(单核)不会 调度 A 进程了 B 永远自旋,产生死锁 。
2、进程 A 拥有自旋锁,中断到来,CPU 执行中断函数中断处理函数 中断处理函数需要获得自旋锁 访问共享资源,此时无法获得锁 只能自旋 产生死锁 。
主要原因:进入sleep暂时不会占用CPU,结果CPU被趁虚而入,被自旋锁占用;无法再运行别的命令。
自旋锁最核心的问题就是申请不到锁,就会一直占据一个CPU;如果这个锁一直不被释放那么就会导致死锁;不被释放可能是由于带锁的程序被自旋锁抢占了CPU,或者自己出不来了等等。
如何选择锁
1、在中断中只能使用自旋锁是为了快,尽可能少的占用CPU时间;
2、任务睡眠使用互斥体;
互斥体和信号量
互斥体和信号量很相似,内核中两者共存会令人混淆。所幸,它们的标准使用方式都有简单规范:除非 mutex 的某个约束妨碍你使用,否则相比信号量要优先使用 mutex 。
当你写新代码时,只有碰到特殊场合(一般是很底层代码)才会需要使用信号量。因此建议选 mutex 。如果发现不能满足其约束条件,且没有其他别的选择时,再考虑选择信号量。
问题是互斥体只限于一个访问,信号量可以多个坑位。