一、操作系统基本概念
操作系统(系统软件)
控制和管理整个计算机的硬件与软件资源**(系统资源管理者,安全、高效)**
提供给用户和其他软件方便的接口与环境 (向上层提供方便易用的服务),用户无需关心底层实现原理,只需向操作系统发出命令即可。
一个程序需要被放到内存中才能被CPU处理(内存是CPU唯一可直接接触的区域?)。
操作系统功能
处理器管理
存储器管理
文件管理
设备管理
操作系统提供服务
用户直接使用:
- GUI(图形化用户接口)
- 联机命令接口/交互式命令接口(用户说一个,系统做一个)
如:cmd - 脱机命令接口/批处理命令接口(用户说一堆,系统做一堆)
如:运行.bat文件
用户不能直接使用:
4. 程序接口:用户无法直接调用,可以用过程序代码进行系统调用的方式来间接使用。
操作系统目标
实现对硬件机器的扩展(没有操作系统的只能叫裸机)
操作系统特征
并发:多个事情在同一时间间隔内发生。(宏观上同时发生,微观上交替发生)
并行:多个事情在同一时刻发生。
单核CPU只能并行一个程序,各个程序只能并发。
X核CPU表示可以并行执行X个程序。
互斥共享:一个时间段只允许一个进程访问。
同时共享:一个时间段多个进程并发使用。
并发性与共享性互为存在条件
虚拟:将物理上的实体(实际存在)变为若干逻辑上的对应物(用户感受到的)。
空分复用(虚拟存储技术)
时分复用(并发,虚拟存储器)
没有并发性就没有虚拟性
异步:在多个程序并发运行时,由于资源有限,进程执行并非一贯到底,而是阻塞(等待系统资源被分配)后继续运行,以不可预知的速度前进。
只有系统拥有并发性才有可能拥有异步性
操作系统历史
一:手工操作阶段(将操作写在纸带机上后(长时间),让机器运行(短时间),再用纸带机输出(长时间))
用户独占全机,人机速度矛盾,资源利用率低
二:批处理阶段
- 单道批处理(多个程序员将纸带用外围机存到磁带里,计算机读取磁带,用监督程序来控制输入输出)
内存同时只能有一个程序,CPU大量的时间在空闲等待I/O完成 - 多道批处理(CPU计算时内存再读入,利用率提升)
无法优先处理紧急任务,每个任务优先级相同。
三:实时操作系统(能优先响应紧急任务而无需排队)
硬实时系统:严格的在规定时间内完成处理
软实时系统:能偶尔接受违反时间规定
四:网络操作系统:实现各类资源共享与各种计算机之间的通信。
五:分布式操作系统:分布性、并行性,多台计算机协同完成。
六:个人计算机操作系统:eg:Windows XP等。
操作系统运行机制
指令:(二进制机器指令):CPU能处理、识别的最基本的指令。
代码编译为机器指令(二进制)后由CPU执行。
内核程序:实现操作系统
CPU指令:
- 特权指令(1,内核才能调用,内核态/核心态/管态)
- 非特权指令(0,用户态/目态)
程序状态字寄存器存储CPU状态
改变CPU状态的指令是特权指令!
当CPU处于用户态出现非法指令时会产生中断。
CPU检测到中断后会变为核心态,停止当前运行的应用程序,并拒绝执行特权指令。然后执行处理中断的内核程序。
中断是操作系统夺回CPU使用权的唯一途径!
中断分类
- 内中断(异常,与当前指令有关,源于CPU内部)
故障(可能被内核程序修复)
陷入指令/访管指令(应用程序请求系统调用,不是特权指令!)
终止(致命错误无法修复) - 外中断(中断,与当前指令无关,源于CPU外部)
时钟中断(定时器)
I/O中断请求(输入输出结束)
中断原理
根据中断信号不同,在中断向量表中来找到对应进程在内存的位置
系统调用(获得内核提供的服务)
与库函数的区别:系统调用是更为底层的东西,部分库函数需要系统调用
(如创建新文件)
凡是和**共享资源有关(存储分配,I/O操作,文件管理)**的操作都需要系统调用,防止用户非法操作,增加稳定性与安全性。
设备管理
文件管理
进程控制
进程通信
内存管理
调用过程:
传参指令(调用参数)->陷入指令(用户态)->处理相关(核心态)->返回应用程序
大内核(操作系统最核心的部分)
- 时钟管理(实现计时功能)
- 中断处理(实现终端)
- 原语(具有原子性,最底层部分,运行时间短,调用频繁,执行时不可被中断)
- 对系统资源进行管理(进程、存储器、设备)
1~3属于微内核
微内核需要频繁被切换,消耗大
二、进程管理
进程概念
程序是静态的可执行文件
进程是动态的,程序的一次执行过程(系统进行资源分配调度的独立单位,每个进程内部内存空间独立)
进程在内存被创建时会分配一个唯一的PID,CPU再根据进程中的指令执行。
系统会记录进程被分配了哪些资源与运行情况,存入进程控制块(PCB)中
PCB是进程存在的唯一标志!
- PCB
- 程序段(程序代码)
- 数据段(程序变量)
进程特征
- 动态性(基本特征):动态产生、变化、消亡
- 并发性:内存中有多个进程实体并发运行
- 独立性:独立运行、获得资源、调度的基本单位
- 异步性:各进程异步执行,操作系统提供“进程同步机制”,但并发会导致结果的不确定性
- 结构性:每个进程都有PCB
进程状态转换(用原语完成,否则会导致关键数据信息不统一)
进程修改状态时,会先将状态码改变,最后放入相应队列。
原语的实现
采用关中断指令与开中断指令两个特权指令来决定是否检查中断信号
进程创建(创建原语,创建态->就绪态)
- 申请PCB
- 分配资源
- 初始化PCB
- 将PDB插入就绪队列
引起进程创建
- 用户登录(用户登录成功时系统会建立新进程,所有该用户下的进程为其子进程)
- 作业调度(新的作业放入内存时,会为其新建一个进程)
- 提供服务(用户向操作系统提出部分请求时,系统会新开一个进程处理该请求)
- 应用请求(用户进程请求创建子进程)
进程终止(撤销原语,运行态->终止态)
- 找到需要被终止的PCB
- 若进程正在运行,剥夺其CPU,将CPU分配给其他进程。
- 终止其所有子进程
- 将该进程拥有的资源返还给父进程或操作系统
- 删除PCB
引起进程终止
- 正常结束(进程自己请求终止,exit)
- 异常结束(整数除以0,非法使用特权指令,被操作系统kill)
- 外界干预(用户主动kill进程,如任务管理器)
进程阻塞(阻塞原语,运行态->阻塞态)
- 找到需要阻塞的PCB
- 将PCB设置设置为“阻塞态”,暂时停止其运行
- 将PCB插入等待队列
引起进程阻塞
- 需要等待系统分配资源
- 需要等待其他合作的进程完成工作
进程唤醒(阻塞态->就绪态)
- 找到PCB
- 将PCB从等待队列移除,并变为“就绪态”
- 将PCB插入就绪队列,等待被调度
引起进程唤醒
- 进程原本等待的事件发生(阻塞与唤醒成对出现)
进程切换(运行态->就绪态,就绪态->运行态)
- 将进程上下文存入PCB
- PCB移到响应队列
- 选择另一个进程运行,并更新其PCB
- 根据PCB恢复进程所需的进程上下文(配合CPU中寄存器完成)
(将原本执行的进程踢出,换为其他的,因此两个状态的转换成对出现)
引起进程切换
- 当前进程时间片已到
- 有更高优先级的进程到达
- 当前进程主动阻塞
- 当前进程终止
进程通信
进程不能直接访问另一进程的地址空间
共享存储(操作系统提供)
两个进程对共享空间的存储必须是互斥的(不能互相影响,通过系统内部工具实现,操作系统只提供共享空间与同步互斥工具)
基于数据结构的共享(低级通信方式,速度慢限制多)
基于存储区的共享
管道存储(内存中开辟的大小固定的缓冲区)
- 只能采用半双工通信(某段时间内只允许单向传输,若要实现双向通信则需要两个管道)
- 各进程要互斥地使用管道
- 数据以字符流的形式写入,
管道满时,写入会被阻塞。
管道空时,读入会被阻塞。 - 管道没满不能读,没空不能写。
- 数据一旦被读出就会被管道抛弃(读数据只能有一个)
消息传递
消息:进程间数据交换的单位,利用原语(发送原语、接收原语)完成数据交换。
消息组成
消息头:发送进程ID,接受进程ID,消息类型,消息长度等信息。
消息体:具体信息
直接通信方式(消息直接发送到接收进程的消息缓冲队列上)
间接通信方式/信箱通信方式(发动到中间实体(信箱),再发动到接收进程)
线程(程序执行流最小单位,CPU执行单元)
进程包含多个线程,同进程的各个线程可以互相并发,提高系统的并发性,系统开销减小
进程为除CPU之外的系统资源的分配单元
线程拥有就绪、阻塞、运行三个状态
每个线程也有唯一ID,线程控制块(TCB)
同进程下的不同线程可以共享进程资源
切换同进程下的线程开销很小,切换进程开销大。
多线程模型
线程的实现方式
用户级线程(用户看得见的进程,早期系统不支持线程只支持进程)
- 用线程库实现管理
- 线程切换在用户态下即可完成,无需系统干预
- 从内核的角度来看,实际上并没有线程
优点:不需要切换到核心态再切换线程,开销小
缺点:一个被线程被阻塞后整个进程都会被阻塞,并发度低
内核级线程(操作系统支持的线程,处理机分配的单位)
- 由操作系统内核实现管理
- 线程切换需要在核心态下完成
- 每个线程都会有TCB,从内核的角度来看,是确实存在的
优点:当一个线程被阻塞后,别的线程可以继续执行,并发度高
缺点:每次切换都需要先切换到核心态,开销大
多线程模型(结合用户级线程与内核级线程)
一对一模型(一个用户级线程映射到一个内核级线程)
优点:一个线程被阻塞后,其他线程还可以继续执行,并发度高
缺点:一个进程会占用多个内核级线程,切换时开销大
多对一模型(多个用户级线程映射到一个内核级线程)
用户级线程映射到一个线程库,再由线程库映射到内核级线程。
优点:线程切换不需要切换到核心态,开销小
缺点:当一个线程被阻塞后,整个进程都会被阻塞,并发度低
多对多模型(n个用户级线程映射到m个内核级线程,n>=m,最优)
克服了多对一中并发度低、一对一中开销大的缺点。开销小、并发度高
处理机调度(修改任务处理的顺序来保证开销资源的节省)
作业:一个具体的任务
用户向系统提交作业->用户让操作系统启动一个程序(处理具体的任务)
高级调度(作业调度,面向作业)
根据一定原则从作业后备队列中挑选出一个调入内存并创建进程,每个作业只调入一次(建立PCB),调出一次(撤销PCB)
低级调度(进程调度/处理机调度,最基本的调度,频率很高)
根据某种策略从就绪队列中选取进程,将处理机分配给他
狭义:选出要运行的进程
广义:选择进程+进程切换(让另一个进程占用处理机,原进程各类数据的保存、对新进程的各类数据的恢复)
中级调度(内存调度,频率比高级调度高,对象进程)
内存不足时,将部分进程数据调出至外存(挂起状态,PCB进入挂起队列),等到内存空闲时在调入
进程调度的时机
1.当前进程主动放弃处理机(部分系统只允许程序主动放弃处理机)
如正常终止、异常终止、主动阻塞(如等待I/O)
2. 当前进程被动放弃处理机
如时间片用完、优先级更高的进程进入就绪队列、更紧急的时间需要处理(如I/O中断)
在处理中断、进程在内核临界区、执行原语时不可调度!
注意!内核程序临界区和临界区是有区别的!
进程在普通临界区中是可以进行调度、切换的。
前者:访问某种内核数据结构(如进程的就绪队列)
后者:访问临界资源(一个时间段只允许一个进程访问的,各进程需要互斥地访问)的代码
进程调度方式
非抢占式(只允许主动放弃处理机)
实现简单,开销小但无法及时处理紧急任务,适合早期批处理系统
抢占式(处理机会被分配给更重要紧急的进程)
调度算法(用于作业/进程)
调度算法评价指标
CPU利用率:忙碌时间/总时间
系统吞吐量:完成作业数量/花费时间
周转时间:从作业被提交开始,到被完成为止的时间
平均周转时间:所有作业周转时间之和/作业数
带权周转时间:作业周转时间/实际运行的时间(必然≥1,时间越小满意度越高)
平均带权周转时间:所有带权周转时间/作业数
等待时间:作业处于等待被服务状态的时间之和
(I/O完成的时间不算在内,但要考虑作业在外存后背队列的时间)
响应时间:用户提交请求到首次响应的时间
先来先服务(FCFS,非抢占,不会导致饥饿)
根据作业到达的先后顺序进行服务
优点:公平、实现简单
缺点:带权周转时间很大,对长作业有利,短作业不利
短作业优先(SJF,抢占&非抢占,默认是非抢占式,会产生饥饿(一直不被调度到))
服务时间较短的作业优先得到服务
优点:平均等待/周转/带权周转时间更低,抢占式的比非抢占式的更低
所有进程几乎同时到达时,SJF的平均等待时间、平均周转时间最少
缺点:对短作业有利、对长作业不利
(1)非抢占
(2)抢占
HRRN(高响应比优先,非抢占,不会饥饿)
响应比:(等待时间+要求服务时间)/要求服务时间(必定≥1)
高响应比的作业优先
优点:综合FCFS、SJF的优点
时间片轮转(RR,只用于进程调度,抢占式,常用与分时操作系统,不会饥饿)
每隔一段时间,轮流地为各个进程服务,强制获取控制权,利用时钟中断实现
如果时间片太大,会退化成FCFS,因此时间片不能太大。
如果时间片太小,进程切换过于频繁,开销大,因此时间片不能太小。
时间片要让切换进程的开销占比不超过1%
把已经到达的进程放入就绪队列、每次转移控制权时将前一个进程置于队尾
优点:公平、响应快、适用于分时系统
缺点:进程切换有开销、不区分任务的紧急程度。
优先级调度(抢占&非抢占,会饥饿)
优先级高的优先被调度
优先级分为静态和动态
通常,系统进程高于用于进程,前台进程高于后台进程。
优先数和优先级的大小看题目而定!!!
优点:可以灵活调整各类作业的偏好程度
缺陷:若有许多高优先进程到来,可能导致饥饿
(1)非抢占
(2)抢占
多级反馈队列调度算法(只用于进程调度,其他调度算法的折中权衡,抢占式,可能会饥饿)
设计多级就绪队列**(优先级从高到低,时间片对应着由小到大)**
新进程先进入第一级队列,按FCFS排队等待被分配时间片,若用完时间片仍未结束则进入下一级队列队尾。若此时已是最下级的队列则进入队尾。
只有第k级队列为空时才会为第k+1级队头的进程分配时间片
优点:各类调度综合
进程同步
由于进程本身有异步性,同步即是制约多个进程的工作次序。
进程互斥
进程互斥:一个时间段里只能允许一个进程访问临界资源
临界区是访问临界资源的代码段
进入区与退出区是负责实现互斥的代码段
如果没有互斥,部分进程的内容会混淆在一起
互斥实现方法
单标志法(将进入临界区的权限只能由前一个进程赋予后一个)
用一个变量来表示当前允许进入临界区的进程号
但有可能出现允许进入的编程却不进入的现象
双标志先检查(设置数组表示该进程是否想要进入临界区,先“检查”后“上手”)
每个人进程在进入临界区之前先检查其他进程是否需要进入临界区,如果无,则将自己设为true,之后开始访问
如果进程并发,可能会出现多个进程想同时使用临界区。
“检查”与“上锁”不是一气呵成的
双标志后检查(先“上锁”后“检查”,但可能产生饥饿现象)
PeterSon算法(结合双标志、单标志的,会产生忙等现象,不会产生饥饿)
如果多个进程想进入,优先让对方用,在彼此都“谦让”后(只会“谦让”一次),由第一个先“谦让”的进程再开始调度
硬件实现进程互斥
中断屏蔽(期间不可被中断)
简单高效,但不适用于多处理机,只适用于操作系统内核进程
TestAndSet(TS/TSL指令)
实现简单,但会产生“忙等”
old记录是否已被上锁,
再将lock设为True。
检查临界区是否已被上锁
Swap指令(Exchange/XCHG指令)
信号量(实现进程互斥、进程同步,用原语实现)
信号量表示系统中某种资源的数量(用变量实现)
wait(S) P操作 进入区、申请资源,S–
signal(S) V操作 退出区、释放资源,S++
整型信号量(存在“忙等”)
拥有三种操作:初始化、P操作、V操作
“检查”与“上锁”一气呵成,避免并发、异步导致的问题
但会发生 “忙等”
记录型信号量(用数据结构存储,不会忙等)
剩余资源数量+等待队列
当wait时发现资源不够,会阻塞
当signal时发现资源不够,会唤醒
信号量实现进程互斥(互斥信号量mutex)
设置互斥信号量mutex,初值为1,表示进入临界区的名额
不同资源需要不同信号量
在进入区P(mutex)来申请资源
在退出去V(mutex)来释放资源
信号量实现进程同步(实现“一前一后”,同步信号量S)
设置同步信号量S,初始为0
在前操作后执行V操作
在后操作前执行P操作
信号量实现进程先驱关系
本质每一个前驱关系就是一个进程同步
生产者消费者问题(互斥&同步、两对同步关系)
多生产者-多消费者问题
如果缓冲区大小为1的话,有可能不需要互斥信号量 Mutex。
如果缓冲区大小大于1的话,必须要互斥信号量Mutex。
吸烟者问题(生产者可生产多种产品)
读者写者问题
哲学家进餐问题(解决死锁问题)
管程(更方便地实现互斥与同步,封装)
管程组成
- 局部于管程的共享数据结构说明:
- 对该数据结构进行操作的函数/过程
- 对共享数据设置初始化的语句
- 管程有一个名字
管程基本特征
- 局部于管程的数据只能被该管程访问
- 一个进程只有通过调用管程内的函数才能进入管程访问共享数据
- 每次只允许一个进程在管程内执行(管程每次只开放一个函数,且只有一个进程/线程可以进入,由编译器实现)
- 可以在管程中设置条件变量与等待/唤醒操作来解决同步问题
Java中用Synchronized描述后,该函数只能同一时间段内被一个线程调用
死锁
死锁、饥饿、死循环的区别
死锁:各进程(两个及以上)等待对方拥有的资源,各进程都阻塞,无法推进(资源分配错误)
饥饿:进程长时间得不到想要的资源,导致无法推进(资源分配错误)
死循环:进程执行时跳不出循环(代码逻辑错误)
死锁产生条件
- 互斥:互斥会产生资源争抢
- 不剥夺:资源在进程使用结束之前,不可被其他进程强制夺走
- 请求和保持:每个进程在拥有资源的同时有提出新的资源请求
- 循环等待:在资源等待链中每个进程已获得的资源同时被下一个进程所请求
发生死锁时必定有循环等待!但发生循环等待时不一定死锁!
(比如循环等待但是可使用的资源充足,如果可使用的资源仅满足最低需求,但循环等待就是充要条件)
死锁发生的情况(对不可剥夺资源的不合理分配)
- 系统资源竞争(对不可剥夺的资源)
- 进程推进顺序非法(请求与释放资源的顺序不当)
- 信号量使用不当
死锁应对策略
预防死锁(静态策略,不允许死锁发生,打破四个必要条件中的一个或多个)
破坏互斥条件(将只能互斥使用的资源改为共享使用,如SPOOLing技术)
并非所有设备可以破坏互斥(部分处于系统安全,不可破坏互斥)
很多时候无法破坏互斥!
破坏不剥夺条件
一:进程无法得到想要的资源时,强制让进程释放已有的资源(可能导致饥饿)
二:让操作系统将进程资源强行剥夺
- 实现复杂
- 可能导致前一阶段工作的失效(适用于易保存易恢复的资源,如CPU)
- 增加系统开销,降低吞吐量
破坏请求与保持条件(静态分配)
进程运行前申请完所有其需要的资源,否则不运行
实现简单,但资源利用率极低,同时也可能导致饥饿
破坏循环等待条件(顺序资源分配)
给系统资源编号,只能根据编号递增顺序来请求资源。
拥有小编号的进程可以申请大编号的进程,但反过来不可以。
不便于增加新设备,会打乱编号
使用资源顺序与编号顺序不一致,导致资源浪费
必须按顺序申请资源,不方便
避免死锁(动态策略,不允许死锁发生,用某种方法使系统进入不安全状态来避免)
如果系统按照某种顺序分配资源,每个进程都能顺利完成(进程在完全获取到自己想要的资源并结束使用后会归还资源),则该序列为安全序列,系统处于安全状态,安全序列可能有多个
安全状态下一定不会发生死锁,不安全时有可能会发生死锁!
银行家算法(预先判断分配是否会使系统进入不安全状态)
将多种资源抽象为n维向量
死锁的检测与解除(允许死锁发生)
死锁检测(依次消除与不阻塞进程相连的边直到无边可消)
一:使用某种数据机构保存资源请求与分配信息
二:提供算法利用上述信息来检测是否进入死锁
将进程与资源试做节点,请求和分配看作边,可以建成资源分配图
找到既不阻塞又不是孤点的进程Pi (至少有一条边与其相连,且资源请求数量小于已有空闲资源数量),消去其所有请求边与资源分配边,使之成为孤点。
如果所有的边都能消除,这该图是可完全简化的,不会发生死锁,若不能消除,则该进程进入死锁
(出现死锁时,并非所有进程都是死锁状态)
死锁解除
资源剥夺:挂起(暂时放到外存)死锁进程并抢占其资源,分配给其他死锁进程(要保证被挂起的进程不会饥饿)
撤销/终止进程:强制撤销部分,甚至全部死锁进程,并剥夺其资源,实现简单,代价很大。
进程回退:让死锁进程回退到避免死锁的状态
死锁进程选择:
- 优先级
- 已执行时间
- 完成还需要的时间
- 是交互式还是批处理式