概念
备注
备注2022/8/25
进程的工作原理:
当启动一个程序,开始我们的任务,然后等任务结束,进程停止,该进程就会从进程表中移除;
简单说下你对并发和并行的理解?
并发 = 两个或多个事件在同一时间间隔内发生;同一实体上的多个事件;
并行 = 两个或多个事件在同一时刻发生;不同实体上的多个事件;
同步、异步、阻塞、非阻塞的概念?
同步:当一个同步调用发出后,调用者会一直等待返回结果。通知后,才能继续后续的执行。
异步:当一个异步调用发出后,调用者不用一直等待返回结果。实际处理这个调用的部件完成后,通过状态、通知和回调来通知调用者。
阻塞:调用结果返回前,当前线程会被挂起,即阻塞;
非阻塞:调用结果没有返回,也不会阻塞当前线程。
操作系统概念?
一个程序从开始运行到结束的完整过程,你能说出来多少?
一个程序从开始运行到结束的完整过程是:预处理(预编译)、编译、汇编、链接。
预处理——处理源文件中以#开头的预编译指令,生成.i文件;
编译——将.i文件 转换为汇编语言,生成.s文件;
汇编——将.s文件(汇编语言)转化.o(Linux下)/.obj(Windows下)(机器代码);
链接——链接目标代码生成可执行文件;
详细解释:
预处理(预编译):
主要处理源代码文件中以“#”开头的预编译指令;
条件编译、头文件包含、宏替换的处理、生成.i文件;
处理规则
删除所有#define,展开所有的宏定义(宏替换处理)
处理所有的条件编译指令(“#if” “#endif” “#ifdef” “elif” “#else”)(条件编译)
处理“#include”预编译指令,将文件内容替换到它的位置,该过程是递归进行的,文件中包含其他文件;(头文件包含)
删除所有注释(“//” 或 “/* */ ”)
保留所有的#pragma编译器指令,编译器需要使用到他们,如:#pragma once 为了防止有文件被重复引用;
添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告使能够显示行号;
编译:
将预处理后的文件.i或 .ii文件 转换成汇编语言,生成.s文件;且经过词法分析、语法分析、语义分析及优化;
处理过程:
处理 | 说明 |
---|---|
词法分析 | 类似于“有限状态机”算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号 |
语法分析 | 对词法分析器生成的记号进行语法分析,产生语法树 |
语义分析 | 语义分析器则对表达式是否有意义进行判断,分析的语义是静态语义,即在编译期能分析的语义,而动态语义是在运行期才能确定 |
优化 | 源代码级别的一个优化过程 |
目标代码生成 | 由代码生成器将中间代码转换成目标机器代码,生成汇编代码 |
目标代码优化 | 针对汇编代码进行优化,寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等 |
汇编:
汇编语言变成目标代码(机器代码)生成.o(Linux下)或 .obj(Windows下)的文件;
没有复杂语法、没有语义、不用进行指令优化,只需要根据汇编指令和机器指令的对照表一一翻译即可;
链接:
链接目标代码,生成可执行程序;
将不同的源文件产生的目标文件进行连接,从而形成一个可以执行的程序,分为静态链接和动态链接;
用户态和内核态是如何切换的?
-
概念:
用户态:用户空间,主要用于执行用户程序;
内核态:内核空间,主要负责运行系统、硬件交互; -
区别:
用户态:运行代码需要受到CPU的很多检查;
内核态:运行代码不受任何限制; -
用户态 → 内核态:
通过中断、异常、系统调用(cpu中的实现又称为陷阱指令(Trap Instruction))
外设中断:当外设完成用户的请求时,会向CPU发送中断信号;
异常:进程处于用户态,发生异常事件(缺页异常),就会触发切换;
系统调用:用户态进程主动要求切换到内核态的一种方式; -
内核态 → 用户态:
设置程序状态字PSW;
程序状态字(Program Status Word,PSW),又称状态寄存器,主要用于反映处理器的状态及某些计算结果以及控制指令的执行;
操作系统通过控制cpu的PSW寄存器中的一个2进制位来区分内核态和运行态;
区分用户态和内核态
主要是对访问范围的限制,对于一些比较危险的操作,需要在内核态下运行,不可以随意更改。(设置时钟、内存清理)
什么是用户态和内核态?
-
用户态:
运行用户程序;
用户态的CPU指令集只能受限的访问内存,并且不允许访问外围设备;
不允许单独占用CPU,可以被其他程序获取; -
内核态:
运行操作系统程序、操作硬件;
处于内核态的CPU指令集——
①可以访问任意的数据,包括外围设备(网卡、硬盘等);
②可以从一个程序切换到另一个程序,并且占用CPU不会发生被强占的情况,一般处于特权级0的状态称之为内核态;
用户态与内核态只是不同的资源访问权限;是操作系统的两种运行状态;是CPU指令集权限的区别;
每个进程都会有两个栈,分别是用户栈与内核栈,对应用户态与内核态的使用;
原子操作是如何实现的?
处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。
基本操作原子性
处理器会自动保证基本的内存操作的原子性;
处理器保证从系统内存中读取或者写入一个字节是原子的(处理器读取一个字节,其他处理器不能访问该字节的内存地址);
复杂操作原子性
处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
-
总线锁定:
使用处理器提供的一个LOCK# 信号,当一个处理器在总线上输出此信号时,其他处理器的请求都被阻塞,那么该处理器可以独占共享内存;
缺点:将CPU和内存之间的通信锁住了,使得其他处理器不能操作其他内存地址的数据,该方式开销太大。 -
缓存锁定:
同一时刻对某个内存地址的操作保证其原子性;
该机制会阻止同时修改由两个以上CPU缓存的内存区域数据,当其他处理器写回已被锁定的缓存行的数据时,会使缓存行无效;
两种情况下不会使用缓存锁定:
①当操作的数据不能被缓存在处理器内部 或 操作的数据跨多个缓存行(Cache line)时,则处理器会调用总线锁定;
②有些处理器不支持缓存锁定,intel 486、Pentium处理器,就算锁定的内存区域在处理器的缓冲行中也会调用总线锁定;
扩展
采用原子操作的原因:
多处理器:一个CPU里有多个核,当多核同时读内存中的某个变量并更新该变量的值时,会出现冲突;例如两个核同时执行“i++”操作;
(备注:2022/8/23)
多核CPU = 一个芯片里面有多个处理器部件;
单处理器:一个CPU里有一个核,当一个核中运行的两个线程同时对内存中的变量进行“i++”操作时,底层实际上执行了三条指令:
lw $1 &i #将变量i读入寄存器$1
addi $1 $1 1 #把寄存器$1中的值加1
sw $1 &i # 把寄存器$1中的值写回内存
两个线程在单核处理器中执行上述三个语句出错的原因:
每个线程执行三条指令的任意一句结束后都可能产生线程切换;
寄存器是线程的独立空间,线程间不共享;
出错分析:
前提:i的值是0,线程A对i+1,线程B对i+1,期望i=2
①线程A执行语句1,寄存器的值是0;
②线程A切换线程,线程A保存当前上下文,把所有寄存器的值保存在自己的内核栈空间;
③线程B运行,执行了语句1/2/3,内存变量i的值为1;
④线程A被调度运行,CPU会重新加载线程A的内存栈保存的上下文信息,加载完毕之后,寄存器的值为0,再执行语句2/3,最后计算出寄存器的值是1,把结果写回内存;
扩展2022/8/23
操作系统的存储管理包括:CPU寄存器、Cache缓存、主存、外存。
外中断和异常有什么区别?
图示:
中断和异常的相同点:
最后都是由CPU发送给内核,由内核去处理;
处理程序的流程设计上是相似的;
中断和异常的区别:
区别 | 中断(外中断) | 异常(内中断) |
---|---|---|
概念 | 由硬件设备产生,物理上而言就是电信号 通过中断控制器发送给CPU,接着CPU判断收到的中断来自于哪个硬件设备,最后由CPU发送给内核处理中断 | 异常包括出错、陷入、可编程异常 由CPU产生,同时会发给内核处理异常 |
产生源不同 | 硬件设备产生 | CPU产生 |
内核处理程序不同 | 中断调用其处理程序 | 异常调用其处理程序 |
同步/异步性 | 异步的,表示中断可能随时到来 | 时钟同步的,由CPU产生 |
处理XX | 处理中断,处于中断上下文中 | 处理异常,处于进程上下文中 |
异常内容介绍
- 出错:
保存的EIP指向触发异常的那条指令;当从异常返回时,会重新执行触发异常的指令; - 陷入(trap):
保存的EIP指向触发异常的那条指令的下一条指令,不会重新执行触发异常的指令;
陷入最主要的应用是在调试中,被调试的进程遇到设置的断点,会停下来等待处理,等到让其重新执行,并且不会再去执行已执行过的断点指令; - 可编程中断:
中断由编程者用int指令来触发;
Linux中,唯一使用的可编程中断,即int 0x8系统调用;
硬件对可编程中断的处理与对trap(陷入)的处理类似,即从这类异常返回时也是返回到触发异常的下一条指令;
EIP = 企业信息门户(Enterprise Information Portal),是指在Internet的环境下,把各种应用资源、数据资源和互联网资源统一集到企业信息门户之下,根据每个用户使用特点和角色的不同,形成个性化的应用界面,并通过对事件和消息的处理、传输把用户有机地联系在一起。
进程管理
进程和线程的基本概念
进程 | 说明 |
---|---|
是系统进行资源分配和调度的独立单位,系统中并发执行的单位 | |
一个程序至少有一个进程 | |
在执行过程中独占内存资源 |
线程 | 说明 |
---|---|
是进程的实体,是CPU进行分派和调度的基本单位 | |
一个进程至少有一个线程 | |
多个线程之间共享内存,通过减少切换时间来提高执行效率 | |
多个线程之间可以并发执行 |
扩展
程序-进程-线程-协程:
程序:含有指令和数据的文件,被存储在磁盘或其他数据存储设备中=静态代码;
进程:程序的一次执行过程,是系统并发运行的单位;
线程:是进程的实体,是CPU调度和分派的基本单位;
协程:线程的更小单位,是一种用户态的轻量级线程,也被称为“微线程”;
进程和线程的区别
区别 | 说明 |
---|---|
概念 | 进程是资源分配的最小单位 线程是CPU调度的最小单位 |
开销 | 创建进程或撤销进程,操作系统都会为之分配或回收内存,操作系统的开销远大于创建或撤销线程时的开销 |
地址空间 | 不同的进程之间地址空间是相对独立的,同一进程内的线程共享同一地址空间。 一个进程的线程在另一个进程内是不可见的 |
影响 | 进程间不会互相影响 一个线程挂掉将可能导致整个进程挂掉 |
为什么有了进程,还要有线程呢?
-
原因:
有的进程需要“同时”做很多事情,而传统的进程只能串行地执行一系列程序,因此引入线程概念,增加并发度。 -
进程的优点:
可以使多个程序并发执行,以提高资源的利用率和系统的吞吐量。 -
进程的缺点:
进程在同一时间内只能干一件事情;
进程在执行的过程中如果阻塞,整个进程都会被挂起,即使进程中有些工作不依赖与等待的资源,仍然不会执行;
两个进程之间不能直接通信,也就是说不能使用腾讯视频来买淘宝的东西。 -
引入线程带来的好处:
好处 | 进程 | 进程 + 线程 |
---|---|---|
资源分配上 | 进程是资源分配、调度的基本单位 | 进程是资源分配的基本单位,线程是调度的基本单位 |
并发性 | 只能进程间并发 | 进程、线程间都可以并发,提高了并发度 |
系统开销 | 进程间切换开销大,由于它们具有独立的数据空间,数据传递只能通过进程间通信实现 | 线程间切换,同一进程的所有线程共享内存区域,系统开销小 |
进程的状态转换
进程是程序的一次执行过程。
进程有三种运行状态:运行态(Running)、就绪态(Ready)、阻塞态(Waiting/Blocked)。
介绍 | 运行态 | 就绪态 | 阻塞态 |
---|---|---|---|
概念 | 占有CPU,并在CPU上运行 | 已经具备运行条件,但由于没有空闲CPU,而暂时不能运行 | 因等待某一事件而暂时不能运行 |
解释 | 单核处理机环境下,每一时刻最多只有一个进程处于运行态 | 进程已经拥有了除处理机之外所有需要的资源, 一旦获得处理机,即可立即进入运行态开始运行 | 等待操作系统分配打印机、等待读磁盘操作的结果。 因为CPU是比较昂贵的部件,为了提高CPU的利用率,需要先将进程需要的其他资源分配到位,才能获得CPU的服务 |
图示:
运行态→阻塞态,是一种进程自身做出的主动行为;
阻塞态→就绪态,不是进程的主动行为,是一种被动行为;
不能实现的:阻塞态→运行态,就绪态→阻塞态。
就绪→阻塞解释:因为进入阻塞态是进程自动请求的,那么必然需要进程此时是运行态才能发出这种阻塞请求。
进程间的通信方式有哪些?
进程间通信(IPC,InterProcess Communication)是指在不同进程之间的信息交换。
常用的通信方式有:管道、消息队列、共享内存、信号量、信号、Socket。
管道
-
概念:
内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是输入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据。
内核 = 操作系统的核心,是基于硬件的第一层软件扩充,提高操作系统的最基本功能。 -
缺点:
半双工通信,一条管道只能一个进程写,一个进程读。
一个进程写完后,另一个进程才能读,反之同理。
不适合进程间频繁的交换数据。
消息队列
- 概念:
用于解决管道不适合进程间频繁的交换数据;
是保存在内核中的消息链表;
进程a把数据放在对应的消息队列后就正常返回,进程b需要的时候再去取就好了; - 缺点:
消息队列在通信的过程中,存在用户态与内核态之间的数据拷贝开销。进程写入数据到内核中,存在用户态拷贝数据到内核态的过程,进程从消息队列中读取数据,会发生内核态拷贝数据到用户态的过程。
共享内存
- 概念:
为了解决消息队列内核态与用户态数据拷贝的开销;
共享内存机制 = 拿出一块虚拟地址空间来,映射到相同的物理内存中。 - 缺点:
多个进程对同一个共享内存中写入数据可能会产生覆盖,如果只读没有问题;
信号量
概念:
为了解决两个进程对共享内存的修改所产生的冲突;
是一个整型的计数器,用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据;
采用PV操作实现。
P操作 = 把信号量减1,如果信号量小于0,则表示资源占用,进程阻塞;如果信号量大于等于0,则表示还有资源可用,进程可以正常继续执行;
V操作 = 把信号量加1,如果信号量小于等于0,表示当前有阻塞进程,需要将进程唤醒;如果信号量大于0,表示当前没有阻塞中的进程。
信号
- 概念:
一般用于一些异常情况下的进程间的通信,是一种异步通信,数据格式一般是一个数字;
信号是进程间通信机制中唯一的异步通信机制; - 介绍:
Linux系统中,ctrl+c产生sigint信号,表示终止该进程;ctrl+z产生sigstp信号,表示停止该进程;
Socket
- 概念:
跨网络与不同主机上的进程之间通信,需要Socket通信;
进程的调度算法有哪些?
调度算法:根据资源分配策略所规定的资源分配算法。
常用的有:
先来先服务调度算法、优先级调度算法(前两者进程、作业)、时间片轮转调度算法、最短剩余时间优先(中间 进程)、短作业优先调度算法、高相应比优先调度算法(最后 作业)等。
调度算法 | 说明 |
---|---|
先来先服务 进程/作业 | 最简单的调度算法,先进先出或严格排队方式。 当每个进程就绪后,加入就绪队列;当前正在运行的进程停止后,选择在就绪队列中等待时间最长的进程运行。 即当进程运行结束或者被中断的时候,才会选择就绪队列的进程执行 该算法可用于作业调度或进程调度。 比较合适于长作业(进程)、而不利于短作业(进程) |
优先级调度算法 作业/进程 | 作业调度中,每次从后备队列中选择优先级最高的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列 进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入使用 |
时间片轮转 进程 | 适用于分时系统。 系统将所有就绪进程按照到达时间的先后次序排列成一个队列,进程调度总是选择就绪队列中的第一个进程执行,即先来先服务,但仅能运行一个时间片 |
最短剩余时间优先 进程 | 针对最短进程优先增加了强占机制 该算法下,进程调度总是选择预期剩余时间最短的进程。即假设新进程加入就绪队列时,它可能比正在运行的进程所剩余运行时间还短,因此只要新进程就绪,调度进程就可能抢占当前正在运行的进程。 |
短作业优先 作业 | 比较适合短作业 从后备队列中选择一个或若干个运行时间最短的作业,将它们调入内存运行 是非抢占策略,原则是下一次选择预计处理时间最短的进程,导致短作业会超过长作业跳至队列头 |
高响应比优先 作业 | 主用于作业调度 算法对先来先服务调度算法和短作业优先调度算法的一种综合平衡 每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的队列投入运行 |
作业和进程
区别 | 作业 | 进程 |
---|---|---|
调度 | 作业调度 外存与内存之间的调度,发生频率很低,使进程从创建态到就绪态的过程 | 进程调度 从内存到CPU的调度,发生频率很高,使进程从就绪态到运行态的过程 |
概念 | 用户需要计算机完成某项任务时要求计算机所做工作的集合 | 具有独立功能的程序对某个数据集在CPU处理器上的执行过程 |
关系 | – | 一个作业可以由多个进程组成 |
扩展
- 响应比
= 1 + (等待时间/运行时间) - 后备队列
通常将作业控制块排成一个或多个队列,而这些队列称为后备队列。 - 作业控制块(JCB)
是记录与该作业有关的各种信息的登记表。
JCB是作业存在的唯一标志,包括用户名、作业名和标志状态等信息。
备注2022/8/12 后备队列、就绪队列
后备队列 =放作业、在外存中,就绪队列 = 放进程,在内存中
操作系统首先从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。然后再将新创建的进程插入就绪队列,准备执行。
进程状态转换+挂起
进程控制—对系统中的所有进程实施有效的管理,具有创建新进程、撤销已有进程、实现进程状态之间的转换等功能;
- 进程的特点:
特点 | 说明 |
---|---|
动态性 | 进程是对应程序的执行 进程在内存三种基本状态(就绪、运行、阻塞),也可能被挂起到外存 |
独立性 | 各进程的地址空间相互独立,采用进程间通信手段进行通信 |
并发性 | 多个进程可以同时运行 |
异步性 | 每个进程都以其相对独立的不可预知的速度运行 |
结构化 | 进程 = 代码段 + 数据段 + PCB(程序控制块) PCB是系统感知进程存在的唯一标识 进程与PCB一一对应 |
进程挂起
-
概念:
当系统资源紧张的时候,操作系统会对在内存中的资源进行更合理的安排,这时就会将某些优先级不高的进程设为挂起状态,将其移到内存外面; -
引入挂起状态的原因:
原因 | 说明 |
---|---|
用户的请求 | 程序运行期间发现了可疑的问题,需要暂停进程 |
操作系统的需要 | 对运行中资源的使用情况进行检查和记账 |
安全 | 系统有时可能会出现故障或某些功能受到破坏,就需要将系统中正在进行的进程进行挂起,当系统故障消除以后,对进程的状态进行恢复 |
负载调节的需要 | 一些实时任务非常重要,需要得到充足的内存空间,这时需要把非实时的任务进行挂起,优先使得实时任务执行 |
定时任务 | 一个进程可能会周期性的执行某个任务,那么在一次执行完毕后挂起而不是阻塞,这样可以节省空间 |
父进程的请求 | 考察、协调 或修改子进程 |
进程中挂起状态有两种:就绪挂起状态 和 阻塞挂起状态 。
区别在于前者就绪挂起状态还存在于内存中,后者存在与外存中;
进程间状态的转变过程
状态1 | 状态2 | 转变条件 |
---|---|---|
就绪 | 运行 | 系统分配CPU |
运行 | 就绪 | 时间片运行结束 |
运行 | 阻塞 | 进程调用系统调用的方式申请资源 或者 等待某事件的发生 |
运行 | 终止 | 进程执行结束 或者 运行过程中 出现不可修复的错误 |
运行 | 就绪挂起 | 程序在运行的时候直接被挂起 |
阻塞 | 就绪 | 申请的资源已获得 或者 等待事件已发生 |
阻塞 | 阻塞挂起 | 没有进程处于就绪状态 或 就绪状态的进程要求获取更多的资源,需要将进程放入外存 |
阻塞挂起 | 阻塞 | 一个进程释放足够的内存时,系统会把一个高优先级阻塞挂起进程转为阻塞进程 |
阻塞挂起 | 就绪挂起 | 当阻塞挂起进程等待的相关事件发生 |
就绪 | 就绪挂起 | 程序中有高优先级阻塞进程,低优先级就绪进程,系统会挂起低优先级就绪进程,可以让优先级更高的进程获得更多资源 |
就绪挂起 | 就绪 | 系统中没有就绪进程 |
挂起状态 和 阻塞状态 的区别
区别 | 挂起(suspend) | 阻塞(pend) |
---|---|---|
是否释放CPU | 不释放CPU,如果优先级高则永远轮不到其他任务运行 挂起用于程序调试中的条件中断,出现某个条件的情况下挂起,然后进行单步调式 | 释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现 |
是否主动 | 主动,挂起后还要受到CPU的监督 | 被动,发生在磁盘、网络IO、wait、lock等要等待某种事件发生的操作之后 |
与调度器是否相关 | 任务调度是操作系统实现的,任务调度,直接忽略挂起状态的任务 | 任务调度会顾及处于阻塞状态的任务,当阻塞状态任务就绪后,只需要等CPU就可以可以执行了 |
进程终止的方式?
进程终止的方式有四种:
正常退出(自愿)、错误退出(自愿)、严重错误退出(非自愿)、被其他进程杀死(非自愿)。
方式 | 说明 |
---|---|
正常退出 | 完成工作而退出。编译器完成了所给程序的编译后,编译器会执行一个系统调用告诉操作系统它完成了工作 UNIX中是exit,Windows中是ExitProcess |
错误退出 | 编译某文件xx.c,但文件不存在,于是编译器就会发出声明并退出 面向屏幕的交互式进程通常不会直接退出,而是会告知用户可能发生了系统错误,需要重试或退出 |
严重错误退出 | 由进程引起的错误,通常是由于程序中的错误所导致的 非法指令,引用不存在的内存或除数是0 UNIX系统中,进程可以通知操作系统,希望自行处理某种类型的错误,进程会收到信号(中断)而不是这类错误出现时直接终止进程 |
被其他进程杀死 | 某个进程执行系统调用告诉操作系统杀死某个进程 UNIX,系统调用是kill,Win32对应的是TerminateProcess |
进程终止方式具体介绍:
正常终止 | 异常终止 |
---|---|
从main函数中返回 main作为当前程序的入口,也是当前程序的出口 | 调用abort函数 发出signalabort信号给当前进程,杀死当前进程,获得calmdown文件 |
调用exit,直接结束进程 exit是库函数 | 接到一个信号并终止 例如Ctrl + C终止进程 |
调用_exit 或 _Exit ,系统调用 | 最后一个线程对其取消请求作出响应 也是被迫终止,故算异常终止 |
最后一个线程从其启动例程(线程本身)中返回 | – |
最后一个线程调用pthread_exit | – |
守护进程、僵尸进程和孤儿进程?
守护进程
-
概念:
守护进程是在后台运行,不与任何终端关联的进程;
守护进程命名习惯上以d结尾; -
作用:
通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache 和postfix)运行,并能处理一些系统级的任务。 -
创建过程:
调用fork(),创建新线程,它就是守护线程;
在父进程中调用exit,确保子进程不是进程组长;
调用setsid()创建新的会话区
将当前目录改为根目录
将标准输入、标准输出、标准错误重定向到/dev/null
僵尸进程
-
概念:
子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,子进程将成为一个僵尸进程。 -
目的:
维护子进程的信息。 -
大量僵尸进程存在出现问题:
他们进程号一直被占用,但是系统能使用的进程号是有限的,系统将因为没有可用的进程号而导致系统不能产生新的进程。
孤儿进程
- 概念:
父进程在子进程退出之前就结束了自己的生命,此时子进程叫做孤儿进程;
init进程会收留孤儿进程,变成孤儿进程的父进程;
如何避免僵尸进程?
①通过signal(SIGCHLD,SIG_IGN) 通知内核对子进程的结束不关心,由内核回收;
②父进程主动调用wait、waitpid等函数等待子进程结束;
如果尚无子进程退出wait会导致父进程阻塞;waitpid可以通过传递WNOHANG(wnonhang)使父进程不阻塞立即返回;
如果父进程很忙可以用signal注册信号处理函数,通过信号处理函数调用wait、waitpid等待子进程退出,这样父进程就可以不用阻塞;
③杀死父进程。如果僵尸进程的父进程还存在,将其杀死,这样就变成了②,init或systemd会负责善后工作;
什么是死锁?
- 概念:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞的现象。
若无外力作用,他们都将无法推进下去。
也就说:死锁 = 两个线程同时占用两个资源,但又在彼此等待对方释放锁。
产生死锁的原因?
- 概念:
由于系统中存在一些不可剥夺资源,当两个或两个以上进程占有自身资源的同时要求对方资源时,会导致每个进程都无法向前推进,这就是死锁。 - 死锁产生的原因:
系统资源不足;
进程运行的推进顺序不合适;
资源分配不当;
系统中资源可以分为两类:
分类 | 概述 | 举例 |
---|---|---|
可剥夺资源 | 是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺 | CPU和主存属于可剥夺性资源 |
不可剥夺资源 | 系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放 | 磁带、打印机等 |
死锁产生的必要条件?
条件 | 说明 |
---|---|
互斥条件 | 进程要求对所有分配的资源进行排他性控制,即在一段时间内某资源仅一进程所占用 |
请求和保持条件 | 当进程因请求资源而阻塞的时候,对已获得的资源保持不变 |
不剥夺条件 | 进程已获得的资源在未使用完之前,不能剥夺,只能是功能完成时由自己释放 |
循环等待条件 | 在发生死锁时,必然存在一个进程资源的环形链 |
解决死锁的基本方法?
基本方法 | 说明 |
---|---|
预防死锁 | 通过设置一些限制条件,去破坏产生死锁的必要条件 |
避免死锁 | 在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁 |
检测死锁 | 允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清理掉 |
解除死锁 | 该方法与检测死锁配合使用 |
怎么预防死锁?
概念
预防死锁 = 破坏死锁产生的四个条件。
方法 | 说明 |
---|---|
破坏请求条件 | 一次性分配所有资源,这样就不会有请求了 |
破坏请求保持条件 | 只要有一个资源得不到分配,就不给这个进程分配其他的资源 |
破坏不可剥夺条件 | 当某进程获得了部分资源,但得不到其他资源,则释放已占有的资源 |
破坏循环等待条件 | 系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反 |
怎么避免死锁?
概念
在资源分配前,使用某种方法避免系统进入不安全状态,从而避免发生死锁。
安全状态与不安全状态:
安全序列 = 按照某种进程顺序,给每个进程分配其所需要的资源,直至满足进程对资源的最大需要,且每个进程都可以顺序完成,则该进程的顺序就是安全序列。
如果系统可以找到这样的安全序列,那么系统就处于安全状态,否则处于不安全状态。
安全状态图示
避免死锁的方式最主要是银行家算法,分为 单个资源的银行家算法 和 多个资源的银行家算法。
银行家算法
需要知道的三件事情:
每个进程所要获取的每种资源数量是多少;
每个进程当前所分配到的每种资源的数量是多少;
系统当前可分配的每种的资源数量是多少;
单个资源的银行家算法
算法主要是判断对请求的满足是否会进入不安全状态,如果会—拒绝请求,否则—予以分配。
图示:
多个资源的银行家算法
怎么解除死锁?
方法 | 说明 |
---|---|
资源剥夺 | 挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他死锁进程 注意防止被挂起的进程长时间得不到资源 |
撤销进程 | 强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源 撤销的原则可以按进程优先级和撤销进程代价的高低进行 |
进程回退 | 让一个或多个进程回退到足以避免死锁的地步。 进程回退是自愿释放资源而不是被剥夺。 要求系统保持进程的历史信息,设置还原点 |
介绍一下几种典型的锁?
互斥锁
-
概念:
互斥锁是独占锁,只要该锁被线程A占用,那么线程B加锁就会失败; -
加锁失败:
互斥锁加锁失败后,线程会释放CPU,给其他线程使用;
互斥锁加锁失败而阻塞,是由操作系统内核实现;加锁失败,内核会将线程设置为睡眠状态,等到锁被释放后,内核才会唤醒线程; -
互斥锁加锁失败,会从用户态陷入到内核态,涉及两次线程上下文切换:
①加锁失败,内核会把线程从运行状态设置为睡眠状态,然后把CPU切换给其他进程运行;
②锁被释放,睡眠状态变成就绪状态,内核会在合适的时候,把CPU切换给该线程执行。
条件变量
-
概念:
条件变量允许线程阻塞和等待另一个线程发送信号的方式弥补互斥锁的不足,以免出现竞争条件;
互斥锁缺点:只有两种状态(锁定 与 非锁定); -
理解:
当条件不满足时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发生改变;
一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程;
自旋锁
- 概念:
自旋锁不会引起调用者睡眠,如果自旋锁被别的进程占用,调用者会一直在那里等待自旋锁的释放;
通过CPU提供的CAS函数函数实现,在用户态完成加锁和解锁,不会主动产生上下文的切换;
在发生多线程竞争锁的情况,加锁失败的线程会忙等待,直到拿到锁; - CAS函数
CAS函数 = Compare and Swap 比较并且交换。期待值E,内存中的值V,要修改的新值是N
当E 不等于 V时,线程不会执行操作会一直等待;当 E 等于 V时,线程才会将V的值替换成N;
读写锁
-
概念:
读写锁由读锁和写锁两部分组成,如果只读取共享资源用读锁加锁,如果要修改共享资源则用写锁加锁; -
读写锁工作原理:
当写锁没有被线程所占有的时候,多个线程能够并发地持有读锁,提高了访问效率;
当写锁被线程持有,那么读线程获得读锁和其余的写线程获得写锁将都会被阻塞;
悲观锁 和 乐观锁
- 悲观锁:
互斥锁、自旋锁、读写锁;认为多线程同时会有冲突,要提前加锁; - 乐观锁:
无锁编程;先修改共享资源,再验证该时间段内有没有发生冲突,如果没有其他线程操作则操作完成,有其他线程修改该资源则放弃操作;
谈谈你对动态链接库和静态链接库的理解?
静态链接库:
-
概念:
把(lib)文件中用到的函数代码直接复制到目标程序,程序运行的时候不再需要其它的库文件;
在编译链接时直接将需要的执行代码拷贝到调用处 = 把lib文件中用到的函数代码直接链接进目标程序 = lib中的指令都全部被直接包含在最终生成的EXE文件中; -
优点:
运行速度快;可移植性强;可独立运行;→可执行程序中已具备执行所需的所有东西,因此在执行的时候速度快; -
缺点:
代码冗余、可执行文件的体积大; -
后缀名:
Linux系统中,静态连接 = .a Windows系统中,静态连接 = .lib
动态链接库(DLL,Dynamic Link Library)
-
概念:
把调用函数所在的文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码;
编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中;然后当程序在运行到指定的代码时,去共享执行内存中已经加载的动态连接库可执行代码,最终达到运行时连接的目的。
采用共享代码的方式;该DLL不必被包含在最终EXE文件中,EXE文件在执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。 -
优点:
多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝;
更新方便,只需要替换原来的目标文件,无需将所有文件再重新链接一遍; -
缺点:
由于运行时加载,可能会影响程序的前期执行性能;
性能消耗,把链接推迟到运行阶段,每次执行都需要进行链接,会造成性能耗费; -
动态链接:
Linux系统中,后缀是.so;Windows系统中,后缀名是.dll;
扩展2022/8/25
库文件
- 概念:
就是压缩文件;文件包含多个目标文件(二进制文件),每个文件是一个实用的功能模块; - 头文件与库文件最大的区别是:
头文件只存储变量、函数或类这些模块的声明部分,库文件才负责存储各模块具体的实现部分;所有的库文件都有对应的头文件作为调用它的接口;
链接
C或C++程序从源文件到生成可执行文件需经历 4 个阶段,分别为预处理、编译、汇编和链接。
链接阶段所要完成的工作,是将同一项目中各源文件生成的目标文件和程序中用到的库文件整合为一个可执行文件。
在 Linux 发行版系统中,静态链接库文件的后缀名通常用 .a 表示,动态链接库的后缀名通常用 .so 表示;
在 Windows 系统中,静态链接库文件的后缀名为 .lib,动态链接库的后缀名为 .dll。
I/O管理
什么是缓冲区溢出(Buffer overflow)?有什么危害?
-
概念:
缓冲区溢出 = 当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的内容会覆盖在合法数据上。
缓冲区 = 一段可读写的内存区域; -
危害:
程序崩溃,导致拒绝服务;
跳转并且执行一段恶意代码;
缓冲区攻击
- 目的:
希望系统能执行这块可读写内存中已被蓄意设计好的代码; - 缓冲区攻击
原理——因为按照冯诺依曼存储程序理论,程序和数据都采用二进制的形式存储在内存中,直接从内存中取存储的二进制数据,从形式上无法分辨是代码还是数据,这也为缓冲区溢出攻击提供了可能;
结果——向缓冲区中随便填写东西,会造成溢出,但一般只会出现分段错误(Segmentation Fault),而不能达到攻击的目的。
扩展
- 冯诺依曼结构计算机 2022/8/12
概念:提出计算机制造的三个原则:二进制逻辑、程序存储执行、计算机由五个组成部分。
组成:运算器、控制器、存储器、输入设备、输出设备。
存储程序理论 = 将数据和指令变成 二进制码存储在内存中。
内存管理
分页与分段的区别?
区别 | 分页 | 分段 |
---|---|---|
概念 | 页是信息的物理单位 分页是由于系统管理的需要 | 段是信息的逻辑单位 分段是为了满足用户的需要 |
大小 | 页的大小固定且由系统决定 分页存储管理方式中直接由硬件实现 | 段的大小不固定,取决于用户所编写的程序; 通常由编译程序在对源程序进行编译时,根据信息的性质来划分 |
地址空间 | 分页作业的地址空间是一维的,程序员只需要利用一个记忆符,即可表示一个地址 | 分段作业地址空间则是二维的,一个地址需要段号和段内偏移共同决定 |
共享性 | 页的保护和共享受到限制 | 段是信息的逻辑单位,便于存储保护和信息的共享 |
相同点:
都采用离散分配方式,且都由地址映射机制来实现地址的转换。
物理地址、逻辑地址、虚拟内存的概念?
- 物理地址(Physical Address):
是内存单元真正的地址,程序在运行时执行指令和访问数据最后都要通过物理地址从主存中存取数据; - 逻辑地址(Logical Address):
是计算机用户看到的地址。
逻辑地址并不一定是元素存储的真实地址(即数据元素的物理地址,在内存条中所处的位置),并非是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思维。 - 虚拟内存(Virtual Memory):
概念——是计算机系统内存管理的一种技术。
作用——使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
页面置换算法有哪些?
-
请求分页系统:
概念——在基本分页系统基础上,为了实现虚拟存储器功能而增加了请求调页功能和页面置换功能。
具体执行——请求分页系统中,只要求将当前需要的一部分页面装入内存,以便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存时,再通过调用功能将其调入,同时还可以通过置换功能将暂时不用的页面换出到外存中,以便腾出内存空间。 -
页面置换算法:
目标——页面置换频率最低(缺页率最低)。
算法 | 说明 |
---|---|
最佳置换算法(OPT) | 选择的被换出页面是以后不再使用的,或者在最长时间内不再被访问,通常可以保证最低的缺页率。 该算法是理论上的算法,可以用来评价其他算法 |
先进先出置换算法(FIFO) | 选择换出最先进入的页面。 该算法实现简单,但会将那些经常被访问的页面换出,从而使缺页率上升 会存在由于分配的物理块数量增大而缺页率不减反增的异常现象,称为Belady异常 |
第二次机会算法 | 为解决FIFO算法会将经常使用的页面置换出去的问题所提出 页面存在R位标识页面是否被使用过,1=最近被用过,0=最近没有被使用过;链表 = 从前往后表示页面放入的顺序 当页面被访问(读或写)时设置该页面的R位为1。需要替换的时候,检查最老页面的R位;如果R位是0,则这个页面既老又没有被使用,直接替换;如果R是1,则将R位清0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入一样,然后继续从链表的头部开始搜索 |
最近最久未使用算法(LRU) | 将最近最久未使用的页面换出 存在一个链表,存储页面的访问顺序,表尾是最近最久未使用的页面 页面有个访问字段 = 自上次访问以来所经历的时间 |
最不常用算法(LFU) | 如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也会很小 |
时钟算法 最近未使用算法(NRU,Not RecentlyUsed) | 第二次机会算法需要在链表中移动页面,降低了效率。 时钟算法使用环形链表将页面链接起来,再使用一个指针指向最老的页面 |
改进时钟算法 | 页面中增加了修改位,1 = 修改过;0 = 没有修改过 |
-
最佳置换算法
-
先进先出置换算法
-
最近最久未使用置换算法
实现方式:
①在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头,这样就能保证链表表尾的页面是最近最久未访问的。
②为每个页面设置一个访问字段,来记录页面字上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
-
时钟算法:
实现策略 = 给每一页框关联一个附加位,称为使用位;
当某一页首次装入内存中时,则将该页页框的使用位设置为1,;当该页随后被访问到时(在访问发生缺页中断之后),它的使用位也会被设置为1;
该方法中,用于置换的候选页框集合被看做是一个循环缓冲区,并且有一个指针与之关联;
指针——指向可替换的位置;
-
改进时钟算法:
当发生缺页中断时:
把未修改过的页面替换进外存 = 把页面直接从内存中删除 , 因为内存和外存中所对应的该页面的内容相同,处理时间只有一次缺页中断访问外存的时间;
把修改过的页面替换进外存,需要向外存中写入一次、再加上缺页中断的时间,相当于访问了两次外存,是上述修改的两倍;
所以避免把修改过的页面替换可以提高性能。
访问位A 和 修改位M
访问位A | 修改位M | 说明 |
---|---|---|
0 | 0 | 表示该页最近既未被访问,又未被修改,是最佳淘汰页 |
0 | 1 | 表示该页最近未被访问,但已被修改,并不是很好的淘汰页 |
1 | 0 | 表示该页最近被访问,但未被修改,该页有可能被再次访问 |
1 | 1 | 表示该页最近被访问,也被修改,该页可能被再次访问 |
第二组(访问位 = 0,修改位 = 1)的情况会出现,页帧被修改 》 修改位M = 1,即它同时也会被使用》使用位=1,
也就是说不会存在被修改但是没有被使用的情况;真实情况,页帧的访问位可能被清零,这样第四组(访问位=1,修改位 =1)将访问位清零之后就变成第二组了。
算法执行步骤
扫描 | 说明 |
---|---|
第一轮扫描 | 从指针的当前位置开始,扫描帧缓冲区;(该次扫描中,对使用位不做任何修改) 遇到第一类页(A=0,M=0)作为淘汰页, 失败则开始第二轮扫描; |
第二轮扫描 | 查找第二类页,找到的第一个作为淘汰页。 扫描过程中,对所有经过的页,把它的访问位置0 。 失败则开始第三轮扫描 |
第三轮扫描 | 指针将回到它的最初位置,并且集合中所有页的访问位均为0; 重复第一步,并且如果有必要,重复第二步,这样可以找到被淘汰的页 |
- 最不常用算法(LFU)
算法选择最近时期使用次数最少的页作为淘汰页。
为每个页面配置一个计数器,一旦某页被访问,则将其计数器的值加1;在需要选择一页置换时,则将选择其计数器值最小的页面,即内存中访问次数最少的页面进行淘汰。
存在的问题:有些进程开始时被访问的次数很多,但以后这些页面可能不再被访问,这样的页面不该长时间停留在内存中;
解决:定期将定时器右移,以形成指数衰减的平均使用次数。
常见内存分配错误?
内存错误 | 说明 |
---|---|
内存分配未成功,但是使用了它 | 解决:在使用之前检查指针是否为NULL |
内存分配成功,但是尚未初始化就引用 | 对内存初始化缺省值了解不清楚,内存的缺省值没有固定的标准,尽管有些时候值为0 |
内存分配成功且初始化,但操作越过了内存边界 | 数组越界需要注意循环中的变量 |
忘记释放内存,造成内存泄露 | 动态内存的申请与释放必须配对,程序中的malloc与free的使用次数一定要相同(new和delete也一样),否则肯定报错 |
对同一动态内存空间多次释放 | 由于记忆问题导致在对一块内存空间释放后再次释放,造成错误 |
释放内存之后,还继续使用 | 可能场景: 程序中的对象调用关系过于复杂,不清楚哪个对象释放内存 函数的return语句有误 (返回指向“栈内存” 的“指针” 或 “引用”,因为该内存的函数体结束时被自动销毁) 使用free 或 delete 释放了内存后,没有将指针设置为null |
备注2022/8/25
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
内存交换中,被换出的进程保存在哪里?
内存交换中,被换出的进程保存在磁盘中(外存),具体来说就是对换区。
具有对换功能的操作系统中,通常把磁盘空间分为文件区 和 对换区 两部分;
文件区主要用于存放文件,主要追求存储空间的利用率,对文件区空间的管理采用离散分配方式;
对换区只占磁盘空间的小部分,被换出的进程数据就存放在对换区;
抖动你知道是什么吗?它也叫颠簸现象
-
概念:
在更换页面时,如果更换的页面是一个很快会被再次访问的页面,那么会出现缺页中断之后又很快发生新的缺页中断,这就是颠簸现象。 -
物理块数量对颠簸现象的影响:
为进程分配的物理块太少,会使进程发生抖动现象;
为进程分配的物理块太多,又会降低系统整体的并发性,降低某些资源的利用率。 -
解决方式:
①页面替换策略失误,修改替换算法;
②运行进程太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低程序的数量;
③前两者不行,就只可:终止该进程、增加物理内存容量;
虚拟内存是什么?
- 概念:
也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中;
即使进程a和进程b的虚拟地址一样,其访问的是不同的物理内存地址;