本文主要是介绍由自旋锁引起的思考,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《在深入理解Linux内核》这本书中第五章讲到了内核同步,当内核控制路径必须访问共享数据结构或进入临界区时,就需要为自己获得一把“锁”。由锁机制保护的资源非常类似于限制于房间内的资源,当某人进入房间时,就把门锁上。如果内核控制路径希望访问资源,就试图获得钥匙“打开门”。当且仅当资源空闲时,它才能成功。然后,只要它还想使用这个资源,门依然锁着。当内核控制路径释放了锁时,门就打开,另外一个内核路径就可以进入房间。这是书中主要对自旋锁的描述,这里把自旋锁放在内核同步里讲,但是我个人感觉可以把自旋锁的功能单独拿出来讲一下。
自旋锁的功能是为实现保护共享资源,保护共享资源在Linux中主要有以下几种: 自旋锁(spin lock)、信号量(semaphore)、原子操作、中断屏蔽 、BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4内核中)、RCU和seqlock(只在2.6内核中).
(一)自旋锁(spin lock)
自旋锁(spin lock)是专为防止多处理器并发而引入的一种锁,它应用于中断处理等部分。对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。自旋锁最多只能被一个内核任务持有,内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在周围“旋转”,反复执行一条紧凑的循环指令,直到锁被释放。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
事实上,自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量(在后面将做介绍)。但是自旋锁节省了上下文切换的开销。自旋锁都用spinlock_t结构表示,其中包含两个字段:
slock
该字段表示自旋锁的状态:值为1表示“未加锁”状态,而任何负数和0都表示“加锁”状态
break_lock
表示进程正在忙等自旋锁
因为自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。这点很好地满足了对称多处理机器需要的锁定服务。在单处理器上,自旋锁仅仅当作一个设置内核抢占的开关。如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核。简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。
死锁:假设有一个或多个内核任务,一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。递归使用一个自旋锁就会出现这种情况。
(二)信号量(semaphore)
在《深入了解Linux内核》第一章就提到过信号量,从本质上讲信号量(semaphore)是让等待着睡眠,如果内核控制路径试图获取内核信号量正在保护的忙资源时,信号量会将其推入等待队列,相应的进程被挂起然,让其睡眠。这时处理器就获得自由时间去执行另外的代码。当持有信号量的进程将信号量释放后,在等待队列中被挂起的一个任务将被唤醒,从而便可以获得这个信号量,只有可以睡眠的函数才能获得内核信号量。
信号量有睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。 如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。如果需要在自旋锁和信号量中作选择,当不受睡眠的限制并且需要长时间加锁时,使用信号量通常来说更加简单一些。但时间加锁自旋锁比较理想。然而理想情况是所有的锁都应该尽可能短的被持有。另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。这意味者信号量不会对影响调度反应时间带来负面影响。
这篇关于由自旋锁引起的思考的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!