申小林 发表于 2025-1-17 10:52

《Linux内核深度解析》-05-Linux内核互斥技术说明

<div class='showpostmsg'><p>在 Linux 内核的复杂环境中,多进程并发执行是常态 。当多个进程同时访问共享资源时,就如同多个线程同时对一个共有的变量进行加 1 操作,由于并行运行,可能导致本该被加两次的变量只被加了一次。这就是所谓的竞态条件,会造成数据的不一致和错误 。</p>

<p>为了避免这种情况,就需要引入互斥技术,保证在同一时刻,只有一个进程能够访问共享资源,从而确保数据的完整性和一致性。互斥技术就像是一把锁,当一个进程获取到这把锁时,其他进程必须等待,直到锁被释放,才能有机会访问共享资源。</p>

<div style="text-align: center;"></div>

<div style="text-align: center;">
<h3 style="text-align: left;">(一)并发与竞态</h3>

<p style="text-align: left;">&nbsp; &nbsp; &nbsp; &nbsp;在 Linux 内核的运行环境中,并发是指多个执行单元(如进程、线程或中断处理程序)在同一时间段内同时执行 。想象一下,在一个繁忙的火车站,多个乘客同时在售票窗口买票,这就类似于并发的场景。而竞态则是当这些并发执行的单元同时访问和修改共享资源时,由于它们的执行顺序不确定,导致最终结果出现不可预测的情况。例如,多个进程同时对一个共享的计数器进行加 1 操作,如果没有适当的同步机制,可能会出现计数器的值增加的次数少于预期的情况。</p>

<h3 style="text-align: left;">(二)临界区</h3>

<p style="text-align: left;">&nbsp; &nbsp; &nbsp; 临界区是指访问共享资源的代码段,这段代码在同一时间只能被一个执行单元执行,以避免竞态条件的发生。继续以上述火车站为例,售票窗口的工作人员处理每个乘客的购票请求时,这个处理过程就相当于临界区,同一时间只能为一位乘客服务,否则就会出现混乱。临界区的存在是为了确保共享资源的一致性,它需要被严格保护,防止多个执行单元同时进入。</p>
</div>

<div>
<div style="text-align: center;"></div>

<div style="text-align: center;">
<h3 style="text-align: left;">(一)中断屏蔽</h3>
</div>

<div style="text-align: center;">
<p style="text-align: left;">&nbsp; &nbsp; &nbsp; 中断屏蔽的原理是在进入临界区之前,通过特定的指令将 CPU 的中断响应功能暂时关闭 ,使得在临界区代码执行期间,不会被外部中断所打断。在 Linux 内核中,提供了一系列用于中断屏蔽的函数,如local_irq_disable()用于禁止本地中断,local_irq_enable()用于使能本地中断 。以一个简单的示例来说明,如果有一个共享资源是一个全局变量shared_variable,当一个进程要对其进行修改时,为了避免在修改过程中被中断干扰,可以这样使用中断屏蔽:</p>

<p style="text-align: left;">&nbsp;</p>

<table style=" border-collapse:collapse; border:none">
        <tbody>
                <tr>
                        <td style="border-bottom:1px solid black; background-color:#f5f6f7; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p style="text-align: left;">local_irq_disable();</p>

                        <p style="text-align: left;">// 访问和修改共享资源</p>

                        <p style="text-align: left;">shared_variable = shared_variable + 1;</p>

                        <p style="text-align: left;">local_irq_enable();</p>
                        </td>
                </tr>
        </tbody>
</table>

<p style="text-align: left;">中断屏蔽的优点在于它能够非常有效地保证在临界区内的代码执行不会被中断,从而避免了由于中断导致的竞态条件。由于中断与进程调度紧密相关,屏蔽中断也限制了系统进程的并发,进一步降低了竞态发生的可能性 。然而,它的缺点也很明显。Linux 内核中许多重要的操作,如异步 I/O 等都依赖于中断,如果长时间屏蔽中断,会导致这些操作无法正常进行,可能会对整个系统的性能和稳定性造成严重影响。中断屏蔽只能解决单 CPU 内部的竞态问题,对于多 CPU 系统(SMP),由于其他 CPU 的中断仍然可能发生,所以无法解决 SMP 多 CPU 引发的竞态 。因此,中断屏蔽通常适用于临界区代码非常简短的场景,并且在单 CPU 环境下使用更为合适。</p>
</div>

