操作系统❤️⭐️❄️⛅️
- ❤️第一章 操作系统概述
- ❤️第二章 进程和线程
- ❤️第三章 内存
❤️第一章 操作系统概述
⭐️操作系统的概念
从上往下看
指控制和管理整个计算机系统的硬件和软件资源,并合理的调度计算机的工作和资源的分配,提供给用户方便的接口和环境
从下往上看
它是计算机系统中最基本的系统软件
⭐️操作系统的功能
❄️一,作为用户和计算机硬件之间的接口
⛅️第一大类:命令接口
1.联机命令接口(交互式命令接口)
例子:windows系统打开控制台输入控制信息
2.脱机命令接口(批处理命令接口)
例子:windows系统中,我们编写的C语言代码,当我们编译好之后,运行时系统会自动的一行一行的按次序运行,不需要任何交互
⛅️第二大类:程序接口
允许用户通过程序间接使用,由一组系统调用(广义指令)组成
⛅️第三大类:图形界面
将各种数据应用图形化呈现给用户
❄️二,作为资源的管理者
比如打开qq视频聊天
1.首先要在各个文件夹中找到qq的安装位置(文件管理)
2.双击打开qq.exe(即将程序放入内存当中,即存储器管理)
3.qq程序正常运行(qq运行的进程被分配进cpu,即处理机管理)
4.开始和朋友视频聊天(将摄像头分配给进程,即设备管理)
❄️三,作为最接近硬件的层次
操作系统实现对硬件机器的拓展和抽象化
⭐️操作系统的特征
❄️1.并发
区别于并行,并发在宏观上是同时发生的,但是在微观上是交替发生的,操作系统的并发性指的是计算机系统中同时存在着多个运行着的程序
⛅️为什么需要并发
单核处理机:一次只能处理一个程序(并行)
多核处理机:(现在一般的处理机)一次性可以处理多个程序(并行)
但是在计算机系统之中,肯定有多余四个以上的程序在同时工作,所以才需要并发
❄️2.共享
即资源共享,并发和共享互为存在的条件
⛅️分类
1.互斥共享方式:(一段时间只能匀速一个进程访问该资源)
eg:当qq和微信不能同时使用摄像头
2.同时共享方式:(允许一个时间段内(并不是同一时刻) 由多个进程通过是对他们进行访问
比如微信和qq同时发送文件资源
❄️3.虚拟
1.举个例子,一个程序需要放入内存并给他分配cpu之后才能够被执行,当内存的容量小于程序总的大小时,为什么还能保证程序的正常运行呢,这时候就要使用虚拟存储技术中的空分复用技术
2.使用时分复用技术
❄️4.异步
异步是指,在多道程序环境下,允许多个程序并发执行,但是因为资源有限,所以进程的执行不是一贯到底的,二十走走停停,已不可预知的速度向前推进,这就是进程的异步性
⭐️操作系统的分类
❄️1.手工操作阶段
使用纸带来记录,速度十分缓慢,资源的利用率低
❄️2.单道批处理系统
人们把数据先输入在一个磁带里,然后系统再对这个词带进行信息读取(类似于脱机接口),缓解了一定程度的人机速度矛盾,但是呢内存中只能有一道程序运行
❄️重点:3.多道批处理系统(操作系统正式诞生)
计算机从磁带中读取多段数据,使用中断技术来控制程序运行,各程序并发执行,但是没有人机交互能力,因为用户是提前将数据输入磁带中,在读取磁带的过程中用户无法对自己的作业进行控制
❄️重点:4.分时操作系统
可以轮流为各个用户/作业进行服务,解决了人机交互问题,允许多个用户同时使用计算机,但是不能优先处理一些紧急任务
❄️重点:5.实时操作系统
计算机要在严格的时限内处理完事件,能优先处理紧急事件,主要特点是及时性,可靠性
分类
1.硬实时系统:必须在时限内限制
2.软实时系统:能接受偶尔违反所规定的时间
⭐️操作系统的运行机制和体系结构
❄️运行机制
⛅️两种指令:
1.特权指令
2.非特权指令
⛅️两种处理机状态
1.核心态,可以执行任何指令
2.用户态
⛅️两种程序
1.内核程序,只能在核心态下执行
2.应用程序
❄️操作系统的内核
⛅️时钟管理
⛅️中断管理
⛅️原语
是一种特殊的程序,其执行具有原子性(即不允许中断)
⛅️对系统资源进行管理的功能
(下面的了解应该不用背)
1.进程管理
2.存储器管理
3.设备管理
❄️操作系统体系结构
⛅️大内核结构
优点:高性能
缺点:难以维护
⛅️微内核结构
优点:方便维护,结构清晰
缺点:性能低,需要频繁的在和心态和用户态之间转换
⭐️中断和异常
❄️中断
作用:为了实现多道程序并发执行而引起的一种技术
❄️中断过程
1.中断发生的时候,CPU立即进入核心态(由于进程转换或者是分配IO设备等操作是特权指令,应用程序无法完成,所以需要CPU进入核心态,使操作系统获得计算机的控制权)
2.当中断发生后,当前运行的进程暂停运行,并由操作系统内核对中断进行处理
用户态和核心态之间的转换是如何实现的?
答:通过中断实现从用户态转换为核心态(中断是用户态转化为核心态的唯一方式)
而核心态转换为用户态是通过执行一个特权指令
❄️中断分类
1.内中断,也称异常(信息来源:CPU内部,与当前执行的指令有关)
2.外中断,就是我们所指的中断(信息来源:CPU外部,与当前执行的指令无关)
⭐️系统调用
❄️中断和异常的异同
相同点:都是CPU对系统发生的某个事情做出的一种反应。
区别:中断由外因引起,异常由CPU本身原因引起。
❄️什么是系统调用?
应用程序有时会需要一些危险的、权限很高的指令,如果把这些权限放心地交给用户程序是很危险的(比如一个进程可能修改另一个进程的内存区,导致其不能运行),但是又不能完全不给这些权限。于是有了系统调用,危险的指令被包装成系统调用,用户程序只能调用而无权自己运行那些危险的指令。另外,计算机硬件的资源是有限的,为了更好的管理这些资源,所有的资源都由操作系统控制,进程只能向操作系统请求这些资源。操作系统是这些资源的唯一入口,这个入口就是系统调用。
❤️第二章 进程和线程
⭐️进程
❄️进程的定义
进程是资源分配,接受调度的基本单位
❄️进程和程序的区别
1、进程是动态的,而程序是静态的;
2、进程有一定的生命期,而程序是指令的集合,本身无“运动”的含义。没有建立进程的程序不能作为一个独立单位得到操作系统的认可。
3、一个程序可以对应多个进程,但一个进程只能对应一个程序。
4、进程和程序的组成不同。从静态角度看,进程由程序、数据和进程控制块(PCB)三部分组成,而程序是一组有序的指令集合。
联系:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
❄️进程的组成
由程序段(程序代码存放于此),数据段(程序运行时需要使用的数据),PCB(操作系统通过PCB来管理进程,所以PCB中存放有进行管理所需要的各种信息)PCB是进程存在的唯一标志
❄️进程的组织
系统中通常有许多个PCB(也就是许多程序再并行运行),为了对他们加以管控应该用适当的方法把这些PCB组织起来
⛅️链接方式
将多个PCB连接成一个队列,重要的PCB会优先排在前面,操作系统持有指向各个队首的指针
⛅️索引方式
根据进程状态不同,建立几张索引表
操作系统持有指向索引表的指针
❄️进程的特征
1.本质特征:动态性
2.进程是进行资源分配,调度的基本单位:独立性
3.各个进程以不可预知的速度向前推进,可能导致运行结果的不确定性:异步性
⭐️进程的状态和转换
❄️进程的5种状态
1.运行态:进程正在占据CPU
2.就绪态:所有除了CPU的其他设备都已经准备就绪,只要等到CPU空闲就可以直接进入运行态
3.阻塞态:除了CPU还有其他设备没有做好准备
ps:因为CPU是计算机中最核心的设备,为了tigaoCPU的利用率,需要先将其他资源分配到位,才能得到CPU的服务
4.创建态:进程正在被创建
5.终止态:进程正在从系统中撤销
⭐️进程控制
就是实现进程状态的转换
❄️用原语来实现进程控制
用原语(一种特殊的程序)实现进程控制,原语的特点是,执行期间不允许中断,只能一气呵成,这种不允许中断的操作就叫做原子操作
⛅️原语操作
开关中断的权限非常大,必然是核心态才能执行的特权指令
⛅️原语分类
1.更新PCB的信息
2.将PCB插入合适的队列
3.分配/回收资源
⭐️进程通信
此节摆烂了直接上图
指的就是进程之间的信息转换
进程是系统分配资源的单位,因此各进程拥有的内存地址空间相互独立,为了保证安全,一个进程不能直接访问另一个进程的地址空间
❄️共享存储方式
❄️管道通信
只要管道里有数据就可以读写
❄️消息传递
⭐️线程概念和多线程的模型
❄️什么是线程
传统的进程只能串行的执行一系列程序
为此引入了线程来增加并发度,这是线程就成了程序执行的最小单位,引入线程后,进程内的各个线程也可以并发,进一步提升了系统的并发度,使得一个进程内的也可以并发处理各种任务,引入线程之后,进程是系统资源的分配单位,(内核级)线程作为处理机调度的基本单位(也就是任务调度的基本单位)(比如打印机,其内存地址空间都是分配给进程的但是使用打印机时是由线程来执行)
❄️引入线程的优势
减少并发带来的开销,并且可以增加并发度
❄️线程的分类
1.用户级线程
可以由应用程序负责,线程切换可以在用户态下即可完成,无需操作系统干涉,用户线程对用户不透明,对操作系统透明,也就是从用户视角就能看到的线程
比如从用户看着由三个线程
从操作系统看来只有一个线程
2.内核级线程
内核级线程由操作系统内核完成,所以内核级线程只能在核心态下才能完成切换,也就是说从操作系统内核视角能看到的线程
比如从用户看着有三个线程
从操作系统看来有三个线程
❄️线程的实现方法
在同时支持用户级线程和内核级现成的系统中,可采用两者组合的方式:将n个用户级线程映射到m个内核级线程上
操作系统只能看见内核级线程,比如上图中操作系统只能看见2个线程,所以就算将此进程放在一个四核的处理及上处理时,同一时间也只能处理并行处理2个线程
❄️多线程模型
一对多模型
多个用户映射到一个核心级线程,每个用户进程只对应一个内核级线程
优点
用户级线程在用户态就能完成转换,线程管理的系统开销小,效率高
缺点
当一个用户级线程被阻塞后,整个进程也会被阻塞(一个用户线程被阻塞,其对应的内核级线程也被阻塞了,从而导致整个线程都被阻塞,所以并发度不高,多个线程不可以再多核处理机上并行运行
⛅️一对一线程
优点
并发能力强
缺点
一个用户进程会占据多个内核级线程,每次线程的切换都需要进入核心态来切换,管理成本高,开销大
⛅️多对多模型
集合了两者的优点(并发度高,且管理成本低,开销小),避免了两者的缺点
❄️线程总结
⭐️处理机调度
❄️基本概念
当有一系列任务需要被处理时,由于资源有限,这些事情不能同时处理,所以就需要确定某种规则来确定处理这些任务的顺序,这就是调度所要研究的问题
❄️高级调度(作业调度)
一个作业可由多个进程组成,且必须至少由一个进程组成,反过来则不成立。
高级调度主要是指调入的问题,因为只有调入才需要操作系统来进行,调出时只要该作业执行完就调出
高级调度按一定的原则从外存上处于后备队列的作业中挑选一个给他们分配内存等必要资源,并建立相应的进程(PCB),使他们获得竞争处理机的权力。
每个作业值调入一次,调出一次,作业调入时会建立相应的PCB,作业调出时才会撤销PCB
资源调度方向
从外存到内存
❄️中级调度(内存调度)
就是决定挂起队列中的哪个进程重新进入内存
引入虚拟存储技术后,可将暂时不运行的进程调制外存等待,等到重新具有运行条件了且内存有空闲了再重新调入内存
暂时被调到外存等待的进程的状态叫做挂起状态,但是PCB不会调至外存,因为操作系统会通过内存中的PCB来保持对各个进程的监控管理,被挂起的进程PCB会被放到挂起队列中
⛅️挂起状态和阻塞的区别
两种状态都是暂时不能获得处理机的服务,但是阻塞状态的进程也是在内存中的,而挂起状态是在外存中的
方向:从外存到内存
❄️低级调度(进程调度)
按照某种方法将处理机分配给就绪队列中的一些进程,是操作系统中的最基本的一种调度,在一般的操作系统中都必须配置进程调度
方向:内存–处理机
❄️三种调度的关系
注意对进程状态的影响和频率
❄️后备队列和就绪队列的区别
操作系统首先从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。然后再将新创建的进程插入就绪队列,准备执行。
❄️总结
⭐️进程如何调度
心情不好直接开摆
进程切换主要完成了
1.保存前一个进程的数据
2.对新的进程数据的恢复
⭐️调度算法
❄️1.先来先服务算法(FCFS)
⛅️算法思想:从公平的角度来实现(比如生活中的排队买奶茶)
⛅️是否可以抢占
非抢占式算法
⛅️优缺点
优点:算法公平,实现简单
缺点:对短进程不利,对长进程有利
(比如买奶茶的时候,前面一个人要买30杯奶茶,我只买1杯,但是我必须等到前面的人买完了再轮到我
⛅️相关运算
❄️短作业优先算法(SJF)
⛅️思想:最短的进程优先得到服务
⛅️是否可抢占
有抢占式的,也有非抢占式的
⛅️优缺点
“最短的“进程调度算法(不严谨)
⛅️是否会饥饿
如果一直来短作业的话,长作业就会饥饿,如果一直得不到服务甚至会饿死
⛅️相关运算
非抢占式
周转时间,带权周转时间,平均周转时间都比先来先服务算法更好
抢占式:最短剩余时间算法
每当有进程进入就绪序列时,系统都要看当前运行的进程的剩余时间相较于刚进入就绪序列的进程的运行时间谁更小,然后选择更小的进程进行运行,当一个进程主动放弃处理机后(也就是运行完了),系统也会采用这种算法进行调度
三个指标都比非抢占式调度算更好
抢占式的短进程优先算法(最短剩余时间算法)的平均等待时间,平均周转时间最少
在所有进程几乎同时到达时,采用SJF调度算法(非抢占式)的平均等待时间,平均周转时间最少
❄️高响应比优先算法(HTTN)
⛅️算法思想
需要综合考虑进程的等待时间和运行时间,每次调度选择响应比更高的进程来分配处理机
⛅️是否可以抢占
不可以
⛅️优缺点
综合考虑了SJF和FCFS的缺点
当等待时间相同时,要求服务时间段的优先(SJF)
当服务时间相同时,等待时间长的优先(FCFS)
长进程通过增大等待时间的方法避免了被饿死
❄️三种算法的总结
三种算法都是适用于多道批处理系统,人机交互能力十分差
⭐️另外三种适用于交互式系统的算法
❄️时间片轮转算法
适用于分时操作系统
⛅️算法思想
和FCFS相似,排队进行处理,但是每次处理只给进程分配比如100ms的时间片来使用处理机,若该进程在此时间片内没有处理完进程,就重新将进程放在队尾排队
⛅️是否可抢占
是,超时直接剥夺使用处理机的权利
⛅️优缺点
优点:
公平,响应快,适用于分时操作系统
缺点:
进程切换会导致开销,而且不能区分紧急任务
⛅️实际操作
响应时间:比如一个系统中有10个进程在并发执行,时间片为1秒,当一个进程发出一个命令,可能需要等10秒才能被系统响应,这10秒就是响应时间
当时间片增大时:响应时间就会增大,当时间片太大时,此算法就退化成了FCFS算法
当时间片太小时:进程的切换过于频繁,进程皆会耗费大量的时间来进行进程的切换,使系统的效率降低
❄️优先级调度算法
⛅️算法思想
处理紧急情况的算法
⛅️是否是抢占型
都是,因为当进程执行完后主动放弃处理机时进行的是非抢占式调度,而在就绪队列更新的时候发生的调度就是抢占型调度
⛅️优缺点
优点:
适用于实时操作系统,可以区分进程的紧急程度
缺点:
优先级高的进程一直进来时,可能会导致优先级低的进程饿死
⛅️是否饥饿
会
⛅️实际过程
❄️多级反馈队列算法
没怎么听懂直接放图算了
集合了FCFS的公平,SJF的尽可能快的完成任务,时间片轮转法的使每个进程都能尽快得到响应,优先级调度算法的可以灵活的调整各种进程的优点
⭐️进程同步和进程互斥
❄️进程同步
解决进程异步性的问题,同步又称为直接制约关系,它是指为了完成某种任务而建立的两个或者多个进程,这些进程需要在某些位置上协调他们的工作次序而产生的制约关系(比如在进程的通信中,同一数据要保证读数据写入要在读出之前)
❄️进程互斥
还记得之前学的进程共享的方式:
1.互斥共享
2.同时共享
我们将一个时间段内只允许一个进程使用的资源称为临界资源 ,比如说摄像头,打印机都属于临界资源,所以对临界资源的访问必须互斥的进行,进程互斥指的就是当一个进程放某个临界资源时,另一个想要访问该临界资源的进程必须等待当前进程结束访问,并释放该资源后才能去访问
⛅️逻辑代码的四大部分
1.进入区
检查是否可以进入临界区,若需要则”上锁“
2.临界区
访问临界资源
3.退出区
解除临界区被占用的标志
4.剩余区
做其他处理
⛅️进程互斥的四大原则
1.空闲让进原则
2.忙则等待原则
3.有限等待原则
4.让权等待原则
⭐️进程互斥的软件实现方法
❄️单标志法
两个进程在访问玩临界区后会把权限给另一个进程,也就是说每个进程进入临界区的权限只能被另一个进程赋予
设置标志位true,true等于1时,进程1就可以进入,true等于0时,进程0就可以进入
比如两个进程P0,P1
缺点:
如果允许进入临界区的是进程P0,但是P0一直不想进入临界区,那么虽然此时临界区空闲,但是不允许其他进程访问,违背了空闲让进原则
❄️双标志先检查
设置一个两个元素的bool类型的数组flag,flag【】中存储的是对应位置的元素是否有意愿进入程序,每当有进程要进入临界区时,会检查是否有进程有意愿进入临界区,如果有的话就等待,如果没有的话就自己进入
缺点:因为进程执行的异步性,导致可能执行如下代码时,第1步执行完后就执行第5步,导致有多个进程可能同时进入临界区,违反了“忙则等待原则” (如果1,2和5,6是一步原子操作的话就是正确的算法)
❄️双标志后检查
区别于上一种的先检查才上锁,此类算法是先上锁后检查
但是执行步骤为1,5,2,6时,P0进程发现1已经占位了,P1又发现0已经占位了,循环就会一直执行下去,导致临界区明明是空的却没有进程进来(虽然标志位显示P1,和P0都是想进入临界区,但是都没有进入,就像是两个人争着过桥,结果最后谁都没有过去,反而把自己给耽误了)
虽然解决了忙则等待,却违背了空闲让进,有限等待的原则,还可能会导致两个进程的饥饿
❄️Peterson算法
“孔融让梨”,相较于上一种算法,如果两种算法争着进入临界区,那么其中一个进程会让给另一个进程
遵循了空闲让进,忙则等待,有限等待,但是违背了让权等待(即忙等现象)
⭐️三种进程互斥的硬件实现方法
❄️中断屏蔽方法(原语控制进程)
利用开关中断的方式来限制进程当前是否能被中断,比如关中断后,当前进程就不会发生进程转换
优点:
简单高效
缺点:
不适用于多处理机(因为多处理机不受单纯的开关中断来限制)
不适用于用户进程(因为开关进程只能在内核态,用户态不能随意使用)
❄️TS指令
执行过程中不允许被中断
优点:
实现简单,适用于多处理机系统
缺点:
不满足让权等待原则,暂时无法进入临界区的进程会zhanyongCPU并循环执行区指令,从而导致忙等
❄️Swap指令
和TSL指令实现原理一样
⭐️信息量机制
之前学习的进程互斥的解决方法中
1.进入区的检查,上锁,操作无法一气呵成,导致了两个类进程有可能同时进入临界区的问题
2.所有解决方案都无法实现让权等待
❄️信号量机制
Dijkkstra提出了一种卓有成效地实现进程互斥同步的方法:信号量机制
⛅️实现原理
用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥,进程同步
信号量:
可以用一个信号量来表示系统中某种资源的数量,比如系统中有1台打印机,就可以设置一个初始值为1的信号量
原语:
一种特殊的程序段,执行只能一气呵成,通过开/关中断来实现
一对原语:
wait(S)和signal(S),其中S为信号量,wait和signal可以理解为我们写的一个函数
⛅️整型信号量
用一个整数作为信号量,用来表示系统中某种资源的数量
和整型变量的区别:
整型变量可以完成加减乘除等操作,但是整形信号量只能完成P操作,V操作,初始化着三种操作
int flag=1;初始化
void wait(int flag){wait原语,相当于进入区的代码
while(flag<=0);如果资源不够就一直等待
flag-=1;如果资源够的话就让资源-1
}
void signal(int flag){相当于退出区代码
flag+=1;
}
双标志先检查类似,只不过不能被异步执行(原语操作),但是还是会忙等(当资源已经被占用时,进程依然会占用处理机并执行while那串代码)
⛅️重点:记录型信号量
每次都对value进行操作,和双标志后检查类似
typedef struct {
int value;剩余的资源数
struct process *L;等待资源的队列
}semaphore
void wait(semaphore S){
S.value--;先将value--然后再判断
if(S.value<0){如果没有资源的话
block(S.L); 将进程从运行态转换为阻塞态并挂到等待队列L
}
}
void signal(semaphore S){
S.value++;释放资源让value+1
if(S.value<=0){当value还是<0时说明等待队列中还有进程在等待
wakeup(S.L);唤醒在等待队列中的进程,即将阻塞态转换为就绪态
}
}
不会产生忙等现象
⭐️用信号量机制实现进程同步,互斥,前驱关系
❄️信号量机制实现同步过程
通过V或者P操作实现
初始化信号量为0(用来表示资源的使用情况,检查是否有资源可以被使用)
(V操作:使信号量++,P操作:使信号量–)
❄️信号量实现前驱关系
为每一对前驱设置一个信号量,在每一组前操作之后执行一次V操作,在每一次后操作之后执行一次P操作
(即每一次前操作之后将信号量+1,使后操作-1时能让信号量的值>=0,每一次后操作之前都让信号量-1,判断是否资源还能被利用)
❄️进程实现互斥
前面讲过硬件软件实现方式
硬件:TSL,中断,swap
软件:单标志法,双标志先检查,双标志后检查,皮特森算法
实现过程:将flag初始值置为1,当有进程占用时将其置为0,进程占用结束时置为1
⭐️生产者-消费者问题
❄️分析
⛅️思路分析
1.当生产者发现缓存中有空间时才能向缓存中存储数据,消费者发现缓存中有空间才能取数据,即符合进程同步规律
2.当一个进程存数据时,另一个进程不能同一时间存储数据,符合进程的互斥性
⛅️初始化分析
1.首先,要保证进程的互斥,可以设置一个mutex初始值=1,进程在mutex的值为1时才能进入
2.要保证发送方进程的同步,可以设置一个empty初始值=n,表示此时空闲的缓冲区的数量
3.要保证接收方的进程同步,可以设置一个full初始值=0,表示此时接收方能接受的数据数量
所以当要生产一个产品时
p(empty);生产了一个产品,所以缓冲区容量-1
p(mutex);表示进程已经被占用了,其他进程不能再使用缓冲区以免当前数据被覆盖
把进程放进缓冲区
v(mutex);释放进程
v(full);能使用的产品+1
当要消费一个产品时
p(full);
p(mutex);
从缓冲区中取出一个产品
v(mutex);退出进程
v(empty);缓冲区容量+1
⛅️死锁
先同步再互斥
⭐️多生产者-多消费者问题
桌子上有一只盘子,每次只能在盘子中放一个水果,爸爸专门向盘子中放苹果,妈妈专门方橘子,儿子专门吃橘子,女儿专门吃苹果,只有当盘子为空时才能放一个水果,仅当盘子中有自己需要的水果时儿子或女儿才能从其中取出水果
⭐️吸烟者问题
设置四个变量
1.桌子上组合1的数量offer1=0
2.桌子上组合2的数量offer2=0
3.桌子上组合3的数量offer3=0
4.抽烟者抽烟是否完成finish=0
5.此时应该轮到哪一个抽烟者抽烟i=(i+1)%3
(不用设置互斥访问量,因为同一时间只能有一个抽烟者抽烟,所以当其他两个抽烟者进程由于没有对应组合的数量被阻塞,而供应者因为抽烟者还没有抽完,所以finish的值还是0,被阻塞,所以不需要设置互斥访问量)
⭐️读者-写者问题
⭐️管程
❄️为什么要引用管程
信号量机制编写的程序复杂困难,易出错
所以需要引入一种机制,来简化pv操作,这种机制用来管理进程的同步和互斥
❄️管程的定义和特征
⛅️定义
1.管程≈java中的类,可以定义多组可以共享的数据(封装)
2.管程中有许多功能对应相应的入口(也就是其中定义的函数),进程只有进入这一些入口后才能访问其中的数据
⛅️特征
1.每次只允许开放管程中的一个入口,并且只能让一个进程或线程进入,编码者不需要再编写互斥的代码,因为编译器会自己实现进程互斥的进入管程
2.同步问题可以使用等待唤醒操作解决,可以让一个进程或者线程先等待,等到其前操作使用完管程后,入口开放后再将这个等待的进程唤醒,再进入管程
❄️总结
⭐️死锁
❄️什么是死锁,饥饿,死循环
死锁,饥饿是操作系统的问题(管理者)
死循环是被管理者的问题
⛅️死锁
定义:各个进程都在等在等待对方手里的资源,导致各个进程都阻塞了
进程数量限制:至少是两个进程才能发生死锁
发生时处于什么状态:发生死锁一定是处于阻塞态
⛅️饥饿
定义:一个进程很久都没有得到处理及资源的分配
进程数量限制:最少可以是一个进程
进程数量限制:发生饥饿可能处于阻塞态(没有被分配到IO设备),可能处于就绪态(没有被分配到处理机),也可能处于外存中还没有调进内存
⛅️死循环
定义:死循环可以上处理及运行
进程数量限制:最少可以是1个进程
进程数量限制:可能处于运行态
❄️死锁产生的必要条件
1.进程之间必须是互斥进行的
2.进程之间不能抢夺资源
3.进程已经占据了一个资源,但是又提出了新的资源请求,新的资源却又被另外一个进程占据
4. 存在一种循环等待链
注意:如果同类资源数>1,即是有循环等待链也未必能发生死锁
❄️什么时候发生死锁
对非抢占型资源的不合理分配
1.对系统资源的竞争(仅限不可抢夺的资源比如打印机,可以抢夺的资源比如处理机是不会引起死锁的)
2.进程推进顺序非法,比如两个进程占据了资源,使用后却不释放导致双方向对方还在占据的资源提出申请时发生死锁
3.信号量使用不当,比如生产者-消费者模型中的先互斥再同步就会产生死锁
❄️总结
⭐️如何处理死锁(理解为主)
❄️分类
1.不允许死锁发生
静态策略:预防死锁
动态策略:避免死锁
2.允许死锁发生
死锁的检测和解除
❄️预防
⛅️1.破坏不可剥夺条件
⛅️2.破坏请求和保持条件
⛅️破坏循环等待条件
❄️避免死锁
⛅️安全序列和不安全序列
安全序列:指的是如果系统按照某种顺序能够使每一个进程都顺利完成,那么这个顺序就叫做安全序列
不安全序列:如果系统没有找到安全序列那么此时系统就处于不安全状态,但是如果有进程归还了一些资源,系统也有可能重新回到安全状态
安全序列一定不会发生死锁,但是非安全序列可能出现死锁
银行家算法实现方法(还不会)
❄️检测和删除死锁
⛅️死锁的检测
前提:
1.使用某种数据结构来保存资源的请求和分配
两种节点
进程结点:对应一个进程
资源节点:表示一种资源,可以有多个
两种边
进程节点->资源节点,表示进程想申请几个资源
资源节点->进程节点,表示已经为进程分配了几个资源
2.提供一种算法,利用上述的信息来检测系统是否已经进入死锁状态
如果系统中的剩余可用资源数足够满足进程的需求,那么这个进程是不会阻塞的,当这个进程处理完后,将所有占用的资源返还,即将所有边去掉
然后唤醒其他处于阻塞态的进程,如果到了最后能消除所有边的话就说明此时一定不会发生死锁
⛅️死锁的解除
资源剥夺法
挂起(放到外存)某些进程,将他占有的资源分配给其他进程
撤销进程法
直接将进程终止
进程回退法
将一个进程退回到足以避免死锁的地步
⛅️总结
❤️第三章 内存
⭐️内存的概述
❄️什么是内存
内存是用于存放数据的硬件,程序执行前需要先存放到内存中才能被CPU处理
因为外存中的数据传输过于缓慢,而内存中速度快,CPU直接和内存进行交互效率更高
❄️如何确定数据存入的是内存中的哪个单元
分为按字节编址,按字编址
❄️进程运行的原理:指令
代码翻译成指令,这些指令会指示CPU去那个地方取地址,存地址,这个数据应该做什么样的处理,指令中直接给出了变量x的实际存放地址(物理地址),但是在生成机器指令时并不知道进程的数据会被存放到哪里,所以编译生成的指令一般使用的是逻辑地址
⛅️相对地址和绝对地址
eg:
比如四个同学去住酒店,四个人按照学号的递增序列(0,1,2,3)入住酒店的8,9,10,11号房间,这时四个人的学号就是相对地址(逻辑地址),而实际入住的酒店号就是绝对地址(物理地址),所以我们只需要找出四个人中的一个人的地址就能够推出其他人的地址
❄️相对地址和逻辑地址的转换
程序运行的过程
先将代码编译成若干个小模块,再由若干个小模块合成一个大的模块,最后由装入程序将装入的模块装入内存中运行(装入的是内存中的绝对地址)
即
1.编译:将代码翻译为机器语言
2.链接:有目标模块生成装入模块,链接后形成完整的逻辑地址
3.装入:将装入模块装进内存,装入后形成物理地址
⛅️绝对装入
⛅️静态重定位
动态重定位
总结
⭐️操作系统对内存管理(不太重要,了解即可)
❄️内存空间的分配和回收
❄️操作系统对内存空间进行扩充
❄️实现逻辑地址与物理地址的转换
⛅️三种装入方式
绝对装入:编译时就产生绝对地址
静态重定位装入:装入时将逻辑地址转换为物理地址
动态重定位装入:运行时才将逻辑地址转换为物理地址
❄️进程保护(保证各个进程互不影响)
⛅️1.设置上下限寄存器
⛅️2.采用重定位计时器和界地址寄存器
重定位计时器:记录装入模块存储进内存的初始地址
❄️总结
⭐️用覆盖和交换实现内存空间的扩充(选择题)
❄️覆盖方式
当程序的大小大于内存的容量时,可以将内存和程序都分段
1.将内存分为固定区和覆盖区,固定区是执行一个程序必须的代码段,这个代码段常驻内存,而覆盖区中的代码在程序需要时调入内存,不需要则调出
2.将程序分成多个段,常用段常驻内存,不常用的在需要时才调入内存
❄️交换技术
和处理机调度的中级调度类似
补充:为什么调出时会保留进程的PCB,因为调出时内存会记录该进程被调到了外存的哪里,方便之后的调入
⛅️外存在什么位置保存换出的进程
外存基本被分为了两个部分
1.对换区:占外存的小部分,被换出的数据就存放在对换区(追求对换速度,所以采用连续分配方式)
2.文件区:存储文件(追求存储空间的利用率,所以文件去空间的管理采用离散分配方式)
⛅️什么时候交换
内存符合过高时
⛅️换出哪些进程
可以有限换出处于阻塞态的进程,优先级低的进程等
❄️总结
⭐️用连续分配管理的方式实现内存的分配与回收
连续分配方式指的是为用户进程分配的空间必须是连续的
❄️单一连续分配
系统被分为系统区和用户区
系统区用于存储操作系统有关数据,用户区用于存放用户进程相关数据,内存中只能有一道用户程序,用户程序独占整个用户区
适用于单用户,单任务,简单但是利用率极低
❄️固定分区分配
将内存中的用户区部分成若干个固定大小的分区,在么一个分区中可装入一道作业,形成了最早的,最简单的一种可以多道程序运行的管理方式
❄️动态分区分配
系统不会预先划分内存,但是会动态的根据进程的大小建立分区
❄️外部碎片和内部碎片
❄️总结
⭐️动态分配分区算法
动态分区算法:指的是先从一个内存空间中已有的一段分区种,对这个分区再进行一次划分一个区域,然后用该区域对该进程进行分配,所以没有外部碎片。
一个进程进入内存后可能有多个内存区域满足该进程,这时就需要一种算法来让该进程选择哪一个分区
❄️首次适应算法
每次从低地址开始查找
❄️邻近适应算法
首次适应算法每次都是从链表头开始查找,可能会导致低地址部分出现很多小的空闲的分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销,如果每次都从上次查找结束的位置开始检索,就能解决上述问题
缺点:可能导致大分区被使用了,后续如果有大进程进入的话就装不下了
❄️最佳适应算法
每次使用后都要将将空闲的分区从小到大依次排序,每次分配时顺序查找空闲分区链,找到大小能满足的要求的第一个空闲分区(因为要尽可能地留下大片的空闲区域给大进程,所以优先使用小进程)
缺点:每次使用都会留下更小的内存块,使用这种方法会产生很多外部碎片
❄️最坏适应算法
为了不留下小碎片,每次分配使用最大的空闲区,每次使用后都要将将空闲的分区从大到小依次排序
缺点:导致后来的大进程没有大分区可用了
❄️总结
⭐️分页存储管理实现对内存的分配与管理(属于非连续分配)
连续反配方式有很多缺点
比如静态连续分配会产生许多内部碎片,动态连续分配会产生许多外部碎片,所以就出现了非连续的分配管理方式
❄️基本分页存储
就是把固定分区分配改造成非连续分配
⛅️基本概念
将内存空间分为一个个相等的分区(每一个分区就叫做一个页框,或者成页帧,内存块等),每一个页框有一个编号,即页框号
将用户进程分为与页框大小相等的一个个区域,成为页,每一个页面也有一个编号称为页号
即让页框和页一一对应,但是最后一个页可能没有页框那么大,所以页框不能设置太大,否则可能产生过大的内部碎片
⛅️如何实现地址的转换
和动态运行时装入类似(绝对装入,动态运行时装入,静态装入)
比如内存中分了3页,第1页对应在内存中的其实物理地址为100,然后算出内存单元在1页的哪一位就可以算出该内存单元在内存中的物理地址
❄️总结
内存分为:页框
进程分为:页
页的大小=页框的大小
页表:又称页面映像表,存储在内存中,通过页表建立页(面)与物理块的索引。
页表项:在页表中,一个页号与其对应的物理块号称之为一个页表项(只存储了页框号)
⭐️分页管理中如何实现地址转换
❄️例题
1.页号2对应的内存块好b=8:表示页号2存在,且对应在物理内存中起始地址为第八个内存块的气质地址
2.页内偏移量占10位:每一个页面的大小为210B
❄️总结
⭐️分页管理中的快表(类cache)
❄️局部性原理
⛅️时间局部性
如果执行了程序中的某条指令访问了a这个数据,那么不久后这条指令很有可能再次执行并再次访问a这个数据
⛅️空间局部性
如果程序访问了某个存储单元,在不久之后,其附近的存储单元也有可能被访问(比如说数组)
❄️快表
为什么引入快表:每次访问一个逻辑地址时都会查询内存中的页表,由于局部性原理,可能连续很多次都查到了同一个页表项
定义:快表是一个访问速度比内存快很多的高速缓冲处理器,用来存放当前访问的若干页表项,加速地址变换的过程,与此相对,内存中的页表就称为慢表
类似于高速缓存和内存
过程:
只需要1步访存
如果没有找到对应的页号,就要
1.先从内存的页表中找到页号并拼接成物理地址
2.找到这个物理地址对应的内存单元
所以需要2步访存
❄️总结
⭐️两级页表
❄️单级页表的不足
⛅️页表占据的连续空间太大
⛅️进程运行时并不需要整个页表在内存中
因为进程在一段时间内只需要访问某几个特定的页面,可以使用虚拟存储技术(后面讲)
❄️解决思路
和内存分组类似,我们将内存分为很多页,每一页对应内存中页框(内存块),那么页表也可以进行分页,让每一组也对应一个内存块
比如上面的题中,每个页面能存放1k个分组,因此将1k个页表项分为1组,每一组占用一个内存块,然后将各个内存块离散的发放到各个内存块中
为了知道每个分组放的位置在哪儿,所以需要为页表建立一个“页表”,成为页目录表
❄️如何地址转换
偏移量有错?
好像是4095
重点:多查几次表而已
⛅️例题:
注意:各级页表的大小不能超过一个页面(防止页表项放不下的问题)
❄️访存次数
内存空间利用率上升(为什么,是因为不需要的连续空间所以内存的利用率上升吗?)的代价就是逻辑地址变化的时候需要更多的时间
❄️总结
⭐️分段式存储方式
❄️分段
按照程序自身的逻辑关系划分为若干个段,每个段在内存中占据连续空间,但是各个段之间可以不相邻
❄️格式
分为段号,段内地址
段号:决定了每一个进程可以分成几个段
段内地址:决定了每个段的最大长度是多少
❄️段表
程序分为了多个段,因为是离散的装入内存的,所以需要一个段表来记录每一个段存储的位置信息,所以每个进程建立了一张映射表:段表(段号-段长-基质)
页表:页号-页框号
之所以相比于页表多了一个段长是因为,页表中页面大小和页框大小是一样的,但是每个段的大小不一定相同,但是每一个段表项的大小是相同的
❄️过程
❄️和分页管理的区别
⛅️1.页是信息的物理单位
主要目的是为了实现离散分配,提高内存的利用率,由系统管理,对用户不可见
⛅️2.段是信息的逻辑单位
主要目的是更好的满足用户需求,分段对用户是可见的
⛅️3.分段是二维的,分页是一维的
⛅️4.分段比分页更容易实现进程的共享和保护
❄️总结
⭐️分段式管理和分页式管理的结合
❄️分段式+分页式=段页式
结合了分段管理的方便用户需求和分页管理的增大内存利用率的优点,将各个进程先分块再对每一个块儿进行分页
在段式管理中,逻辑地址结构由段号和段内地址组成:
段表由段号-段长-段起始地址组成
在段页式管理中,逻辑地址组成为
段页表由段号-页表长度-页表存放地址
页表由页号-页内偏移量组成
❄️过程
1.得到段号,页号,页内偏移量
2.判断段号是否越界
3.若没有越界则找到对应的段表项第一次访存,段表项的存放地址为段表项存放的初始地址+段表项长度*段号
3.检查页号时候越界,若不越界则取出页号对应的地址块第二次访存
4.去该地址快中找到对应的逻辑地址第三次访存
⭐️用虚拟内存来扩充内存容量
❄️传统类型的存储管理方式
1.连续分配
单一分配
固定分配
动态分配
2.非连续分配
分页式
分段式
段页式
⛅️特点
1.一次性
作业必须一次性装入内存中才能运行,当作业很大时,不能一次性全部装入内存,所以大作业无法运行
当有多个作业同时运行时,内存无法容纳多个作业,所以并发性低
2.驻留性
== 一旦作业被装入了内存就会一直驻留在内存中==,导致了就算某个时间不用该作业的部分内存,但是内存资源依然被该作业占据浪费
❄️高速缓冲技术
⛅️原理
将近期经常会用到的数据放到更高速的存储器中,暂时用不到的数据放在低速存储器中
❄️操作系统如何实现虚拟内存
1.进程装入时,操作系统会选择近期要用到的程序放入内存,暂时不用的放在外存
2.进程执行时,当所要访问的数据不在内存中时,由操作系统负责将所需要的信息从外存调入内存
3.内存空间不够时,由操作系统负责将内存中暂时不用的数据换到外存
系统的实际物理内存是没有变的,但是呈现给用户的内存大小远大于实际内存大小
⛅️适用于非连续分配方式
在连续分配方式中,各个进程的地址是连续的,虚拟内存进行调度时难以保证调进来的进程连续性,所以采取非连续性的分配方式