当前课程知识点:Linux 内核分析与应用 >  第7章 内核同步 >  7.1 内核同步概述 >  Video

返回《Linux 内核分析与应用》慕课在线视频课程列表

Video在线视频

Video

下一节:Video

返回《Linux 内核分析与应用》慕课在线视频列表

Video课程教案、知识点、字幕

大家好 今天我们来开始讲第七章内核的同步机制

那么首先我们介绍一下为什么要讲内核的同步

如果我们把内核看作不断对各种请求进行响应的服务器 那么

正在CPU上执行的进程

发出中断请求的外部设备等就相当于客户

正如服务器要随时响应客户的请求一样 内核也会随时响应进程 中断 系统调用等的请求

我们之所以这样比喻是为了强调内核中的各个任务呢

并不是严格按着顺序依次执行的 而是相互交错执行的

对所有内核任务而言 内核中的很多数据都是共享资源

这就像高速公路供很多车辆行驶一样

对这些共享资源的访问必须遵循一定的访问规则

否则就可能造成对共享资源的破坏

就如同不遵守交通规则会造成撞车一样

那么并发执行是由什么引起的呢 我们在这里给出四点原因

第一种是中断 中断几乎可以在任何时刻异步的发生

也可能随时打断正在执行的代码
第一种是中断 中断几乎可以在任何时刻异步的发生

也可能随时打断正在执行的代码

第二种是内核的抢占

若内核具有抢占性 内核中的任务就可能会被另一任务抢占

那么第三种呢是睡眠

在内核执行的进程可能会睡眠

这将唤醒调度程序 导致调度一个新的进程执行

那么第四种呢就是对称多处理器

两个或多个处理器可以同时执行代码 那么这个时候呢就可能造成并发

那么问题是呢系统中还有没有其他的并发源

在并发执行的过程中 会产生竞争

那么在这里面 我们介绍一个概念叫竞争的条件

当以下两个条件同时发生的时候呢竞争就发生

第一个是至少有两个可执行上下文“并行执行”

在这里有两种情况一种是真正的并行 比如说两个系统调用在不同的处理器上执行

第二种是其中一个上下文能随意抢占另一个

比如说一个中断抢占系统调用

第二种情况是可执行上下文对共享变量执行“读写”的访问

那么这里也随之给出一个问题

为什么这些竞争条件会导致各种难以调试的错误

竞争条件导致错误 我们这里给出一个例子

比如说 举一个释放资源的例子

在大多数情况下释放资源的函数呢只释放一次资源

然而在图中呢 我们看呢

如果counter的初值为2

当线程A将该变量的值呢

减为1的时候呢B线程呢就抢占了A

那么counter的值呢又被减了一次减为0

这个时候呢则线程B调用释放函数释放资源

然后呢线程A呢恢复执行

因为这个时候呢counter的值为0

那么线程A呢也释放资源 因此就出现什么情况呢

一个资源被释放两次这样的错误发生

那么问题也来了 如何解决这样的问题

这里引出同步中的重要概念临界区

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

多个内核任务并发访问同一个资源通常是不安全的

为了避免对临界区进行并发访问呢

编程者必须保证临界区代码被原子地执行

也就是说 代码在执行期间不可被打断

就如同整个临界区是一个不可分割的指令一样 如图

A进程进入临界区后 B试图进入的时候呢就被阻塞

只有当A离开临界区后呢 B才能进入

那么下面就给出一个问题

在前面释放资源的例子中临界区是什么

那么如何保护临界区呢 下面将给出一个简单的介绍 下一讲给出具体的措施

第一个就是使临界区的操作能够原子地进行

例如 使用原子指令

那么第二种方法是进入临界区后呢就干脆禁止抢占

比如通过禁止中断 禁止下半部处理程序或者线程抢占等等

第三种是串行的访问临界区

比如使用自旋锁 互斥锁只允许一个内核任务访问临界区

下面我们看一下并发执行中 共享变量v的加1操作

多个CPU和内存是通过总线互连的

在任意时刻只能有一个总线主设备 比如说是CPU DMA 控制器呢

访问该从设备 在这个场景中呢从设备呢就是RAM芯片

因此呢来自两个CPU上的读内存操作被串行化执行

分别获得了同样的旧值比如说是0

完成修改以后呢两个CPU都想进行写操作

把修改的值写回到内存

但是 硬件中的限制使得CPU的写回必须是串行化的