<div>&nbsp;</div>

<div>
<h3>(二)原子操作</h3>
</div>

<div>
<p>&nbsp; &nbsp; &nbsp; 原子操作指的是在执行过程中不会被中断的操作,要么全部执行成功,要么全部不执行,具有不可分割性 。在单处理器系统中,由于不存在多个处理器同时访问共享资源的情况,原子操作相对容易实现。一些简单的指令,如对单个变量的赋值操作,本身就是原子的,因为在单处理器上,指令是顺序执行的,不会被其他处理器干扰 。而在多处理器系统中,情况就变得复杂起来。不同的处理器体系结构采用了不同的方法来实现原子操作。在 x86 架构中,通过在指令前加上 &ldquo;LOCK&rdquo; 前缀来实现原子的读 - 修改 - 写操作。当一个处理器执行带有 &ldquo;LOCK&rdquo; 前缀的指令时,它会锁住总线,使得其他处理器在该指令执行期间无法访问内存,从而保证了操作的原子性 。在 ARM 架构中,使用 &ldquo;load exclusive&rdquo; 和 &ldquo;store exclusive&rdquo; 指令对来实现原子操作。&ldquo;load exclusive&rdquo; 指令会加载内存值,并标记该内存位置为独占访问,&ldquo;store exclusive&rdquo; 指令会尝试存储值,并检查该内存位置是否仍处于独占状态,如果是则存储成功,否则失败。这种机制确保了在多处理器环境下,对共享资源的操作能够以原子的方式进行。</p>

<h3>(三)自旋锁</h3>

<p>&nbsp; &nbsp; &nbsp; 自旋锁是一种用于实现多处理器环境下互斥访问的机制 。其工作原理是,当一个进程试图获取自旋锁时,如果锁当前处于可用状态(即未被其他进程持有),则该进程立即获得锁并继续执行;如果锁已被其他进程持有,那么该进程会在原地不断循环检查锁的状态,直到锁被释放 。自旋锁的结构体在 Linux 内核中定义为spinlock_t,相关的操作函数有spin_lock()用于获取自旋锁,spin_unlock()用于释放自旋锁 。例如:</p>

<p>&nbsp;</p>

<table style=" border-collapse:collapse; border:none">
        <tbody>
                <tr>
                        <td style="border-bottom:1px solid black; background-color:#f5f6f7; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p>spinlock_t lock;</p>

                        <p>spin_lock_init(&amp;lock);</p>

                        <p>// 尝试获取自旋锁</p>

                        <p>spin_lock(&amp;lock);</p>

                        <p>// 访问临界区</p>

                        <p>// 释放自旋锁</p>

                        <p>spin_unlock(&amp;lock);</p>
                        </td>
                </tr>
        </tbody>
</table>

<p>自旋锁适用于临界区执行时间较短的场景,因为在等待锁的过程中,进程不会睡眠,而是一直占用 CPU 资源进行自旋,所以如果临界区执行时间过长,会浪费大量的 CPU 资源 。当一个处理器长时间持有自旋锁,而其他多个处理器在不断自旋等待时,会导致系统的整体性能下降。</p>

<h3 >(四)信号量</h3>

<p>&nbsp; &nbsp; &nbsp; 信号量是一个整型变量,它通过一个计数器来控制对共享资源的访问 。信号量分为二值信号量和计数信号量,二值信号量相当于一把锁,只有 0 和 1 两种状态,用于实现互斥访问;计数信号量则可以有多个值,用于控制对多个相同资源的访问 。当一个进程试图获取信号量时,如果信号量的值大于 0,则将其值减 1 并成功获取;如果信号量的值为 0,则该进程会被阻塞,放入等待队列中,直到有其他进程释放信号量 。在 Linux 内核中,信号量的结构体为struct semaphore,相关操作函数有down()用于获取信号量,up()用于释放信号量 。比如:</p>

<p>&nbsp;</p>

