操作系统(OS)是控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配;以提供给用户和其他软件方便的接口和环境;它是计算机系统中最基本的系统软件。
操作系统的功能:
我们通过使用QQ软件的例子来体会操作系统的功能:
1.操作系统作为管理者需要做的事情
2.理解向上层提供方便易用的服务
封装思想:操作系统把一些丑陋的硬件功能封装成简单易用的服务,使用户能更方便地使用计算机,用户无需关心底层硬件的原理,只需要对操作系统发出命令即可。
那么具体提供方便易用的服务有哪些呢?
(1)GUI:图形化用户接口
用户可以使用形象的图形界面进行操作,而不再需要记忆复杂的命令,参数。
(2)命令接口
3.操作系统作为最接近硬件的层次
小结:
操作系统有 并发,共享,虚拟,异步 这四个基本特征,其中并发和共享是两个最基本的特征,二者互为存在条件。
(1)并发:指两个或多个事件在同一时间间隔内发生。在这些事件宏观上是同时发生的。
注意:这里通常与并行混淆,并行是指两个或多个事件在同一时刻同时发生。
(2)共享:即资源共享,是指系统中地资源可供内存中多个并发执行地进程共同使用。
两种资源共享方式:互斥共享方式与同时共享方式
互斥共享方式:系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源。
同时共享方式:系统中的某些资源,允许一个时间段内由多个进程同时对它们进行访问。所谓的同时往往是宏观上的,而在微观上,这些进程可能是交替地对该资源进行访问的(即分时共享)。
举个例子:
互斥共享方式:比如在使用QQ和微信视频,同一时间段内摄像头只能分配给其中一个进程。
同时共享方式:使用QQ发送文件A,同时使用微信发送文件B。宏观上看,两边都在同时读取并发送文件,说明两个进程都在访问硬盘资源,从中读取数据。微观上看,两个进程是交替着访问硬盘的。
并发和共享是两个最基本的特征,二者互为存在条件
(3)虚拟:是指把一个物理上的实体变为若干个逻辑上的对应物。物理实体(前者)是实际存在的,而逻辑上对应物(后者)是用户感受到的。
(4)异步:指多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。
小结:
程序是如何运行的:
注意:指令就是处理器(CPU)能识别,执行的最基本命令。
程序运行的过程其实就是CPU执行一条一条的机器指令的过程
内核是操作系统最重要最核心的部分,也是最接近硬件的部分
在CPU设计和生产的时候划分了特权指令和非特权指令,因此CPU执行一条指令前就能判断出其类型。
内核态与用户态
CPU既然能判断出指令类型,但是它怎么区分此时正在运行的是内核程序或者是应用程序呢?
其实,CPU有两种状态,即内核态和用户态
CPU中有一个寄存器叫程序状态字寄存器(PSW),其中有个二进制位,1表示内核态,0表示用户态。
内核程序在合适的时候主动让出CPU,从核心态切换到用户态,即将让应用程序在CPU上运行。一旦CPU处于用户态时,我们的应用程序将会在此用户态下运行,此时CPU会执行应用程序中一条又一条的指令。但是如果此时在应用程序中恶意植入了一条特权指令,那么此时CPU当执行这条特权指令时,会检查自己的PSW,发现自己依然处于用户态却要执行这条特权指令,那么这个非法事件会引发一个中断信号发送给CPU,使得CPU强行从用户态转成核心态,并且CPU会拒绝执行该特权指令以及暂停执行后面的其他指令。转而CPU会执行一个处理中断信号的内核程序。
换句话说,在发生中断信号之后,中断使操作系统内核再次夺回CPU的控制权,从用户态再次切换到核心态,操作系统会对引发中断的事件进行处理,处理完了再把CPU使用权交给别的应用程序。
我们知道在CPU上会运行两种程序,一种是操作系统的内核程序,一种是应用程序。内核程序是整个系统的管理者,在计算机启动时,CPU处于内核态,只是在合适的情况下,操作系统内核会把CPU的使用权主动让给应用程序。一个应用程序在上CPU运行之后,它就会一直运行下去,除非发送中断。
(1)中断的作用
中断是让操作系统内核夺回CPU使用权的唯一途径,使CPU由用户态变为内核态。
(2)中断的类型
中断分为内中断与外中断
我们来看内中断的例子:
执行陷入指令,意味着应用程序主动地将CPU控制权还给操作系统内核,系统调用就是通过陷入指令完成的
陷入指令是一种特殊的指令不是特权指令
我们再来看外中断的例子:
(3)中断机制的基本原理
不同的中断信号,需要用不同的中断处理程序来处理
当CPU检测到中断信号后,会根据中断信号的类型去查询中断向量表,以此来找到相应的中断处理程序在内存中的存放位置。
小结:
程序:是静态的,就是个存放在磁盘里的可执行文件,就是一系列的指令集合。比如在Windows中就是以后缀为.exe的可执行文件
进程:是动态的,是程序的一次执行过程
同一个程序多次执行会对应多个进程
进程的组成部分——(PCB)
那么既然操作系统是这些进程的管理者,它要怎么区分各个进程呢?
那么既然操作系统要记录这么多信息,那该如何整理呢?
这里需要明确的是PCB是给操作系统用的,而程序段,数据段是给进程自己用的。
什么是程序的运行?
PCB是进程存在的唯一标志!
进程的特征:
注意:
(1)进程是动态的,动态性是进程最基本的特征
(2)异步性会导致并发程序执行结果的不确定性
进程的状态——创建态,就绪态
(1)进程正在被创建时,它的状态是创建态,在这个阶段操作系统会为进程分配资源,初始化PCB。
(2)当进程创建完成后,便进入就绪态,处于就绪态的进程已经具备运行条件,但由于没有空闲CPU,就暂时不能运行。
进程的状态——运行态
在一个进程中可能会有很多个进程都处于就绪态,当CPU空闲时,操作系统就会选择一个就绪进程,让它上处理机运行。
如果一个进程此时在CPU上运行,那么这个进程处于运行态。换句话说,此时CPU会执行该进程对应的程序(执行指令序列)。
进程的状态——阻塞态
在进程运行的过程中,可能会请求等待某个事件的发生,如等待某种系统资源的分配,或者等待其他进程的响应。
在这个事件发生之前,进程无法继续往下执行,此时操作系统会让这个进程下CPU,并让它进入阻塞态。当CPU空闲时,又会选择另一个就绪态进程上CPU运行。
进程的状态——终止态
假设此时运行在CPU上的进程1已经运行结束了,在运行结束时会发出一个(exit)的系统调用,请求操作系统终止该进程。
此时该进程会进入终止态,操作系统会让该进程下CPU,并回收内存空间等资源,最后还是要回收该进程的CPU。当终止进程的工作完成之后,这个进程就彻底消失了。
进程状态的转换(图解):
进程的状态:
进程PCB中,会有一个变量state来表示进程的当前状态。如:1表示创建态,2表示就绪态,3表示运行态。为了对同一个状态下的各个进程进行统一的管理,操作系统会将各个进程的PCB组织起来。
链接方式与索引方式
(1)什么是进程控制?
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程,撤销已有进程,实现进程状态转换等功能。
简单的说:进程控制就是要实现进程状态转换
(2)如何实现进程控制?
进程实现进程不同状态的转换是需要用“原语”来实现的
那么思考一个问题,为什么进程控制(状态转换)的过程要一气呵成?
我们假设此时进程2等待的事件发生,则操作系统中,负责进程控制的内核程序至少做这样两件事:
(1)将PCB2的(state)设为1
(2)将PCB2从阻塞队列放到就绪队列
如果此时我们已经将PCB2的(state)设为1,如图:
但是恰好此时收到了中断信号,那么PCB2的(state)=1本来表示就绪状态却被放在阻塞队列里,这时如果不能一气呵成,就有可能导致操作系统中的某些关键数据结构信息不统一的情况,这会影响操作系统进行别的管理工作。所以这就是为什么进程状态的转换必须一气呵成!
(3)如何实现原语的原子性?
原语的执行具有原子性,即执行过程只能一气呵成,期间不允许被中断。可以用关中断指令和开中断指令这两个特权指令实现原子性。
CPU正常情况下运行:
CPU执行了关中断与开中断指令:
思考:如果这两个特权指令允许用户程序使用的话,会发生什么情况?
所以开中断与关中断是两条特权指令
(4)进程控制相关的原语
在没有引入进程之前,系统中的各个程序只能串行执行,换句话说我们如果想要一边听音乐,一边玩QQ是不可以的。在引入进程之后,我们可以一边听音乐,一边玩QQ。但是问题来了,QQ中许多的功能,比如视频,文字聊天,传送文件等,可我们知道进程是程序的一次执行,那么这些功能显然不可能是由一个程序顺序处理就能实现的。
因此我们引入了线程的概念:
传统的进程是程序执行流的最小单位,但在引入线程后,线程成为了程序执行流的最小单位。
同样,我们可以把线程理解为轻量级的进程
引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务,这时进程只作为除CPU之外的系统资源的分配单元
引入线程机制后,有什么变化?
线程的属性:
线程的实现方式与多线程模型
(1)用户级线程
很多编程语言提供了强大的线程库,可以实现线程的创建,销毁,调度等功能。
线程的管理工作由应用程序提供的线程库来完成的,并不是由操作系统来完成的。
其实线程切换是由应用程序的线程库自己完成的,在用户态下就可以完成线程切换,不需要操作系统的切换。
不能,只有用户才能感知到用户级线程的存在。
用户级线程的优缺点:
(2)内核级线程
内核级线程,由操作系统支持的线程
注意:内核级线程与用户级线程不同
内核级线程的优缺点:
多线程模型
之前我们讨论了用户级线程与内核级线程的优缺点,那么我们是否可以将这两种线程方式结合起来呢?
(1)一对一模型
(2)多对一模型
(3)多对多模型
总结:内核级线程与用户级线程
(1)用户级线程是代码逻辑的载体
(2)内核级线程是运行机会的载体
(3)内核级线程才是处理机分配的单位
(4)一段代码逻辑只有获得了运行机会才能被CPU执行
内核级线程中可以运行任意一个有映射关系的用户级线程代码,只有两个内核级线程中正在运行的代码逻辑都阻塞时,这个进程才会阻塞。
调度的基本原理:
当我们去银行取钱时,我们或许需要排队等待,作为普通客户,一般银行遵循着先到先服务的顺序。但是如果有VIP客户的话,他们未必需要排队就可以优先被服务。
当有一堆任务要处理,但由于资源有限,任务没法同时处理这就需要确定某种规则来决定处理这些任务的顺序,这就是调度研究的问题。
高级调度
作业:一个具体的任务
用户向系统提交一个作业 = 用户让操作系统启动一个程序(来处理一个具体的任务)
但是问题来了,我们的内存空间有限,有时无法将用户提交的作业全部放入内存,那该这么办呢?
低级调度
中级调度
内存不够时,可将某些进程的数据调出外存,等内存空闲或者进程需要运行时再重新调入内存。暂时调到外存等待的进程状态为挂起状态,被挂起的进程PCB会被组织成挂起队列。
进程调度的时机
我们知道进程调度(低级调度),就是按照某种算法从就绪队列中选择一个进程为其分配处理机。那什么时候进行进程调度与切换的情况?
(1)当前运行的进程主动放弃处理机
(2)当前运行的进程被动放弃处理机
临界资源:是指一个时间段内只允许一个进程使用的资源,各进程需要互斥的访问临界资源。
临界区:指访问临界资源的这段代码。
举两个例子:
(1)内核程序临界区访问的临界资源需要尽快的释放,不可以进行进程调度和切换
(2)普通临界区访问的临界资源时可以进行进程调度和切换
进程调度的方式
非剥夺调度方式实现简单,系统开销小但是无法及时处理紧急任务,适合于早期的批处理系统。
剥夺调度方式可以优先处理更紧急的进程,也可以实现让各进程按时间片轮流执行的功能,适合于分时操作系统,实时操作系统。
进程的切换与过程
进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程。
小结:
CPU利用率:是指CPU忙碌的时间占总时间的比例
来道例题:
系统吞吐量
系统吞吐量是指:单位时间内完成作业的数量
来道例题:
周转时间
周转时间是指:从作业被提交给系统开始,到作业完成为止的这段时间间隔
周转时间与平均周转时间
带权周转时间
对于周转时间相同的两个作业,实际运行时间长的作业在相同时间内被服务的时间更多,带权周转时间更小,用户满意度更高。
对于实际运行时间相同的两个作业,周转时间短的带权周转时间更小,用户满意度更高。
注意:
带权周转时间必然大于(等于)1
带权周转时间与周转时间都是越小越好
等待时间:指进程(作业)处于等待处理机状态时间之和,等待时间越长,用户满意度越低。
一个作业总共需要被CPU服务多久,被I/O设备服务多久一般是确定不变的,因此调度算法其实只会影响作业/进程的等待时间。
响应时间
响应时间:指从用户提交请求到首次产生响应所用的时间。
(1)算法思想:
(2)算法规则:
(3)用于作业(进程)调度:
(5)先来先服务调度算法概述:
二. 短作业优先调度算法(SJF)
(1)算法思想:
(2)算法规则:
(3)用于作业(进程)调度:
(4)是否可抢占:
例题:
使用非抢占式的短作业优先调度算法SJF
使用抢占式的短作业优先调度算法SRTN
使用短作业优先算法需要注意:
(5)短作业优先调度算法的优缺点:
(6)短作业优先调度算法概述:
对FCFS和SJF两种算法的思考:
FCFS算法是在每次调度的时候选择一个等待时间最长的作业(进程)为其服务。但是没有考虑到作业的运行时间,因此导致了对短作业不友好的问题。
SJF算法是选择一个执行时间最短的作业为其服务。但是又完全不考虑各个作业的等待时间,因此导致了对长作业不友好的问题,甚至还会造成饥饿问题。
那么,我们能不能设计一个算法,即考虑到各个作业的等待时间,也能兼顾运行时间?
三. 高响应比优先调度算法(HRRN)
(1)算法思想
(2)算法规则
(3)用于作业(进程)调度
(4)是否可抢占?
(5)高响应比优先算法优缺点
(1)算法思想
(2)算法规则
(3)用于作业(进程)调度
(4)是否可抢占?
(5)时间片轮转调度算法的优缺点
例题:
1.当时间片大小为2时:
时刻流程图:
(1)0-5时刻(2)6-11时刻(3)12-16时刻
2.当时间片大小为5时:
同一道例题,我们对比用先来先服务调度算法
- 我们发现:如果时间片太大,使得每个进程都可以在一个时间片内就完成,则时间片轮转调度算法退化为先来先服务调度算法,并且会增大进程响应时间。因此时间片不能太大。
- 如果时间片太小,会导致进程切换过于频繁,系统会花大量的时间来处理进程切换,从而导致实际用于进程执行的时间比例减少。可见时间片也不能太小。
(1)算法思想
(2)算法规则
(3)用于作业(进程)调度
(4)是否可抢占?
(5)优先级调度算法的优缺点
例题:
(6)优先级调度算法概述
思考:
六. 多级反馈队列调度算法(简述)
例题:
小结:
异步性是指:各并发执行的进程以各自独立的,不可预知的速度向前推进
什么是进程互斥?
进程的并发需要共享的支持,各个并发执行的进程不可避免地需要共享一些系统资源。
对临界资源地互斥访问,可以在逻辑上分为如下四个部分:
1.进入区 2.临界区 3.退出区 4.剩余区
注意:
临界区是进程中访问临界资源的代码段
进入区和退出区是负责实现互斥的代码段
为了实现对临界资源的互斥访问,同时保证系统整体性能,需要遵循以下原则:
小结:
问题描述:
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(这里的产品理解为某种数据)
1.整理思路
(1)生产者,消费者共享一个初始为空,大小为n的缓冲区。
如图:缓冲区的大小为5
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
(2)如果缓冲区已经满了,生产者进程依然向缓冲区里写数据,此时生产者进程必须等待,当缓冲区中有空的时候才能向缓冲区里写数据。
(3)只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
(4)缓冲区是临界资源,各进程必须互斥地访问。
2.关系分析
因为缓冲区是临界资源,各进程必须互斥地访问,所以我们还需要设置互斥信号量
4. 代码分析
执行V操作的进程会唤醒对应执行P操作的进程:
思考:能否改变相邻P,V操作的顺序?
小结:
问题描述:
有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致不一致的错误。
因此要求:
(1)允许多个读者可以同时对文件执行读操作
(2)只允许一个写者往文件中写信息
(3)任意写者在完成写操作之前不允许其他读者或写者工作
(4)写者执行写操作前,应让己有的读者和写者全部退出
注意:
1.关系分析
2.如何实现
潜在的问题:只要有读进程还在读,写进程就要一直阻塞等待,可能饿死。因此,这种算法中,读进程是优先的。
那么如何解决写进程饿死的状态呢,我们来看代码:
结论:在这种算法中,连续进入的多个读者可以同时读文件。写者和其他进程不能同时访问文件,写者不会饥饿,但也并不会是真正的写优先,而是相对公平的先来先服务原则。
读者写者问题为我们解决复杂的互斥问题提供了一个参考思路。其核心思想在于设置了一个计数器(count)用来记录当前正在访问共享文件的读进程数。我们可以用(count)的值来判断当前进入的进程是否是第一个/最后一个读进程,从而做出不同的处理。
另外,对(count)变量的检查和赋值不能一气呵成导致了一些错误,如果需要实现一气呵成,自然应该想到用互斥信号量。
问题描述:
一个圆桌上坐着5名哲学家,每两个哲学家之间的桌子上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
1.关系分析
2.整理思路
3.解决方案
chopstick[5]={1,1,1,1,1}
用于实现对5个筷子的互斥访问。并对哲学家按0-4
编号,哲学家i
左边的筷子编号为i
,右边的筷子编号为(i+1)%5
,每个哲学家吃饭前依次拿起左,右两支筷子。4.解决思路
(1)最多允许四个哲学家同时进餐
(2)在每个哲学家进餐之前先判断哲学家是奇数还是偶数
当然,我们还可以这样解决死锁问题:
我们可以规定仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子
我们用信号量mutex
,保证了哲学家拿筷子这件事必须互斥的进行。即使一个哲学家在拿筷子拿到一半时被阻塞,也不会有别的哲学家会继续尝试拿筷子。这样的话,当前正在吃饭的哲学家放下筷子后,被阻塞的哲学家就可以获得等待的筷子了。
什么是死锁?
(1)我们来看一个哲学家进餐的问题
每位哲学家都在等待自己右边的人放下筷子,这些哲学家进程都因为等待筷子资源而被阻塞,即发生死锁。
那么在并发环境下,各进程因竞争资源而造成的一种相互等待对方手里的资源,导致各进程都被阻塞,都无法向前推进的现象,就是死锁。
产生死锁必须同时满足一下四个条件,只要其中任一条件不成立,死锁就不会发生
互斥条件
只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子,打印机设备)。像内存,扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这些资源)。
不剥夺条件
进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
请求和保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
循环等待条件
存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
注意:
- 发生死锁时一定有循环等待,但是发生循环等待时未必死锁(循环等待是死锁的必要不充分条件)。
- 如果同类资源大于1,则即使有循环等待,也未必发生死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了。