您的位置: 首页 - 知识教程 - Linux内核设计与实现——内核同步

Linux内核设计与实现——内核同步

来源:知识教程 / 时间: 2024-12-13

读写信号量和信号量之间的关系 与 读写自旋锁和普通自旋锁之间的关系 差不多。

<p>
	读写信号量都是二值信号量,即计数值最大为1,增加读者时,计数器不变,增加写者,计数器才减一。也就是说读写信号量保护的临界区,最多只有一个写者,但可以有多个读者。</p>
<p>
	所有读-写锁的睡眠都不会被信号打断,所以它只有一个版本的down操作。</p>
<p>
	了解何时使用自旋锁和信号量对编写优良代码很重要,但是多数情况下,并不需要太多考虑,因为在中断上下文只能使用自旋锁,而在任务睡眠时只能使用信号量。</p>

<h2>
	完成变量</h2>
<p>
	如果在内核中一个任务需要发出信号通知另一任务发生了某个特定事件,利用完成变量(completion variable)是使两个任务得以同步的简单方法。如果一个任务要执行一些工作时,另一个任务就会在完成变量上等待。当这个任务完成工作后,会使用完成变量去唤醒在等待的任务。例如,当子进程执行或者退出时,vfork()系统调用使用完成变量唤醒父进程。</p>
<h2>
	Seq锁(顺序锁)</h2>
<p>
	这种锁提供了一种很简单的机制,用于读写共享数据。实现这种锁主要依靠一个序列计数器。当有疑义的数据被写入时,会得到一个锁,并且序列值会增加。在读取数据之前和之后,序列号都被读取。如果读取的序列号值相同,说明在读操作进行的过程中没有被写操作打断过。此外,如果读取的值是偶数,那么就表明写操作没有发生(要明白因为锁的初值是0,所以写锁会使值成奇数,释放的时候变成偶数)。</p>
<p>
	在多个读者和少数写者共享一把锁的时候,seq锁有助于提供一种非常轻量级和具有可扩展性的外观。但是 seq 锁对写者更有利,只要没有其他写者,写锁总是能够被成功获得。挂起的写者会不断地使得读操作循环(前一个例子),直到不再有任何写者持有锁为止。</p>
<h2>
	禁止抢占</h2>
<p>
	由于内核是抢占性的,内核中的进程在任何时刻都可能停下来以便另一个具有更高优先权的进程运行。这意味着一个任务与被抢占的任务可能会在同一个临界区内运行。为了避免这种情况,内核抢占代码使用自旋锁作(可以防止多处理器机器上的真并发和内核抢占)为非抢占区域的标记。如果一个自旋锁被持有,内核便不能进行抢占。</p>
<p>
	实际中,某些情况(不需要仿真多处理器机器上的真并发,但需要防止内核抢占)并不需要自旋锁,但是仍然需要关闭内核抢占。为了解决这个问题,可以通过 preempt_disable 禁止内核抢占。这是一个可以嵌套调用的函数,可以调用任意次。每次调用都必须有一个相应的 preempt_enable 调用。当最后一次 preempt_enable 被调用后,内核抢占才重新占用。</p>
<h2>
	顺序和屏障</h2>
<p>
	对于一段代码,编译器或者处理器在编译和执行时可能会对执行顺序进行一些优化,从而使得代码的执行顺序和我们写的代码有些区别。</p>
<p>
	一般情况下,这没有什么问题,但是在并发条件下,可能会出现取得的值与预期不一致的情况,比如下面的代码:</p>
<pre>/* 
  • 线程A和线程B共享的变量 a和b

  • 初始值 a=1, b=2 / int a = 1, b = 2; /

  • 假设线程A 中对 a和b的操作 / void Thread_A() { a = 5; b = 4; } /

  • 假设线程B 中对 a和b的操作 */ void Thread_B() { if (b == 4)

    printf(&#34;a = %d
    

    “, a); }

    由于编译器或者处理器的优化,线程A中的赋值顺序可能是b先赋值后,a才被赋值。</p>
    

    所以如果线程A中 b=4; 执行完,a=5; 还没有执行的时候,线程B开始执行,那么线程B打印的是a的初始值1。

    这就与我们预期的不一致了,我们预期的是a在b之前赋值,所以线程B要么不打印内容,如果打印的话,a的值应该是5。

    在某些并发情况下,为了保证代码的执行顺序,引入了一系列屏障方法来阻止编译器和处理器的优化。

    为了使得上面的小例子能正确执行,用上表中的函数修改线程A的函数即可:</p>
    

    /*

  • 假设线程A 中对 a和b的操作 / void Thread_A() { a = 5; mb(); /

    • mb()保证在对b进行载入和存储值(值就是4)的操作之前
    • mb()代码之前的所有载入和存储值的操作全部完成(即 a = 5;已经完成)
    • 只要保证a的赋值在b的赋值之前进行,那么线程B的执行结果就和预期一样 */ b = 4; }
返回顶部