因此CPU1首先获得了访问权 进行写回的动作

随后呢CPU2完成写回动作

在这种情况下 CPU1的对内存的修改呢

被CPU2的操作覆盖了 因此执行结果是错误的

本来v两次加1后为2 结果呢v就成为1了

那么这里同样抛出一个问题

在单CPU上 假设一个系统调用和一个中断服务程序并发执行

则对V的加1操作会出现什么情况

在多处理器的SMP系统中 为了提供原子操作呢 不同CPU体系结构提供了不同的技术

例如在X86下 当执行加有LOCK前缀的指令的时候呢

LOCK前缀用于锁定系统总线

使得前面的错误不会发生

那么同样的我们给出一个问题

在ARM平台上采用什么指令

下面我们来介绍一下原子操作 对于那些由多个内核任务进行共享的变量呢

那么对变量的装载 修改 存储呢必须原子地进行 也就是说不能分割

于是呢内核提供了一个特殊的类型叫atomic_t

具体定义为左面这个图

从上面的定义来看出呢 atomic_t实际上就是一个int类型的counter

那么这里同样抛出一个问题

为什么原子类型的定义要把一个整型放在结构体中

在Linux内核中 提供了一组原子操作的API

比如说给一个原子变量V增加i个值

给一个原子变量减一个i的值 获取 或者设定原子变量的值

原子变量加一或者减一操作等等操作呢

都有相应的函数来帮助我们完成

在这里我们对于原子操作给一个例子

在前面的例子里头呢我们说

释放一个资源的时候 首先先对一个变量先减1操作

然后检测它是不是为零

那么这两个操作实际上是不可分割的

所以在原子操作的函数中有一个函数叫atomic_dec_and_test

那么它实际上就是实现原子计数减1并检查的功能

那么这两个操作就可以原子地进行

所以呢我们有了这样的原子操作的函数之后 我们就不会出现

前面一个资源被释放两次这样的错误

那么下面同样跟出一个问题

在多核系统中遇到原子操作的时候 在系统层面上

原子操作还是原子的吗 在核级上还是原子的吗

下面我们来给出共享队列和加锁

当共享资源是一个复杂的数据结构的时候呢

竞争状态往往会使该数据结构遭到破坏

对于这种情况怎么进行处理呢 锁机制是可以避免竞争状态的

正如锁和门一样门后的房间可以想象成一个临界区

在一个给定的时间里呢 房间里只能有一个内核任务存在

当一个任务进入房间以后呢 它会锁住身后的房门

当它结束对共享数据的操作以后呢 就会走出房间打开门锁

如图 A和B试图同时进入房间

当一个任务进去后就必须加锁 出来以后呢打开锁

下面我们讲一下共享队列和加锁

任何要访问队列的代码首先都需要占住相应的锁

这样该锁就能阻止来自其它内核任务的并发访问 比如说任务1试图锁定队列

那么它成功获得锁之后才能访问这个队列

这个时候任务2试图锁定队列 结果可能失败 那么它就需要等待等待再等待

等到什么时候呢等待任务1释放锁 它成功获得锁以后才能访问队列

那么如何确定锁保护的对象呢 这个实际上

是非常难的 那么找出哪些数据需要保护是关键所在

也就是要找出谁是临界区

内核任务的局部数据仅仅被它本身访问 显然呢是不需要保护

如果数据只会被特定的进程访问 也不需要锁

那哪一些需要加锁呢 实际上大多数内核数据结构都是临界区

都是需要加锁的所以在内核代码里面 我们看到有大量的锁机制

那既然有锁 那么实际上就会出现死锁现象

什么是死锁呢 就是所有任务都在相互等待

但它们永远不会释放已经占有的资源

于是任何任务都无法继续执行 这种情况呢就是死锁

那么典型的死锁呢有这么两种

一种呢叫 四路交通堵塞

就像我们交通中发生的堵车现象一样

那么另外一种呢叫自死锁

一个执行任务试图去获得一个自己已经持有的锁的时候呢 就会发生自死锁的现象

那么如何去避免死锁的发生呢

那么如何避免死锁的发生 实际上加锁的顺序是非常关键的

使用嵌套的锁时必须保证以相同的顺序获取锁

这样可以阻止致命拥抱类型的死锁的发生

另外还要防止发生饥饿现象

不要重复请求同一个锁

更重要的是 要注意越复杂的加锁方案越有可能造成死锁

