第九章:内核同步介绍

程序员需要留意保护共享资源,防止共享资源禀赋访问,如果多个执行线程同时访问和操作数据,有可能发生各现场之间相互覆盖共享数据的情况,造成被访问数据处于不一致的状态。

Linux内核是抢占式内核,意味着调度程序可以在任何时刻抢占正在运行的内核代码,重新调度其他的进程执行。

9.1 临界区竞争条件

所谓临界区就是访问和操作共享数据的代码段。

由于多线程操作共享资源的不安全,为避免在临界区中并发访问,编程者必须保证这些代码时原子性的,就是说操作在结束前不可被打断。

所谓竞争条件:如果两个线程有可能处于同一个临界区中同时执行,那么这就是程序包含的一个bug。如果发生了这种情况,我们称之为竞争条件。

避免并发和防止竞争条件称为同步(synchronization)

9.1.1 为什么我们需要保护

以银行取钱为例:

A、B同时读取卡里的金额都是100,A取了90元的同时,B取了10元,可能会发生最后余额显示剩余90元的情况。

为了保证类似的情况不发生,需要在某些操作中增加锁,确保每个事务相对于其他任何事务的操作都是原子性的。

9.1.2单个变量

考虑一个非常简单的共享资源:一个全局整型变量和一个简单的临界区,其中操作仅仅是将整型的值加1:

操作过程如下:

得到当前变量i的值并拷贝到一个寄存器中

将寄存器中的值加1

把i的新值写回到内存中。

如果现在两个线程同时执行并进入这个临界区,如果i的初始值为7,那么所期望的值应该为9,例如下面:

A线程获得i(7),增加i(7->8),写回i(8), 然后B线程获得i(8),增加i(8->9),写回i(9)

但是实际情况可能是如下:

A、B线程获得i(7),A增加i(7->8),同时B增加i(7->8),最后A、B两个线程写回i都是8.

以上是一个最简单的临界区的例子。

对于以上这个例子,我们需要将这些指令变为不可分割的整体来执行就可以了。多数处理器提供了指令来原子地读取变量、增加变量,然后再写会变量。

9.2加锁

当处理的是一个队列的所有请求时,我们可以假定该队列是以链表方式实现的,所有链表中的每个节点都代表一个请求,两个函数一个是向队列中增加数据,一个是从队列中获得数据,如果存在多个线程同时读取或者添加该链表结构,就可能发生数据不一致的问题。

针对以上情况,需要一个方法确保一次有且只有一个线程对数据结构进行操作,或者当另一个线程在对临界区标记时,就禁止其他访问。

前面讲的请求队列,可以使用一个单独的锁进行保护,当一个新请求要加入队列时,线程会首先锁住队列,然后就可以安全的将请求加入到队列中,结束操作后再释放该锁。

一个时刻只能有一个线程持有锁,所以在一个时刻只有一个线程可以操作队列。

如果一个线程正在操作队列,另一个线程出现了,那么第二个线程必须等待第一个线程释放锁,他才可以继续进行。

锁有多种多样的形式,而且枷锁的粒度范围也不同,LInux自身实现了集中不同的锁机制。

锁是采用原子操作实现的,而原子操作不存在竞争。

9.2.1造成并发执行的原因

用户空间之所以需要同步,是因为用户程序会被调度程序抢占和重新调度。

由于用户进程可以在任何时刻被抢占,而调度程序完全可能选择另一个优先级更高的进程到处理器上执行,所以就会使得一个程序正处于临界区,被非自愿的抢占了,如果新调度的进程随后进入同一临界区,前后两个进程互相之间就会产生竞争,

内核中类似可能造成并发执行的原因,它们是:

中断:中断几乎可以在任何时刻异步发生。

软中断和tasklet:内核能在任何时刻唤醒或者调度软中断和tasklet。

内核抢占:因为内核具有抢占行,所以内核中的任务可能会被另一任务抢占。

睡眠及与用户空的同步:在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行。

9.2.2了解要保护些什么

找出哪些数据需要保护是关键所在。

到底什么数据需要加锁那?大多数内核数据结构都需要加锁!,有一条很好的经验可以帮助我们判断:如果有其他执行线程可以访问这些数据,那么久给这些数据加上某种形式的锁:

记住:要给数据而不是代码加锁。

9.3死锁

死锁产生的条件:要一个或者多个执行线程和一个或者多个资源,每个线程都在登台其中一个资源,但是所有的资源都已经被锁占用,所有的线程都互相等待其他线程的已经被锁定的资源,但他们永远不会释放已占用的资源。

最简单的死锁是自死锁:如果一个执行线程试图获得一个自己已经持有的锁,它将不得不等待锁释放,但因为它正在忙着等待这个锁,所以自己永远不会有机会释放锁,最终的结果如下:

获得锁;

再次试图获得锁;

等待锁重新可用;

预防死锁的一些简单规则:

按顺序加锁:使用嵌套锁时必须按顺序获得锁,这样可以阻止致命拥抱类型的死锁。

防止发生饥饿:不要一直等待获得一个锁;

不要重复请求同一个锁;

设计应力求简单:越复杂的加锁方案越有可能造成死锁;

9.4争用和扩展性

锁的争用:指当锁正在被占用时,其他线程试图获得该锁。

一个锁处于高争用状态:表示多个其他线程在等待获得该锁。

锁的作用:可以使程序按照串行的方式对资源进行访问,所以使用锁会降低系统的性能。

高度争用的锁会成为系统的瓶颈,严重会降低系统性能。

扩展性:是对系统可扩展性程度的一个度量;

加锁的粗细粒度会影响系统的性能,例如对整个链表加锁和对链表中每个节点加锁,这两加锁方式,前一种不如后一种性能高,并且可以降低竞争的发生,提供操作性能。