<table style=" border-collapse:collapse; border:none">
        <tbody>
                <tr>
                        <td style="border-bottom:1px solid black; background-color:#f5f6f7; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p>struct semaphore sem;</p>

                        <p>sema_init(&amp;sem, 1);</p>

                        <p>// 获取信号量</p>

                        <p>down(&amp;sem);</p>

                        <p>// 访问临界区</p>

                        <p>// 释放信号量</p>

                        <p>up(&amp;sem);</p>
                        </td>
                </tr>
        </tbody>
</table>

<p>&nbsp; &nbsp; &nbsp; 信号量与自旋锁的主要区别在于,当无法获取信号量时,进程会被阻塞并进入睡眠状态,让出 CPU 资源;而自旋锁在无法获取锁时,进程会自旋等待,一直占用 CPU 。因此,信号量适用于临界区执行时间较长的场景,这样可以避免自旋等待浪费 CPU 资源。</p>

<p>互斥锁是一种用于实现进程或线程间互斥访问的同步机制,它的核心作用是确保在同一时刻,只有一个进程或线程能够进入临界区 。与信号量相比,互斥锁更为纯粹地实现了互斥功能,信号量可以通过设置初始值来控制多个资源的访问,而互斥锁就像一把严格的 &ldquo;独占锁&rdquo;,只允许一个进程进入临界区 。例如,在一个多线程的数据库访问程序中,多个线程可能同时请求对数据库进行写操作,如果不加以控制,就会导致数据的混乱。使用互斥锁,当一个线程获取到锁后,其他线程必须等待,直到该线程完成数据库写操作并释放锁,这样就能保证数据库数据的一致性。</p>

<p>&nbsp; &nbsp; &nbsp; 在 Linux 内核中,互斥锁的主要操作函数包括用于获取互斥锁的mutex_lock()和用于释放互斥锁的mutex_unlock() 。mutex_lock()函数用于获取互斥锁,如果锁当前未被持有,调用该函数的进程将立即获得锁并继续执行;如果锁已被其他进程持有,那么调用该函数的进程会进入睡眠状态,直到锁被释放 。mutex_unlock()函数则用于释放互斥锁,当一个进程完成对临界区的访问后,必须调用该函数来释放锁,以便其他进程有机会获取锁进入临界区 。在使用互斥锁的操作函数时,需要注意以下几点:一定要确保在获取锁后,在合适的时机释放锁,否则会导致死锁。获取锁的时间应尽可能短,以提高系统的并发性能。例如,在对一个共享的链表进行操作时,在获取互斥锁后,应尽快完成链表的插入、删除等操作,然后释放锁,避免其他进程长时间等待。</p>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;Linux 内核的互斥技术是确保系统在多进程并发环境下稳定运行的关键 。从基本的中断屏蔽到复杂的自旋锁、信号量以及互斥锁,每一种技术都有其独特的应用场景和优缺点 。中断屏蔽简单直接,但长时间使用会影响系统性能;原子操作为基本数据操作提供了原子性保障;自旋锁适用于短时间临界区,而信号量和互斥锁则在更广泛的场景中发挥作用 。</p>

<p>随着技术的不断发展,Linux 内核互斥技术也在持续演进 。未来,我们可以期待更高效、更智能的互斥机制出现,以满足不断增长的系统性能需求 。希望本文能为你深入理解 Linux 内核互斥技术提供有益的帮助,也鼓励你在实际的编程实践中不断探索和应用这些技术,提升自己的技术能力 。</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>
</div>

<p>&nbsp;</p>
</div>

<p>&nbsp;</p>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

秦天qintian0303 发表于 2025-1-17 13:53

<p>采用互斥技术是不是就可以确定内容使用的唯一性?&nbsp;</p>

申小林 发表于 2025-1-17 14:28

秦天qintian0303 发表于 2025-1-17 13:53
采用互斥技术是不是就可以确定内容使用的唯一性?&nbsp;

<p>我的理解是操作的唯一性,内容应该也是唯一的,只是有可能内存部分没更新导致不同步。</p>

freebsder 发表于 2025-1-17 16:31

<p>一致性是个大问题,cpu核之间也有一致性机制,只是更底层,一般可能用不到。</p>
页: [1]
查看完整版本: 《Linux内核深度解析》-05-Linux内核互斥技术说明