文章目录
- ⭐前言
- 一、浅谈OS的各个管理模块对应的硬件资源
- 二、从OS的各个管理模块浅谈进程管理
- 2.1 什么是进程?
- 2.2 我知道进程是啥啦,那么OS怎么知道进程的呢?
- 三、OS是如何进行进程管理?
- 3.1 进程状态转换
- 3.1.1 创建态
- 3.1.2 运行态
- 3.1.3 就绪态
- 3.1.4 阻塞态
- 3.1.5 结束态
- 3.1.6 进程状态迁移图
- 3.2 进程调度
- 3.2.1 FCFS调度算法
- 3.2.2 SJF调度算法
- 3.2.3 RR调度算法
- 3.2.4 优先级调度算法
- 3.2.5 多级反馈队列调度算法
- 3.3 进程间通信
- 3.3.1 匿名管道
- 3.3.2 有名管道
- 3.3.3 消息队列
- 3.3.4 信号量机制
- 3.3.5 共享内存
- 🎉总结
⭐前言
在日常生活中使用计算机时,大家会不会出现这样一种好奇,为什么我们打开很多应用或者在玩游戏的过程中,会听到主板上中央处理器(Central Processing Unit,CPU)的散热装置轰轰轰的工作声音,而打开一少部分的应用时,CPU的这个散热装置就几乎没有声音,不难想到的是,CPU散热装置声音越大,代表着我们的CPU正在进行着越高强度的工作。那么我们打开这么多的应用(进程),硬件是怎么知道的呢?这一切的功劳都归于我们的操作系统(Operating System,OS)。对于OS,我们可以简单的理解为是我们使用的上层应用与底层硬件资源交互的媒介,它为用户提供了使用硬件资源的接口(Windows11将这个接口进行了图形化,使得我们可以方便的使用硬件),它也可以通过识别用户对接口的使用,进行硬件资源的调度(分配)。
一、浅谈OS的各个管理模块对应的硬件资源
通过前言部分,我们从最宏观的角度知道了什么是OS,对于科班的小伙伴,相信大家在学习OS理论时,大部分的教学路径基本是进程管理,内存管理,文件管理,输入输出(Input Output,IO)设备管理。笔者认为,这样安排的理论教学是有原因的,从硬件的角度思考,它可以让学生结合OS和硬件资源,对计算机体系结构有一个很好的入门。
进程管理,它结合的硬件资源是我们的最核心部件CPU,当我们切换OS的上层应用时(比如用微信聊完天后,打开我们的游戏),OS就会触发进程管理,将微信这个进程切换下去,让我们的游戏进程占用CPU。
内存管理,它结合的硬件资源是我们常说的内存条,有编程的小伙伴知道,我们写的代码(也叫程序),是各种格式的文本文件,它经过预处理 ⇒ 链接 ⇒ 编译 ⇒ 汇编之后,会生成可执行文件,这些文件都是存储在磁盘中的,我们要运行这个可执行文件(比如点击桌面上的微信可执行程序快捷键),需要将它拿到内存条上来。那么将可执行程序拿到内存条上来时,内存管理就要考虑如何组织这些程序存放到内存中,比如分块、分页(块和页可以理解为分离程序的基本单位)或者连续。
文件管理,它结合的硬件资源是我们常说的磁盘(固态硬盘,机械硬盘),很好理解,文件管理负责的是将我们需要存放在计算机里面的东西进行组织与管理,和内存管理很类似,文件管理对一个文件进行组织,宏观上的两种方式是分块或者连续。此外,大家在学习文件管理或者看文件属性的时候,大概率接触过文件系统(File System),文件系统可以简单的理解为文件管理的一种规范,不同OS的文件管理规范是不一样的。
IO设备管理,它结合的硬件资源是我们常说的IO设备,这个好理解,常见的输入设备比如我们的键盘、鼠标和麦克风等,常见的输出设备比如我们的显卡、网卡和打印机等。IO设备管理主要针对的是IO设备硬件资源,它结合对应的驱动程序,完成计算机内部的信号与IO设备的信号交互,有效地组合和管理IO设备的分配,正是因为有了IO设备,我们的计算机有了强大计算能力的同时,还衍生出了一系列其他的功能(办公、影音和娱乐等)。
二、从OS的各个管理模块浅谈进程管理
本章内容主要从进程的角度,结合CPU硬件资源,来探讨进程是干什么的,OS又是从哪些方面来实现进程管理的。
2.1 什么是进程?
相信大家对进程一点都不陌生,当我们打开Windows OS的任务管理器,如下图所示,就能在进程页面看到很多用户进程和系统进程,并且我们还能发现,它是动态占用CPU和内存等硬件资源的,所以,这里得到进程和程序(我们写的代码)的最大区别,也是进程的最主要特征,它是动态的、可以发生状态转换的、有生命周期的。我们也可以简单的理解为进程是程序 + 数据的动态运行。
2.2 我知道进程是啥啦,那么OS怎么知道进程的呢?
作为聪明的人类,我们可以很容易的对进程的概念进行定义和理解,但是,OS把可执行文件从磁盘装入到内存条中后,又该如何将其转换为进程呢?其实,OS系统对于装入在内存条中的可执行文件,会为其创建一个叫做进程控制块(Process Control Block,PCB) 的数据结构,如下图所示,是xv6操作系统源码的PCB数据结构。OS为一个可执行文件创建了PCB,就能够感知进程的存在了,并且通过PCB控制着进程的运行状态。
三、OS是如何进行进程管理?
OS知道了内存条中装载着的进程,那么在我们使用OS上层应用的时候,OS是如何对进程进行管理的呢?笔者认为,从入门和宏观的角度来看,OS对进程进行管理,可以分为三大部分,第一部分为进程的状态转换,第二部分为进程调度,第三部分为进程间通信,接下来我们对这三部分结合案例进行探讨。
3.1 进程状态转换
要知道进程状态是如何转换的,我们首先应该了解一下,什么是进程的状态,进程一共有几种状态。进程状态,简单的理解就是描述进程在OS运行过程中,是以什么样的状态存在的,OS通过PCB感知进程的存在,并且以此控制进程的状态。进程的状态有5种(一些资料在加入了线程的理解下,会认为进程有7种状态,对于了解进程入门,我们只需要知道进程的5种状态就行了),分别是创建态、运行态、就绪态、阻塞态和结束态。
3.1.1 创建态
创建态是每一个进程都必须经历的状态,只有经历了创建态,OS才能感知到进程的存在,以我们点击VS2019为例,此时VS2019可执行文件被加载进入内存条,OS为该可执行文件创建一个空白的PCB,并且向PCB种填入必要的控制和管理进程的信息,然后为该进程分配运行所需的硬件资源(如内存条、文件索引节点等),至此,进程的创建就完成了。
在进程创建态最后一步,也是最容易忽视的一步是,进程创建完毕后,会从创建态转换为就绪态,插入就绪队列(一种存储着就绪状态PCB的数据结构)中,等待进程调度。
3.1.2 运行态
运行态,顾名思义,就是运行中的进程,以我正在QQ音乐中听音乐为例,此时QQ音乐进程就处于运行态,运行状态的进程占用CPU硬件资源,CPU 执行对应的二进制指令。试想一下,OS中存在着大量的进程,如果我们的QQ音乐进程一直占用所有的CPU资源,那些维持OS正常运行的系统进程就得不到CPU资源,这样OS不就崩溃了?其实,OS对于运行态的进程,是有一直在切换的(比如我们的QQ音乐进程就不可能一直在运行),只是CPU工作频率达到了GHz(执行一条指令只需 1 0 − 9 s 10^{-9}s 10−9s),人类对OS的进程切换是感觉不出来的。
3.1.3 就绪态
就绪态也很好理解,就是对应的PCB存储在就绪队列的进程,这些进程由于进程调度(下面会讲解,这里简单的理解为CPU对运行的进程进行切换) 的原因,失去了CPU的使用权,而进程继续运行下去所需的其他硬件资源都充足,这时,进程就会进入就绪态。就绪态的案例不好演示,还是以我上面QQ音乐为例子,其实在听歌的过程中,随着OS的进程调度,QQ音乐进程是一直在运行态和就绪态切换的。
3.1.4 阻塞态
阻塞态,就是进程在运行时,突然缺失除了CPU以外的其他硬件资源导致程序无法继续运行下去,最常见的比如我们打印文件时,打印机处于脱机状态,此时,打印对应的进程就会被阻塞,如下图所示,我的打印机处于脱机状态,也就是没有打印机硬件资源,我点击打印,出现的结果是,需要打印的文件一直处于打印队列中,不会打印下去,这就是打印队列进程处于阻塞态的一种表现,只有当打印机硬件资源得到时,打印队列进程才会被加入到就绪队列中,等待CPU的调度。
3.1.5 结束态
结束态,指的是进程结束运行的状态,还是以QQ音乐进程为例,当我不想听音乐了,我点击关闭按钮,此时,就会触发OS对QQ音乐进程的PCB进行信息清除,并且释放PCB和所占用的硬件资源,经历了结束态的进程,OS就感知不到其对应程序的存在了。
3.1.6 进程状态迁移图
介绍完进程的状态,我们可以通过进程的状态迁移图,对进程五个状态的转换有一个直观的理解。
3.2 进程调度
在进程状态转换中,我们谈到了进程调度这个概念,简单的理解就是对运行态下的进程进行切换(注意:进程调度对应的状态转换只涉及到运行态和就绪态),让OS中每一个进程都有运行的机会(雨露均沾,不要让一些进程出现“饿死”),只有这样,多个系统进程和用户进程在某一时间间隔内都可以运行起来,我们的OS才能在稳定的前提下实现多样的功能。
知道了什么是进程调度,那么,OS是如何通过进程调度实现运行态下进程的切换呢?总不能对运行态下的进程想换就换吧。其实,OS系统有一系列的进程调度算法,根据进程调度算法来进行运行态进程切换,这样就根据综合因素,保证了对每一个进程都是公平的。常见的五种进程调度算法有:先来先服务(First Come First Served,FCFS)调度算法,短进程优先(Shortest Job Firstest,SJF)调度算法,时间片轮转(Round-Robin,RR)调度算法,优先级调度算法和多级反馈队列调度算法。
3.2.1 FCFS调度算法
FCFS调度算法是最简单的调度算法,根据名字很好理解,就是先到的进程先运行(这个先到怎么判断呢?可以根据创建进程的时间顺序来确定),该调度算法属于非抢占式(就是一个进程占用了CPU,其他进程必须等它运行完毕才能抢占CPU)。读者应该不难联想到,该算法直接通过一个队列即可实现(队列中存放PCB)。
- 算法优点:实现简单,公平性高(制定了哪个进程先执行的规则)
- 算法缺点:平均等待时间长(等待时间就是进程在整个运行期间,等待CPU的时间)
3.2.2 SJF调度算法
SJF调度算法是指优先调度运行时间短的进程,这个算法通俗易懂,读者应该很容易联想到求出每一个进程的运行时间,然后排序,得到进程的调度顺序。实际上,在进程创建之后,OS是无法预知每一个进程的执行时间的,试想一下,我们在编写程序的过程中,不同代码行对应的指令个数不一样,不同的指令所消耗的CPU时钟周期也不一样,而且程序中存在着大量的循环和调用,导致判断同数量级循环的进程(程序)运行时间几乎是不可能的。
- 算法优点:平均等待时间短
- 算法缺点:公平性不足,容易导致长运行时间进程出现饿死现象
3.2.3 RR调度算法
RR调度算法是一种抢占式(进程没有执行完毕有时候也得让出CPU使用权)调度算法。算法的基本执行流程是,按就绪队列的顺序为每一个进程分配时间片,当一个正在执行的进程时间片用完了,就将该进程放到绪队列队尾,然后从就绪队列队头调度另一个进程,为其分配时间片。
- 优点:类似于FCFS调度算法,保证了公平性(以就绪队列的顺序来调度),此外,在一定的时间间隔内,保证了多个程序的并发性
- 缺点:平均等待时间长
3.2.4 优先级调度算法
优先级调度算法,就是为每一个进程分配一个优先级(这个优先级可以分配到PCB中记录),OS按照进程的优先级进行调度,不难联想到,SJF调度算法其实也有点类似于优先级调度算法,只是SJF把最短时间作为了最高优先级,那么,对于优先级调度算法面临的缺点,可以从SJF调度算法的角度来分析。
- 算法优点:可以自定义调度规则,保证了一定的公平性
- 算法缺点:和SJF调度算法一样,优先级低的进程容易导致饿死现象,但是这个缺点可以通过动态调整优先级的方式来解决,对于等待时间长的进程,可以适当的提高其优先级
3.2.5 多级反馈队列调度算法
多级反馈队列调度算法,是在多级队列调度算法的基础上进行的改进。先简单说一下多级队列调度算法,它通过设置多个队列,每个队列都有各自的调度算法(可以一样,也可以不一样),队列与队列之间设置优先级,OS优先调用优先级高的队列里面的进程,直到优先级高的队列进程运行完毕,才会调度低优先级队列里面的进程,不难想到,其实这也是存在一定问题的,那就是低优先级队列里面的进程也会发生饿死现象。所以就出现了多级反馈队列调度算法,它与多级队列调度算法的唯一区别是允许低级队列的进程跃迁到高级队列中去。
- 算法优点:公平性高,可以自定义调度规则,可操作性强
- 算法缺点:多级队列需要更多的存储空间
3.3 进程间通信
在多进程并发执行的OS中,进程间通信对于进程的管理也十分重要,试想一下这个常见的例子你的微信好朋友通过聊天,向你请教了一个技术性的问题,你想了很久,还是决定打开Chrome,将好朋友的问题复制粘贴到了搜索引擎的框中按下了搜索键,这种两个进程之间的消息传递,就是由OS的进程间通信管理实现的。那么,OS系统对于进程间的通信,又是通过哪些方法实现的呢?这里笔者介绍管道、消息队列、共享内存和信号量机制四种进程间通信方法。
3.3.1 匿名管道
每一个程序被装载近内存之后,都会被存放到一个叫做用户空间的内存区,在这个内存区中,每一个进程都有自己独立的内存空间,进程与进程之间的用户空间内存区是不可以相互访问的,那么,用户空间不可以实现进程间传递数据,OS就考虑使用第三方媒介,如下图,内核空间来实现。管道就因此诞生了,所以,我们对管道的概念,就可以简单的理解为是内核空间的一块内存区域,进程间的通信都是通过管道这个中转站来传递数据的。那么,匿名管道又是什么呢?其实就是对管道的通信方式进行了一定的限制。这个限制可以总结为三大块:
- 限制亲子进程之间通信,只允许父子和兄弟进程间使用匿名管道
- 限制半双工通信,只允许信息从一端流入,从另一端流出,也可以理解为当父子进程使用一个匿名管道的时候,一个只能从管道读信息,另一个只能从管道写信息,要想实现双向通信,只能创建两个匿名管道
- 由于匿名,管道在内核空间没有标识符,所以就导致了非亲子进程之间无法使用一个确定的标识符去寻找同一个管道,这也是第一个限制的原因,那么读者就会问了,为什么亲子进程之间可以找到同一个管道呢? 这是因为子进程或者兄弟进程在创建的过程中,会继承父进程的PCB块中的信息,而PCB中存在管道的信息,所以子进程或者兄弟进程就可以通过相同的PCB信息来找到匿名管道。
3.3.2 有名管道
有名管道是对匿名管道的第一个和第三个限制做出的改进,但是和匿名管道一样,只支持半双工通信。具体的实现是,为管道绑定一个文件标识符,这个文件标识符是OS中一个文件的路径,这就不难想到,有名管道对于进程通信的数据存储,是基于磁盘的(和匿名管道比起来,笔者认为是一个缺点,磁盘的读写速度慢于内存条)。绑定文件标识符之后,由于标识符不属于进程的用户空间部分,支持不同进程间的相互访问,所以就解决了匿名管道只支持父子和兄弟进程通信的局限。
3.3.3 消息队列
介绍完两个管道通信之后,这里介绍一下消息队列通信,消息队列通信主要解决的是有名管道通信不能实现异步通信的问题。具体的,如下图,消息队列也是通过在内核空间创建一个链式队列,然后用一个链表节点作为标识符,供不同进程间通信。和有名管道不同的是,数据读取进程不受数据输入进程的影响,只要消息队列中有数据,就能读取,而数据输入进程也不受数据输出进程的影响,只要消息队列中还存在空间,就能往消息队列里面写数据,这就实现了进程通信的异步性。
3.3.4 信号量机制
信号量机制是进程间通信及其重要的部分,它是进程同步与互斥实现的基础。有关信号量机制的具体实现,笔者强烈建议读者阅读相关的OS理论书籍(比如《现代操作系统 第三版》等),本文只针对入门对信号量机制进行简单介绍。
对于信号量,我们可以简单的理解为其实就是一个整型变量,它控制着进程对共享资源的同步访问(同步访问可以简单的理解为多个进程对多个共享资源的非抢占式访问,比如,共享资源量为y,只有等y个进程中有进程对其访问完毕了,才允许其他进程对共享资源进行访问)。这里,我们假设信号量初始值
s
e
m
=
y
sem = y
sem=y,表示共享资源量为
y
y
y(信号量的初始值不可能大于共享资源的个数)。此时,
s
e
m
sem
sem的取值有三种情况,代表着共享资源的三种状态:
- s e m < 0 sem < 0 sem<0,表示此时 y y y个共享资源都被 y y y个进程占用着,而且有 ∣ s e m ∣ |sem| ∣sem∣个进程因为需要访问该共享资源被阻塞
- s e m = 0 sem = 0 sem=0,表示此时 y y y个共享资源刚刚好被 y y y个进程占用着,而且没有进程需要访问该资源
- s e m > 0 sem > 0 sem>0,表示此时 y y y个共享资源还剩下 s e m sem sem个,如果有不超过 s e m sem sem个进程访问该共享资源,不会发生阻塞
那么,进程在对共享资源进行访问的过程中, s e m sem sem的值又是如何变化的呢,这里有两种情况:
- 进程需要申请共享资源,此时的资源数量 s e m sem sem应该减1
- 进程访问完共享资源之后,需要释放共享资源,此时的资源数量 s e m sem sem应该加1
至此,关于信号量机制的简单描述就完成了,理解了上述两个规则,其实就理解了信号量机制,它的主要功能无非就是实现进程对共享资源的同步访问(这里顺便说一下进程互斥,互斥其实就是共享资源数量为1的情况,也叫临界资源,同一时间间隔内只能一个进程访问,是同步的一个特殊情况),实现原理不难,但是有关信号量机制实现的一些进程同步算法,值得读者继续深入研究,比如银行家算法、读者写者算法和哲学家就餐问题等。
3.3.5 共享内存
对比以上四种方法,共享内存是最快的进程间通信(Inter-Process Communication,IPC) 方式,在多个进程交换数据时,共享内存的方式使OS在内核空间中创建一块内存区域,并且让需要访问该内存区域的进程将这块内存区域映射到自己的进程地址空间,那么,这个映射是怎么提高进程间的通信效率呢?如下图所示,共享内存的方式不需要进程拷贝数据到自己的进程地址空间中,而是直接通过地址映射表,拿到内核空间的地址,然后直接访问共享内存(内核空间)中的数据。上述三个方法的进程通信方式为,从内核空间或者磁盘中拷贝数据到自己的进程地址空间中,拷贝过程中,不仅浪费了进程的地址空间,还多出了时间开销。
🎉总结
至此,操作系统科普与入门之进程篇就结束了,该部分用通俗的语言描述了从什么是OS,OS各个管理模块对应着哪些硬件资源(理解了操作系统,对于以后自己组装电脑,就知道为什么要买那些硬件了)。最后,文章主体的第三部分介绍了OS的进程管理部分,这部分笔者仅仅只是用简单的语言进行了描述,不管是进程的状态转换,进程调度,还是进程间的通信,都非常的重要,仅仅阅读本篇文章,只是对进程管理有了一个宏观上的把握,更多细节的东西,需要读者自行阅读相关的OS资料。