购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.3 互斥机制

在Linux 内核中几种不同的活动可能被相互打断;在多处理器环境下,不同的活动可以并行执行。在活动并行运行过程中,保持系统的稳定性和正确性是必须要解决的问题。

如果内核中的各活动相互独立,没有交叉访问,就不会引起任何问题。一旦几个活动要访问同一数据结构,如果不采取相应的保护机制,即使在单CPU的系统中都可能会引起预想不到的结果。

下面我们用一个实例来说明这个问题,系统中有两个活动A和B ,都要访问同一个链表,将自己的数据skb_a和skb_b加入到链表中。活动A在执行过程中被活动B打断,活动B执行一段时间后,活动A恢复执行。图1-2给出了这两个活动执行结束后的结果。将skb_b加入到链表中的操作结果不正确。

图1-2 并发访问造成的问题

为了避免这类问题的发生,当几个活动需要同时操作同一个数据结构时(这段代码通常称为critical section),这种操作必须是原子操作。所谓原子操作,即整个操作过程不能被打断,操作的几个步骤以不可分的方式执行。

在网络实现代码中,互斥机制使用得非常广泛。在Linux系统的发展过程中,不断创建、优化了几种机制来实现代码执行的互斥。以下几节主要总结了用于网络子系统中的互斥机制,每一种互斥机制适于某些特定的执行环境。

1.3.1 spin lock

这种锁定机制在执行时间一次只能由一个线程持有锁。如果一个线程已持有了锁,另一个执行线程想要获取锁时,就只能循环等待直到前一个线程将锁释放,也即在此期间处理器不做别的处理,一直在循环测试锁的状态。因此spin lock只适用于多处理器系统运行环境,而且通常用于预计锁可以在很短时间内就能获取的情况下。使用spin lock时,要求持有锁的线程不能休眠,否则会造成别的线程因不能获取锁而发生系统死锁的情况。

spin lock又称为忙等锁定机制,它适用于当一段critical section代码部分非常短,执行起来很快的合。这时如果我们采用的不是spin lock锁定机制,而是采用另一种方式,比如,当CPU测试到锁被别的活动持有,就用调度器将当前活动换出CPU,调度别的进程来执行;锁有效后再把该活动调度进来执行,其中调度进程进出CPU所花费的时间可能比处理器忙而等待所用的时间更长,效率更低。因此,对很小的critical section程序段的保护采用spinlocks更好。

在Linux内核中,spin lock由spinlock_t类型的变量实现,它由一个整数的锁变量组成,为了使用spin lock,首先要创建和初始化spinlock_t数据结构,使用spin lock的步骤为:

接下来你可以用内核提供的一系列函数在需要的时候测试、获取或释放锁。获取锁的地方标志着critical section部分的开始,释放锁处标志着critical section部分的结束,这时别的活动又可以来获取锁,操作共享的数据结构。

1.获取锁
(1)spin_lock(spinlock_t *my_spinlock)

试图获取锁my_spinlock,如锁已被别的进程持有,必须等待并不断测试锁的状态直到锁释放。锁被释放后可立刻获取。

(2)spin_lock_irqsave(spinlock_t *my_spinlock, unsigned long flags)

与spin_lock函数的功能类似,它自动屏蔽中断,将CPU的当前状态寄存器值保存到变量flags中。

(3)spin_lock_irq(spinlock_t *my_spinlock)

与spin_lock_irqsave类似,但不保存CPU状态寄存器的值,它假定中断已经屏蔽了。

(4)spin_lock_bh(spinlock_t *my_spinlock)

获取锁,同时阻止bottom half的运行。

2.释放锁
(1)spin_unlock(spinlock_t *my_spinlock)

释放一个获取的锁。

(2)spin_unlock_irqrestore(spinlock_t *my_spinlock)

释放一个spinlock的锁,并置中断允许。

(3)spin_unlock_irq(spinlock_t *my_spinlock)

释放锁,允许中断。

(4)spin_unlock_bh(spinlock_t *my_spinlock)

释放锁,并允许立即处理bottom half。

以上的函数可以用于保护critical section 代码的执行,避免多个活动同时操作共享数据结构带来不可预期的错误。比如,在图1-2中所示的操作可以用以下方式来保护活动A和活动B都要操作的链表。

使用了锁定机制来保护链表后,活动A与活动B向链表中插入数据的操作就不会再出错了。

1.3.2 读-写 spin lock

在使用某个锁时,如果可以把操作明显地划分为只读、读写操作时,使用读-写spin lock会更有效。spin lock与读-写spin lock的区别在于:后者可以允许多个读操作同时持有锁;一次只有一个写操作可以获取锁;当写操作持有锁时,读操作不能获取锁;通常赋予读操作的优先级比写操作的优先级高,当读操作的数量大大超过写操作数量时,这种锁定机制的执行效率更高。

例如,在网络子系统中,所有表示网络设备的数据结构net_device的实例都存放在以dev_base为头指针的链表中。在系统初始化时,当我们探测到一个有效的网络适配器时,就将该网络设备的net_device实例插入到链表中,这时为写操作。在系统运行过程中,我们很少改变net_device结构变量的值,大部分操作是在dev_base链表中查找设备,获取设备属性值,这属于读操作。

对这种数据结构的访问,一定要用锁定机制来保护,但对dev_base链表的读操作在没有写操作的情况下,不需要串行等待执行,就最适合读-写spin lock锁定机制。它允许多个读操作的活动同时运行其critical section部分的代码,但一旦锁被写操作获取后,所有的活动只能等到写操作释放锁后才能访问共享数据。

当我们要向dev_base链表加入一个新的网络设备的net_device 数据结构实例时,我们需要用读-写spin lock的写锁来保护dev_base链表。以下列出的函数可以用于获取和释放读-写spin lock。需要注意的是,我们要按照操作的类型(读/写)来区分锁的状态。同样,读-写锁也有处理中断和bottom half的函数,与上节所说的spin lock一样,在这里就不再重复叙述了。

1.3.3 读-复制-更新(Read-Copy-Update,RCU)

RCU是Linux中提供的最新互斥机制,这种锁定机制在以下特定的条件下执行效率非常高。

● 相对于只读锁的要求,要求读-写锁的次数非常少。

● 持有锁的代码是以原子方式执行,绝不会休眠。

● 被锁保护的数据结构是通过指针访问的。

第一个条件涉及的是执行效率,其他两个条件是RCU工作原理的基础。如果第一个条件不满足,在需要使用锁定机制时,更好的方式就是使用读-写spin lock。当数据需要修改时,写线程获取一个数据的拷贝,修改拷贝,随后将相关的指针改到新版本的数据结构上,当内核确定不再有对旧版本数据的引用时,旧数据就可以释放了。 NG+Inu6d3Pt7rXljc0GhPO5SPg0tKj5B11qUmslL2nheSfOIIpeJBtkunZH19LoK

点击中间区域
呼出菜单
上一章
目录
下一章
×