当加锁严重时,会降低可扩张性,而锁争用不明显时,加锁过细会加大系统开销,带来浪费。

注意:在设计初期加锁方案应该力求简单,仅当需要时再进一步细化加锁方案,精髓在于力求简单。

原文地址:https://www.cnblogs.com/use-D/p/10556094.html

时间: 2024-08-05 16:28:52

第九章:内核同步介绍的相关文章

【读书笔记】《Linux内核设计与实现》内核同步介绍&内核同步方法

简要做个笔记,以备忘. 需同步的原因是,我们并发访问了共享资源.我们将访问或操作共享资源的代码段称"临界区",如果两个执行线程处于同一临界区中同时执行,称"竞争条件".这里术语执行线程指任何正在执行的代码实例,如一个在内核执行的进程.一个中断处理程序或一个内核线程. 举个简单例子,i++操作.该操作可以转换为下面的机器指令序列: 1.得到当前变量i的值,并保存到一个寄存器. 2.将寄存器的值加1. 3.将i的新值写回到内存中. 当两个线程同时进入这个临界区,若i初值

9内核同步介绍

一.临界区与竞争条件 临界区就是访问和操作共享数据的代码段. 如果两个执行线程有可能处于同一临界区中同时执行,那么我们就称它们为竞争条件(race conditions) 避免并发和防止竞争条件称为同步(synchronization) 二.加锁 2.1 锁的介绍 我们需要一种方法确保一次有且只有一个线程对数据结构进行操作,或者当一个线程在对临界区标记时,就禁止其他访问.线程持有锁,而锁保护了数据.如请求队列的例子,可以使用一个单独的锁保护队列,每当有新的请求到来时,线程会首先占住锁,然后就可以

《Linux内核设计与实现》读书笔记(九)- 内核同步介绍

存在共享资源(共享一个文件,一块内存等等)的时候,为了防止并发访问时共享资源的数据不一致,引入了同步机制. 主要内容: 同步的概念 同步的方法-加锁 死锁 锁的粒度 1. 同步的概念 了解同步之前,先了解另外2个概念: 临界区   - 也称为临界段,就是访问和操作共享数据的代码段. 竞争条件 - 2个或2个以上线程在临界区里同时执行的时候,就构成了竞争条件. 所谓同步,其实防止在临界区中形成竞争条件. 如果临界区里是原子操作(即整个操作完成前不会被打断),那么自然就不会出竞争条件. 但在实际应用

APUE学习笔记:第九章 进程关系

9.1 引言 本章将更详尽地说明进程组以及POSIX.1引入的会话的概念.还将介绍登陆shell(登录时所调用的)和所有从登陆shell启动的进程之间的关系. 9.1 终端登陆 系统管理员创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名传递给getty程序的参数.当系统自举时,内核创建进程ID为1的进程,依旧是init进程.init进程使系统进入多用户状态.init进程读文件/etc/ttys,对每一个允许登陆的终端设备,init调用一次fork,所生成的子进程则

Android群英传笔记——第九章:Android系统信息和安全机制

Android群英传笔记--第九章:Android系统信息和安全机制 本书也正式的进入尾声了,在android的世界了,不同的软件,硬件信息就像一个国家的经济水平,军事水平,不同的配置参数,代表着一个android帝国的强弱,所以厂商喜欢打配置战,本节就要是讲 Android系统信息的获取 PackageManager的使用 ActivityManager的使用 Android安全机制 一. Android系统信息的获取 由于android手机的开源性,手机的配置各种各样,那些优化大师之类的东西

Linux内核同步机制--转发自蜗窝科技

Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个位于memory中的变量的值到寄存器中 2.修改该变量的值(也就是修改寄存器中的值) 3.将寄存器中的数值写回memory中的变量值 如果这个操作序列是串行化的操作(在一个thread中串行执行),那么一切OK,然而,世界总是不能如你所愿.在多CPU体系结构中,运行在两个CPU上的两个内核控制路径同

第九章:逻辑卷LVM

第九章:逻辑卷LVM 现实生产环境中,经常会遇到磁盘空间不够用的情况,无论规划的多么好,最终还是会遇到这种情况.所以说,磁盘分区没有一个标准的格式,只能根据自己的服务器应用来适当进行划分.那么遇到这样的问题我们如何解决呢?本章我们就来介绍解决之法-逻辑卷管理(LVM). LVM(Logical VolumeManager),通过这种技术可以随意扩大或缩小磁盘或分区的容量.LVM的实现是把磁盘.分区或RAID设备通过软件组合成一块独立的VG,然后将这个VG再进行划分LV,这时候的LV就相当于一个分

[深入理解Android卷一全文-第九章]深入理解Vold和Rild

由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. 第9章  深入理解Vold和Rild 本章主要内容 ·  介绍Vold. ·  介绍Rild. 本章涉及的源代码文件名称及位置 下面是本章分析的源码文件名及其位置. ·  Main.cpp system/vold/Main.cpp ·  NetlinkManager.cpp system/vold/Netli

第九章感想

第九章章介绍了——HAL(硬件抽象层),建立在linux驱动之上的一套程序库.它并不属于linux内核,而是属于linux内核层之上的应用层.Google为Android加入HAL主要有如下的目的.Google为了满足这些不想开源的linux驱动作者的要求,在android层次结构中的运行库层增加了一个HAL,从而统一硬件的调用接口,解决了GPL版权问题并且针对一些特殊的要求.统一硬件的调用接口.由于HAL 有标准的调用接口,所以可以利用HAL屏蔽Linux 驱动复杂.不统一的接口.解决了GPL