因此呢建议设计应当力求简单

下面我们给出参考资料

同样我们给大家《深入理解Linux内核》的第五章的内容

下面带着思考离开

死锁是一种小概率事件还是大概率事件

如果内核出现死锁 该如何应对

谢谢大家

Linux 内核分析与应用课程列表:

第1章 概述

-1.1 Linux操作系统概述

--1.1 Linux 操作系统概述

-1.2 Linux内核结构以及内核模块编程

--Video

-1.3 Linux内核源码中的双链表结构

--Video

-1.4 源码分析-内核中的哈希表

--Video

-1.5 动手实践-Linux内核模块的插入和删除

--Video

-第1章 概述--章节测验

-第1章导学--引领你进入Linux内核的大门

第2章 内存寻址

-2.1 内存管理之内存寻址

--Video

-2.2 段机制

--Video

-2.3分页机制

--Video

-2.4 动手实践-把虚拟地址转换成物理地址

--Video

-第2章 内存寻址--章节测验

-第二章导学-从零打造自己的操作系统

第3章 进程管理

-3.1 进程概述

--Video

-3.2 Linux进程创建

--Video

-3.3 Linux进程调度

--Video

-3.4 动手实践-打印进程描述符task_struct中的字段

--Video

-3.5工程实践-基于内核模块的负载监控

--Video

-第3章 进程管理--章节测验

-第三章导学-进程背后琳琅满目的宝贝到哪里挖?

第4章 内存管理

-4.1 Linux内存管理机制

--Video

-4.2 进程用户空间管理机制

--Video

-4.3 物理内存分配与回收机制(上)

--Video

-4.4 物理内存分配与回收机制(下)

--Video

-4.5 动手实践-Linux内存映射基础(上)

--Video

-4.6 动手实践-Linux内存映射实现(中)

--Video

-4.7 动手实践-Linux内存映射测试(下)

--Video

-4.8 初学者对内存管理的常见疑惑

--初学者对内存管理的常见疑惑(一)

--初学者对内存管理的常见疑惑(二)

--初学者对内存管理的常见疑惑(三)

-第4章 内存管理--章节测验

第5章 中断

-5.1 中断机制概述

--Video

-5.2 中断处理机制

--Video

-5.3 中断下半部处理机制

--Video

-5.4 时钟中断机制

--Video

-5.5 动手实践-中断上半部的代码分析及应用

--Video

-5.6 动手实践-中断下半部的代码分析及应用

--Video

-第5章 中断--章节测验

第6章 系统调用

-6.1 Linux中的各种API

--Video

-6.2 系统调用机制

--Video

-6.3 动手实践-添加系统调用(系统调用日志收集系统)

--Video

-第6章 系统调用--章节测验

第7章 内核同步

-7.1 内核同步概述

--Video

-7.2 内核同步机制

--Video

-7.3 动手实践-内核多任务并发实例(上)

--Video

-7.4 动手实践-内核多任务并发实例(下)

--Video

-第7章 内核同步--章节测验

第8章 文件系统

-8.1 虚拟文件系统的引入

--Video

-8.2 虚拟文件系统的主要数据结构

--Video

-8.3 文件系统中的各种缓存

--Video

-8.4 页高速缓存机制以及读写

--Video

-8.5 动手实践-编写一个文件系统(上)

--Video

-8.6 动手实践-编写一个文件系统(中)

--Video

-8.7 动手实践-编写一个文件系统(下)

--Video

-第8章 文件系统--章节测验

第9章 设备驱动

-9.1 设备驱动概述

--Video

-9.2 I/O空间管理

--Video

-9.3 设备驱动模型

--Video

-9.4 字符设备驱动程序简介

--Video

-9.5 块设备驱动程序简介

--Video

-9.6 动手实践-编写字符设备驱动程序

--Video

-9.7工程实践-编写块设备驱动的基础(上)

--Video

-9.8 工程实践-块设备驱动程序分析(中)

--Video

-9.9 工程实践-块设备驱动程序实现(下)

--Video

-第9章 设备驱动--章节测验

致谢与说明

-致谢与说明

--Video

直播视频:从Linux内核学习到自主操作系统研发

-从Linux内核学习到自主操作系统研发

附录:实验代码、课件以及相关素材

-各章实验代码

-《Linux内核分析与应用》课件

-《Linux操作系统原理与应用》教材课堂视频

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。