2.5 内核中的并发
随着多核笔记本电脑时代的到来,对称多处理器(SMP)的使用不再被限于高科技用户。SMP和内核抢占是多线程执行的两种场景。多个线程能够同时操作共享的内核数据结构,因此,对这些数据结构的访问必须被串行化。
接下来,我们会讨论并发访问情况下保护共享内核资源的基本概念。我们以一个简单的例子开始,并逐步引入中断、内核抢占和SMP等复杂概念。
2.5.1 自旋锁和互斥体
访问共享资源的代码区域称作临界区。自旋锁(spinlock)和互斥体(mutex,mutual exclusion的缩写)是保护内核临界区的两种基本机制。我们逐个分析。
自旋锁可以确保在同时只有一个线程进入临界区。其他想进入临界区的线程必须不停地原地打转,直到第1个线程释放自旋锁。注意:这里所说的线程不是内核线程,而是执行的线程。
下面的例子演示了自旋锁的基本用法:
spinlock_t mylock = SPIN_LOCK_UNLOCKED; /* Initialize */
/* Acquire the spinlock. This is inexpensive if there
* is no on
* contention, spinlock() has to busy-wait.
*/
spin_lock( & mylock);
/* ... Critical Section co
spin_unlock( &mylock); /* Release the lock */
与自旋锁不同的是,互斥体在进入一个被占用的临界区之前不会原地打转,而是使当前线程进入睡眠状态。如果要等待的时间较长,互斥体比自旋锁更合适,因为 自旋锁会消耗CPU资源。在使用互斥体的场合,多于2次进程切换时间都可被认为是长时间,因此一个互斥体会引起本线程睡眠,而当其被唤醒时,它需要被切换 回来。
因此,在很多情况下,决定使用自旋锁还是互斥体相对来说很容易:
(1) 如果临界区需要睡眠,只能使用互斥体,因为在获得自旋锁后进行调度、抢占以及在等待队列上睡眠都是非法的;
(2) 由于互斥体会在面临竞争的情况下将当前线程置于睡眠状态,因此,在中断处理函数中,只能使用自旋锁。(第4章将介绍更多的关于中断上下文的限制。)
下面的例子演示了互斥体使用的基本方法:
/* Statically declare a mutex. To dynamically
create a mutex, use mutex_init() */
static DEFINE_MUTEX(mymutex);
/* Acquire the mutex. This is inexpensive if there
* is no on
* contention, mutex_lock() puts the calling thread to sleep.
*/
mutex_lock( & mymutex);
/* ... Critical Section co
mutex_unlock( &mymutex); /* Release the mutex */
为了论证并发保护的用法,我们首先从一个仅存在于进程上下文的临界区开始,并以下面的顺序逐步增加复杂性:
(1) 非抢占内核,单CPU情况下存在于进程上下文的临界区;
(2) 非抢占内核,单CPU情况下存在于进程和中断上下文的临界区;
(3) 可抢占内核,单CPU情况下存在于进程和中断上下文的临界区;
(4) 可抢占内核,SMP情况下存在于进程和中断上下文的临界区。