Linux学习自旋锁,信号量

自旋锁实际上是一种忙等,对于自旋锁有两点需要注意一下:
1、当锁不可用时,CPU一直循环执行“测试并设置”(测试并设置:是原子性操作)该锁直到可用而取得该锁,CPU在自旋等待时不做任何有用的工作,只是进行等待。因此只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备时,需要较长时间占用锁,使用自旋锁会降低系统的性能。
2、自旋锁可能导致系统死锁,引起这个问题常见的情况是递归使用一个自旋锁,即如果一个已经拥有A自旋锁的CPU想要第二次获得A自旋锁时,该CPU将会死锁。另一种情况是,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。copy_from_user()、copy_to_user()和kmalloc()等函数都有可能引起阻塞,因此在自旋锁期间不能调用这些函数。

//定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);

spin_lock(&lock); //获取自旋锁,保护临界区
...//临界区
spin_unlock(&lock); //解锁


spin_lock() //禁止内核抢占,自旋

引用这样一个例子:
“在这样的场景下,使用spin lock可以保护访问共享资源R的临界区吗?
我们假设CPU0上的进程A持有spin lock进入临界区,这时候,外设P发生了中断事件,并调度到了CPU1上执行,看起来没有什么问题,执行在CPU1上的handler会稍微等待一会CPU0上的进程A,等它立刻临界区就会释放spin lock的,但是,如果外设P的中断事件被调度到了CPU0上执行会怎么样?CPU0上的进程A在持有spin lock的状态下被中断上下文抢占,而抢占它的CPU0上的handler在进入临界区之前仍然会试图获取spin lock,悲剧发生了,CPU0上的P外设的中断handler永远的进入spin状态,这时候,CPU1上的进程B也不可避免在试图持有spinlock的时候失败而导致进入spin状态。为了解决这样的问题,linux kernel采用了这样的办法:如果涉及到中断上下文的访问,spin lock需要和禁止本CPU上的中断联合使用。

这个时候需要使用下面的这个自旋函数来解决这个问题:

spin_lock_irqsave() //禁止本地CPU中断+(禁止抢占+自旋)

信号量:是用于保护临界区的常用手段。与自旋锁相同,只有得到信号量的进程才能够执行临界区的代码。不同的是,进程在得不到信号量时不会原地自旋,而是会进入休眠等待状态(会释放CPU)而自旋锁不会释放CPU(原地打转)。

struct semaphore sem;
sema_init(sem, 1); //初始化信号量,这里把信号量定义为1,作为互斥信号量

down(sem); //获得信号量
…//临界区
up(sem); //释放信号量

“`
自旋锁和信号量都是解决互斥问题的基本手段,面对特定的情况,应该如何进行选择呢?
选择的依据是临界区的性质和系统的特点。

1、信号量是进程级的,用于多个进程之间对资源的互斥,虽然也在内核中,但是该内核执行路径是以进程为身份,代表进程来争夺资源的。如果竞争失败,会发生进程的上下文切换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销很大,因此,只有当进程占用资源时间较长时,用信号量才是较好的选择。

2、当所要保护的临界区访问时间较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间(CPU是一直在忙等,没有切换)。但是CPU得不到自旋锁会在那里空转直到其执行单元解锁为止。所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。

3、自旋锁不能包含可能引起阻塞的代码。因为阻塞代表进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就产生了。这种情况要用信号量。

猜你喜欢

转载自blog.csdn.net/shenjin_s/article/details/80059045
今日推荐