个人博客:https://uesugier11.gitee.io/uesugi-er11/
现代计算机中的计算机硬件:输入设备+输出设备+存储器+运算器+控制器
操作系统(Operating System,OS):是管理计算机硬件与软件资源的系统软件,也是计算机系统的内核与基石。 操作系统处理管理与配置内存决定系统资源供需的优先次序控制输入与输出设备操作网络与管理文件系统等基本事务提供一个让用户与系统交互的操作界面具体的工作过程是首先由监督程序将磁带上的第一个作业装入内存,并把运行控制权交给作业;该作业处理完时,又把控制权交给监督程序,再有监督程序把磁带的第二个作业调入内存等等。可以看成是串行的。
优点:解决人机矛盾和CPU与IO设备速度不匹配问题,提高系统资源的利用率和系统吞吐量。
缺点:不能充分的利用系统资源,现很少使用。
在一台主机上连接了多个配有显示器和键盘的终端并由此所组成的系统,该系统允许多个用户同时通过自己的终端,以交互方式使用计算机,共享主机中的资源。
系统能及时响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致地运行。
并行:指两个或多个事件在同一时刻发生;
并发:指两个或多个事件在同一时间间隔内发生
具体地说:并发指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故在微观上这些程序是分时地交替执行。 若计算机系统有多个处理机,这些可以并发执行的程序便可以被分配到多个处理机上,实现并行执行。即利用每一个处理机来处理一个可并发执行的程序。
引入概念**【进程】**:指在系统中能独立运行 并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。
在一个没有引入进程的系统中,属于同一个应用程序的计算机程序和 I/O程序之间只能是顺序执行,也就是计算机程序执行告一段落后,才允许I/O程序执行;反之,在程序执行I/O操作时,计算程序也不能执行。-----------为计算程序和I/O程序分别建立一个进程(Process)后,这两个程序就可以 并发执行。
**并发和共享是OS的两个最基本的特征!没有并发和共享就谈不上虚拟和异步; **
虚拟和异步是依赖于并发特性的。
所谓虚拟(Virtual)是指通过某种技术把一个物理实体变成为若干个逻辑上的对应物。 物理实体是实际存在的东西,逻辑实体是虚的,它并不存在,但是用户却感觉它存在。 用于实现虚拟的技术称为虚拟技术,在操作系统中利用了两种方式实现虚拟技术:时分复用技术和空分复用技术。
时分复用技术概念:将资源在不同的时间片内分配给各进程以使该资源被重复利用,从而提高资源的利用率。如采用时分复用的虚拟处理机,能够在不同的时间片内处理多个用户的请求,从而使得用户感觉自己独占主机,而处理机在这期间也被充分的利用。
虚拟处理机技术:一台处理机,通过时分复用的方法,能实现同时(宏观上)为多个用户服务,亦即,利用多道程序设计技术,可将一台物理上的处理机虚拟为多台逻辑上的处理机 ---- 虚拟处理机。虚拟设备技术:通过时分复用的方法,将一台物理I/O设备虚拟为多台逻辑上的I/O设备,并允许每个用户占用一台逻辑上的I/O设备。这样可使原来仅允许在一段时间内由一个用户访问的设备(即临界资源),变为允许多个用户“同时”访问的共享设备
当一种资源在时间上复用时,不同的程序或用户轮流使用它。时分复用技术通过利用处理及的空闲时间运行其他程序,提高了处理机的利用率
实质上就是每次只把用户程序的一部分调入内存运行,运行完成后将该部分换出,再换入另一部分到内存中执行,通过这样的置换功能,实现了用户程序的各个部分分时地进入内存运行。
让同一个频段在不同的空间内得到重复利用。空分复用技术利用存储器的空闲空间分区域分存放和运行其他多道程序,以此来提高内存的利用率。
注意:采用分时复用技术,每台虚拟设备的平均速度必然等于或低于物理设备速度的1/N,同理,采用空分复用技术,一台虚拟设备平均占用的空间必然等于或低于物理设备所拥有空间的1/N
由于资源等因素的限制,使进程的执行通常都不可能“一气呵成”,而是以“走走停停”的方式运行。
对于内存中的每个进程,在何时能获得处理机执行,何时又因提出某种资源请求而暂停,以及进程以怎样的速度向前推进,每道程序总共需要多少时间才能完成等等,都是不可预知的。
进程是以人们不可预知的速度向前推进的,此即进程的异步性。
操作系统关于进程方面管理任务有如下几种:进程控制,进程同步,进程通信,调度
内存管理主要功能:内存分配,内存保护,地址映射,内存扩充
引题:
包含关系:
注意事项:
一次完整的交互过程可以分成以下四步:
客户发送请求消息服务器接收消息服务器回送信息客户机接收消息ps:经常会问到 在微内核OS中,为什么要采用客户/服务器模式?我们答客户/服务器模式的优点即可
微内核技术——把操作系统中更多的成分和功能放到更高的层次(用户模式)中去运行,而留下一个尽量小的内核,用它来完成操作系统最基本的核心功能。
足够小的内核:微内核≠一个完整的OS,含有: 与硬件处理紧密相关的部分一些较基本的功能客户和服务器之间的通信 基于【客户/服务器模式】应用"机制和策略分离"原理 机制:是指在实现某一功能时的具体规定或说原则策略:在机制的基础上,借助某些参数和算法,用以实现该功能的优化,或达到不同的目标 采用面向对象技术基于【机制与策略分离】的原理,将机制部分以及与硬件紧密相关的部分放入微内核中。由此微内核通常具有如下几个方面功能:
进程(线程)管理低级存储器管理中断和陷入处理事实上,只有不存在前趋关系的程序之间才有可能并发执行,否则无法并发执行
特征:
间断性:所谓间断性指的是,由于多个程序对资源的要求产生的制约性,而导致的某一个程序在运行时等待资源的情况。 比如有两个程序都需要使用打印机这个资源,如果其中的一个程序已经占用,而另一个必须等待。这样后者表现出来的就是程序运行时的间断性。失去封闭性:所谓失去封闭性指的是,由于多个程序并发执行会共享资源,从而导致各个程序运行环境会失去封闭性。不可再现性:所谓不可再现性是指相同的输入,由于资源的共享,导致最后的输出结果不同。程序:是静态的,是一个存放在磁盘里的可执行文件,是一系列的指令集合
进程:是动态的,是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
其中,进程实体包含三部分:程序段,相关的数据段和PCB。
程序进程关系:一个程序多次执行会对应多个进程,比如说QQ可以登录三个账号。那么既然都是QQ,那OS是怎么区分不同进程的呢?当进程被创建的时候,OS会为该进程分配一个唯一的,不重复的身份证——PID(Process ID,进程ID)
进程控制块(PCB):OS需要记录进程PID,分配了哪些资源,运行情况,那么这些信息需要记录到哪里呢?答案就是进程控制块——PCB。
作用:PCB的作用是使一个在多道程序环境下不能独立运行的程序(含数据)成为一个能独立运行的基本单位,一个能与其他进程并发执行的进程。下面是具体作用: 作为独立运行基本单位的标志:在进程的整个生命周期中,系统总是通过PCB对进程进行控制的,亦即系统是根据 进程的PCB感知该进程的存在的,所以,PCB是进程存在的唯一标志。能实现间断性运行方式提供进程管理所需要的信息提供进程调度所需要的信息实现与其他进程的同步与通信进程状态有五种:创建状态,就绪状态,执行状态,阻塞状态,终止状态;其中就绪、执行、阻塞是三种基本状态。
终止态:即一个进程达到了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止状态。
引起进程状态转换的具体原因如下:
NULL→新建态:执行一个程序,创建一个子进程。
新建态→就绪态:当操作系统完成了进程创建的必要操作,并且当前系统的性能和虚拟内存的容量均允许。
就绪态→运行态:进程被调度
运行态→终止态:当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结。
运行态→就绪态:运行时间片到;出现有更高优先权进程。
运行态→阻塞态:等待使用资源;如等待外设传输;等待人工干预。
阻塞态→就绪态:申请资源被分配,或等待的事件已经发生了
就绪态→终止态:未在状态转换图中显示,但某些操作系统允许父进程终结子进程。
阻塞态→终止态:未在状态转换图中显示,但某些操作系统允许父进程终结子进程。
终止态→NULL:完成善后操作。
接下来,我们引入了两个操作:挂起和激活
当挂起操作作用于某个进程时,该进程将被将被挂起,意味着此时该进程处于静止状态;
假如进程正在执行,他将暂停执行。
假如进程原本就处于就绪状态,则该进程此时暂不接受调度。
与挂起操作对应的就是激活啦。
引入挂起后的进程状态转换五态模型:引入创建和终止——七态模型:
引起进程状态转换的具体原因如下:
等待态—→挂起等待态:如果当前不存在就绪进程,那么至少有一个等待态进程将被对换出去成为挂起等待态;操作系统根据当前资源状况和性能要求,可以决定把等待态进程对换出去成为挂起等待态。挂起等待态—→挂起就绪态:引起进程等待的事件发生之后,相应的挂起等待态进程将转换为挂起就绪态。挂起就绪态—→就绪态:当内存中没有就绪态进程,或者挂起就绪态进程具有比就绪态进程更高的优先级,系统将把挂起就绪态进程转换成就绪态。就绪态—→挂起就绪态:操作系统根据当前资源状况和性能要求,也可以决定把就绪态进程对换出去成为挂起就绪态。挂起等待态—→等待态:当一个进程等待一个事件时,原则上不需要把它调入内存。但是在下面一种情况下,这一状态变化是可能的。当一个进程退出后,主存已经有了一大块自由空间,而某个挂起等待态进程具有较高的优先级并且操作系统已经得知导致它阻塞的事件即将结束,此时便发生了这一状态变化。运行态—→挂起就绪态:当一个具有较高优先级的挂起等待态进程的等待事件结束后,它需要抢占 CPU,,而此时主存空间不够,从而可能导致正在运行的进程转化为挂起就绪态。另外处于运行态的进程也可以自己挂起自己。新建态—→挂起就绪态:考虑到系统当前资源状况和性能要求,可以决定新建的进程将被对换出去成为挂起就绪态。进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程,撤销已有进程,实现进程状态转换等功能;
简单来说,进程控制→实现进程状态转换。
简单的流程图:
与硬件紧密相关的模块、各种常用的设备的驱动程序以及运行频率较高的模块,安排在紧靠硬件的软件层次中,将它们常驻内存,通常被称为OS内核;也就是OS内核其实本质上是各个模块。
创建原语:
申请空白PCB为新进程分配所需的资源初始化PCB将PCB插入就绪队列哪里会用到进程创建呢?
用户登录作业调度提供服务应用请求终止/撤销原语:
从PCB集合中找到终止进程的PCB若该进程正在运行,立刻剥夺CPU,将CPU分配给其他进程终止其所有子进程将该进程所拥有的所有资源还给父进程或操作系统删除PCB哪里会用到进程终止呢?
正常结束:进程自己请求终止异常结束:非法使用特权指令外界干预:用户自己选择杀某进程被什么事件所阻塞就会被该事件所唤醒
切换原语:
将运行环境信息存入PCBPCB移入相应队列选择另一个进程执行,并更新其PCB根据PCB恢复进程所需的运行环境这里有一个地方:进程所需的运行环境是什么东西呢?
首先回顾一下我们程序是如何运行的:是通过把我们的代码保存到硬盘然后转入内存,然后CPU去从内存中读取一条条指令,然后我们的程序就跑起来了。那么CPU中有个东西 叫做寄存器。它们有不同的功能,比如可以存放下一条指令地址,也可以存放正在执行的指令,也可以保存暂时运算得到的结果;然而我们知道寄存器并不是很专一的,他有可能会随时被其他的进程使用,那我们之前运算的结果啊,什么保存的指令啊,岂不是都不见了吗?这个时候就会用到我们的PCB了,它能够保持关键的一些信息,也就是运行环境,这样等我们的寄存器又空闲下来了的时候,我们就可以继续我们未完成的指令啦。
首先,我们回顾一下我们之前学的进程异步问题,由于并发执行的进程以各自独立,不可预知速度向前推进,我们所得到的结果往往也会不一样,而我们往往有时候需要控制一个事件的发生在一个事件前,那么就需要进程同步啦
同步也称为直接制约关系,是指为了完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调他们的工作次序而产生的制约关系。
有进程同步就有进程互斥,那么进程互斥又是什么呢?
要明白互斥,就引入了一个概念:临界资源
临界资源,即一个时间段内只允许一个进程使用的资源。对于该资源我们必须互斥的访问,也就是我访问,你不能访问,反之亦然;因此,进程互斥,即指当一个进程访问某临界资源时,另一个想访问此临界资源的进程必须要等待,只有当我正在访问该资源的进程访问结束了,释放掉了,下一个进程才能去访问该资源。
那么我们一般是如何进行对临界资源的访问的呢?
进入区:负责检查是否可以进入临界区(上锁)临界区:访问临界资源的那段代码退出区:用于将临界区正被访问的标志恢复为未被访问的标志剩余区:做其他处理ps:临界区是进程中访问临界资源的代码段;进入区和退出区是负责实现互斥的代码段;临界区也被称为”临界段"
让硬件同步即为进程互斥的硬件实现方法:中断屏蔽方法,TestAndSet,Swap指令
简称TS指令,也有称TestAndSetLock指令,或TSL指令
用TS指令管理临界区的时候,为每个临界资源设置一个布尔变量lock;
用lock的布尔值就可以判断资源是否空闲,进程能否访问。
优点:实现简单;适用于多处理机的环境缺点:不满足让权等待有的地方称为Exchange指令,或XCHG指令;Swap指令用硬件实现,不允许被中断
Swap指令与TS指令在逻辑上其实差不多,但Swap需要两个参数,不需要返回值,TS需要一个共享的变量实现互斥,因此在不同的地方就要用不同的方式去进行进程互斥。
由于之前的措施方案无法实现进程的“让权等待"问题,因此我们引出了信号量这个概念进行解决;
ps:记录型信号量可实现进程互斥、进程同步;如果出现了P(S),V(S)的操作,除非默认说明,默认S为记录型信号量
现在P1,P2并发执行,由于存在异步性,二者交替推进次序无法控制,是不确定的;
假如我P2的代码4需要用到P1的代码1、2、3运行结果才能执行的话,我就得保证代码4是在代码3后执行;
此即进程同步问题:让本异步并发的进程互相配合,有序推进。
重点:在前操作之后执行V(S),在后操作之前执行P(S);
每一对的前趋关系都是一个进程同步的问题(为了保证一前一后的操作)
为了实现这个目标,我们需要做的事情
为每一对前趋关系各设置一个同步信号量在【前操作】的之后相对应的同步信号量执行V在【后操作】的之前相对应的同步信号量执行P代表资源的数据结构以及由对该共享数据结构实施操作的一组过程所造成的资源管理程序共同构成了一个操作系统的资源管理模块——管程。
管程是一种特殊的软件模块,由以下部分组成:
局部于管程的共享数据结构说明;对该数据结构进行操作的一组过程(就是函数)对局部于管程的共享数据设置初始值的语句【对数据结构初始化的语句】管程有一个名字发现了吗,管程和我们面向对象的类有点相似——可以定义一些数据,可以定义一些对这些数据操作的函数,对属性初始化的语句。
什么意思呢?通过1+2我们可以知道,假如我们要修改管程内的数据结构,我们只能够通过调用管程内封装好的方法(函数)去修改数据结构;而3则实现了进程互斥;
我们使用管程实现进程同步需要用到同步工具,如wait,signal;
但仅仅有这两个是不够的,我们知道,每次仅能有一个进程进入管程,假如某进程在管程中被挂起或阻塞就得等很久了,那么解决这问题就引入了——条件变量 condition。
怎么用呢?管程中对每个条件变量以说明**:condition x,y**;条件变量的操作是wait,signal;
每个条件变量保存一个链表,用以记录因为这条件变量所阻塞的所有进程,提供2个操作:
x.wait 和 x.signal
x.wait:正在调用管程的进程因为x条件需要被阻塞或者挂起,调用x.wait将自己插入到x条件的等待队列上,并释放管程,直到x条件变化。此时其他进程可以使用该管程——让出了入口。x.signal:若正在调用管程的进程发现x条件发生了变化,则调用x.signal,重新启动一个因x条件而阻塞或挂起的进程,如果存在多个这样的进程,则选择一个,如果没有,则继续执行原进程,而不产生任何结果。 注意了 这与信号量机制的signal操作不能混为一谈,信号量中的signal需要s++操作,信号量改变了。Java中,如果用关键字【synchronized】描述一个函数,那么这个函数同一个时间段只能被一个线程调用。
得出结论了:实现互斥的P操作必须放在实现同步的P操作之后;由于V操作并不会导致进程阻塞,因此两个V操作顺序可以交换;
注意 这里的多生产者的多 代表的不是数量 而是多个类型
以父亲和女儿举例:
父亲首先应该P盘子,查看是否为空,如果为空,则V苹果(苹果++);女儿首先P苹果,查看是否准备好了,有的话就取出,V盘子(盘子++);为了保证互斥,必须在P、V操作之中加入对互斥信号量mutex 的 P、V操作。
可不可以不要互斥信号量mutex?
由于缓冲区大小为1,因此orange,apple,plate三者中最多只有一个是1;这样的话最多只有一个进程的P操作不会被阻塞,可以顺利进入临界区,因此可以顺利执行;
那么假如缓冲区(plate)数量为2呢?
那么父亲和母亲都能访问盘子,有可能出现不同进程写入缓冲区的数据相互覆盖的问题;
得出结论:如果缓冲区的大小大于1,那么就必须专门设置一个互斥信号量mutex来保证互斥的访问缓冲区
以smoker1为例,首先P(offer1)检查是否有需要的组合1,有的话就拿走去抽,并且告诉供给者抽完了,然后供给者i++,p一下finish,并将下一个组合放在桌上然后v下一个组合;
然而我们发现 这样子会导致死锁的现象
按照之前的方法会发生死锁现象,那我们怎么解决呢?
有三种方法:
最多允许四个哲学家同时进餐,这样可以保证至少有一个哲学家是可以拿到左右两只筷子的要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子;偶数号哲学家相反。这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭的话,只有有其中一个可以拿起第一只筷子,而另一个因为拿不到第一个筷子就直接阻塞了。仅当一个哲学家左右两只筷子都可用时才允许他抓筷子【这也是后面的破坏请求和保持条件 方法】下面以第三种方法为例,进行解决
前提:两个进程对于共享空间的访问必须是互斥的。
基于数据结构共享:各进程公用某些数据结构,实现各进程的信息交换:如生产者-消费者问题中的有界缓冲区。是低级通信方式。基于存储区共享:在内存中画一片共享存储区,数据形式和存放位置由进程控制而非OS。是高级通信方式。进程通过【格式化的消息】为单位,将通信的数据封装在消息中,通过OS提供的发送接收原语,在进程间进行消息传递,完成进程的数据交换。是一种高级通信方式。
直接通信方式:发送进程利用OS提供的发送原语直接把消息发给接收进程/消息直接挂到接收方的消息队列中。
间接通信方式/信箱通信方式:发送进程先发送到中间实体(信箱)中,接受进程再去接收,完成进程间的通信。
下列是三个进程,他们所占用不同的空间内存和系统资源;假如我们要切换进程的时候,需要用保存/恢复运行环境,还需要切换内存地址空间(更新快表、更新缓存)开销非常大,因此,人们引入了线程机制。
引入了线程之后,线程是CPU调度的基本单位。一个进程里面可以包含多个线程。线程之间可以并发进行。但是进程依旧是资源分配的基本单位,从属一进程的各线程共享使用进程的资源。同一个进程内各个进程间并发,不需要切换进程运行环境和内存地址空间,省时省力。进程间并发,开销很大;线程间并发,开销很小
引入线程机制之后,并发带来的系统开销降低,系统并发性提升
ps:从属于不同进程的线程间切换,也会导致进程间切换,开销也会很大。
从属于同一进程的各个线程共享进程所拥有的资源。进程间通信必须请求操作系统服务(CPU需要切换到核心态),开销大;同进程下线程通信,无需OS干预,开销更小;高级调度/长程调度/作业调度,调度对象为作业;
高级调度主要用于多道批处理系统中,什么是多道批系统呢?虽然前面讲过了 再复习一遍吧:
高级调度根据某种算法/一定的原则从处于后备队列的作业中挑选一个/多个作业,分配内存等资源,建立PCB,使他们获得竞争处理机的权利。
高级调度是外存与内存之间的调度,每个作业只调入一次,调出一次。调入时创建相应PCB,作业调出时又撤销PCB。由于调出的时机一定是作业运行结束的时候,因此高级调度主要是解决调入的问题。
中级调度/内存调度,引入这个调度的目的是为了提高内存的利用率和系统的吞吐量。
引入虚拟存储技术后,将那些暂时不能运行的进程调至外存等待,此时进程的状态称为:就绪驻外存状态(挂起状态),等它们已具备运行条件且内存又稍有空闲时,由中级调度决定,重新调入内存,并修改状态为就绪状态,挂在就绪队列上等待。
ps:PCB并不会一起被调到外存,而是常驻内存。OS通过内存中的PCB保持对各进程的监控和管理。
这个地方有人会说:那么进程处于临界区时,不能进行处理机调度咯? 这个说法是错的 为什么呢?
临界资源:一个时间段内只允许一个进程使用的资源,各进程需要互斥地访问临界资源。临界区:访问临界资源的那段代码内核程序临界区:一般是用来访问某种内核数据结构的(比如进程的就绪队列,由各就绪队列PCB租成)在采用这种方式时,可能引起进程调度的因素归结为:
正在执行的进程运行完毕,或因发生某事件而使其无法再继续执行;正在执行中的进程因提出I/O请求而暂停执行;在进程通信/同步过程中,执行某原语操作;抢占并非是任意的,必须遵循一定原则。包括:
**优先权原则:**指允许优先级高的新进程抢占当前进程的处理机;**短进程优先原则:**指允许新的短进程可以抢占当前长进程的处理机;**时间片原则:**即各进程按时间片轮转运行时,当正在执行的进程一个时间片用完时,便停止该进程的执行而重新进行调度;对于每个用户而言,都希望自己作业的周转时间短一点,然而对于OS,则向作业周转时间平均值小;那么引入了概念:带权周转时间,平均带权周转时间;
FCFS例题:
SJF例题:
非抢占式:
在0时刻,只有P1进来了,在他运行的时间7内,P2,P3,P4都进来了,在P1结束后选择运行时间最短的P3执行,后续同理;
抢占式:
RR例题
时间片大小为2情况:
最后的运行流程图:
时间片大小为5情况:
补充:
时间片太大的影响:如果时间片太大,使得每个进程都可以在一个时间片内完成,则时间片轮转调度算法会退化为先来先服务调度算法,并且会增大进程响应时间。因此时间片不能太大。时间片太小的影响:如果时间片太小,我们知道,进程调度、切换是有时间代价的(保存、恢复运行环境),因此这样的话会导致进程切换过于频繁,系统会花大量时间来处理进程切换,从而导致实际用于进程执行的时间比例减少。例题
非抢占式的优先级调度算法:
抢占式的优先级调度算法:
extra:
就绪队列未必是只有一个的,可以按照不同优先级来组织;另外也可以把优先级高的进程排在更靠近对头的位置根据优先级是否可以动态改变,可将优先级分为静态优先级和动态优先级两种; 静态优先级:创建进程时确定,之后一直不变动态优先级:创建进程时有一个初始值,之后根据情况动态地调整优先级 如何合理设置各进程优先级呢?通常来说 系统进程优先级高于用户进程前台进程优先级高于后台进程I/0进程优先级高于计算型进程 若采用动态优先级,什么时候该调整? 如果某进程在就绪队列中等待了很久,适当提升优先级如果某进程占用处理机运行了很久,适当降低优先级如果发现一个进程频繁进行I/0操作,适当提升优先级例题:
整体思路:破坏死锁产生的四个必要条件中的一个或几个
正因为这种方法缺点太明显,因此衍生出了第二种方法:
它允许一个进程只获得初期所需的资源后,便开始运行。在运行过程中逐步释放已分配给自己的、且用毕的全部资源,然后再请求新的所需资源。
安全序列:是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能会有多个
不安全状态:如果分配了资源之后系统找不出任何一个安全序列,系统就进入了不安全状态。意味着之后可能所有进程都无法顺利执行下去了。为什么说是可能呢?因为假如存在进程提前归还了资源,那么系统也有可能重新回到安全状态。安全状态、不安全状态、死锁 系统处于安全状态一定不会发生死锁系统处于不安全状态,有可能发生了死锁如果发生了死锁,一定处于不安全状态那么对于上述这个例子,OS是怎么做的呢?
准备的数据结构:
长度为m的一维数组Available表示还有多少可用资源n*m矩阵Max表示各进程对资源的最大需求数n*m矩阵Allocation表示已经给各进程分配了多少资源Max - Allocation = Need 矩阵表示各进程最多还需要多少资源长度为m的一维数组Request表示进程此次申请的各种资源数银行家算法步骤:
检查此次申请是否超过之前声明的最大需求数检查此次系统剩余的可用资源是否还能满足此次请求试探着分配,更改各数据结构用安全性算法检查此次分配是否会导致系统进入不安全状态安全性算法步骤:
检查当前剩余的可用资源是否能够满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收
例题:
Allocation Max Available ABCD ABCD ABCD P1 0014 0656 1520 P2 1432 1942 P3 1354 1356 P4 1000 1750 我们会看到一个资源分配表,要判断是否为安全状态,首先先找出它的Need,Need即Max(最多需要多少资源)减去Allocation(原本已经分配出去的资源),计算结果如下: NEED ABCD 0642 0510 0002 0750 然后加一个全都为false的字段 FINISH false false false false 接下来找出need比available小的(千万不能把它当成4位数 他是4个不同的数) NEED Available ABCD ABCD 0642 1520 0510<- 0002 0750 P2的需求小于能用的,所以配置给他再回收 NEED Available ABCD ABCD 0642 1520 0000 +1432 0002------- 0750 2952 此时P2 FINISH的false要改成true(己完成) FINISH false true false false 接下来继续往下找,发现P3的需求为0002,小于能用的2952,所以资源配置给他再回收 NEED Available ABCD A B C D 0642 2 9 5 2 0000 +1 3 5 4 0000---------- 0750 3 12 10 6 依此类推,做完P4→P1,当全部的FINISH都变成true时,就是安全状态。简单来说就是依次消除与不阻塞进程相连的边,直到无边可消;但你要是非说严谨的话就是下面几步
在资源分配图中,找出既不阻塞又不是孤点的进程Pi (不阻塞—— 所申请的资源数量必须小于等于系统中已有空闲资源数量;不是孤点——与该进程至少有一个边相连)。然后消去它的所有请求边,分配边,使之变为孤点进程Pi释放的资源,可以唤醒某些因为等待这些资源而阻塞的进程——原来阻塞进程可能变为非阻塞进程;若剩下进程都能按照如上操作,消去图中所有的边,所有的进程结点都变成孤点,那称该图是 可完全简化 的;反之则称该图是 不可完全简化的 死锁定理:如果某时刻系统的资源分配图是 不可完全简化的,那么此时系统死锁解除死锁的方法:
资源剥夺法:挂起某些死锁进程,并抢夺它的资源,将这些资源分配给其他的死锁进程撤销进程法/终止进程法:强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步什么是内存?内存是用于存放数据的硬件。程序执行前需要先放到内存中才能被CPU处理。
由 x = x + 1这个代码为例,它被编译后为三条指令;
第一条指令是让CPU进行数据传送,把内存单元为01001111的数据取出来取到地址为00000011的寄存器当中
第二条指令是让地址为00000011的寄存器上的数据+1
第三条指令是让CPU进行数据传送,把地址为00000011上的数据传送到地址为01001111的地方.
于是实现了 x = x + 1操作
ps:上述指令不是严谨的二进制指令,仅供参考。
由于后续的学习,经常要进行内存之间的运算,而我们的单位往往需要统一,那么接下来就讲讲单位换算
1B=8b=2^3b
1KB=1024B=2^10B
1MB=1024KB=210KB=220B
1GB=1024MB=210MB=220KB=2^30B
1TB=1024GB=210GB=220MB=230KB=240B
上面的指令操作提到了逻辑地址,那么什么是逻辑地址呢?
eg. 0号同学入住的是房号为5(N=5)的房间,那么3(M=3)号同学入住的就是8(N+M=5+3=8)号房间;
那么固定分区分配又是如何实现的呢?
操作系统需要建立一个数据结构 —— 分区说明表,实现各个分区的分配与回收。每个表象对应一个分区,通常按照分区大小排列。每个表包含对应分区的:大小、起始地址、状态
当某用户程序要装入内存的时候,由OS内核程序根据用户程序大小检索该表,从中找到一个能满足大小、未分配的分区。将之分配给该程序,然后修改状态为“已分配”
优点:实现简单,无外部碎片
缺点:
当用户程序太大的时候,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这会降低性能会产生内部碎片,内存利用率低动态分区分配又称为可变分区分配。
这种分配方式不会预先划分内存分区,而是在进程装入内存的时候,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。
系统分区的大小和数目是可变的
缺点:会产生很多外部碎片,虽然可以用【紧凑】技术去处理,但紧凑的时间代价很高
下面思考三个问题:
系统要用什么样的数据结构记录内存的使用情况?
通常使用 空闲分区表或者是空闲分区链
空闲分区表:每个空闲分区对应一个表项
空闲分区链:每个分区的起始部分和末尾部分分别设置前向指针和后向指针
当很多个空闲分区都能满足需求的时候,应该选择哪个分区进行分配呢?
把一个新作业装入内存的时候,需要按照一定的动态分区分配算法,从空闲分区表(空闲分区链)中选出一个分区分配给该作业。
如何进行分区的分配与回收操作?
分配:
在上面的图示中,假如我们把一个4mb进程分配在了20MB处的话,我们只需要修改分区大小和起始地址即可
假如我们分配在了4MB处的话,状态由空闲变为忙碌,则该行应该被删除,假如用空闲分区链的话,就把该节点删除
回收:
假设一开始有个进程占据了14MB处(10+4),跑完了需要回收,回收区的后面/前面有一个相邻的空闲分区,那么就合并
假如回收区的前、后各由一个相邻的空闲分区的话
例:在20和10mb中间有一个4mb的进程需要进行回收
假如回收区的前后都没有相邻的空闲分区的话
假设有一个进程占满了14mb的地方,此进程需要被回收
内部碎片,外部碎片
内部碎片:位于一个操作系统分配的用于装载进程的内存区域或页面内部的空闲区域。外部碎片:位于任何两个操作系统分配的用于装载进程的内存区域或页面之间的空闲区域,可以用紧凑技术进行解决为了解决上述出现的 产生很多外部碎片 这个问题,我们衍生出了新的与之相对的算法:最坏适应算法
**算法思想:**每次分配的时候优先使用最大的连续空闲区,使得分配后剩余的空闲区不会太小,方便使用;如何实现:空闲分区容量递减次序链接。每次分配内存时候按照顺序查找空闲分区链(or空闲分区表),找到大小能满足要求的第一个空闲分区。缺点:每次都选最大的分区进行分配,会导致较大的连续空闲区被迅速消耗掉,有大进程来了,就没内存空间可以用了。我们每次算法讲到最后一个都是综合类的算法,结合了前面算法的特点综合的算法
不例外,我们这次也一样:临近时应算法
**算法思想:**回想一下 ,首次适应算法是从链头开始找,低地址部分会出现小的空闲分区(外部碎片),而这些空闲分区部分我们往往用不上,因此会增加查找的开销。那么加入我们查找不从头开始,而是从上次查找结束的位置开始检索(因为后面是还没用到的部分,可以用的几率大点),就能减少查找开销。如何实现:空闲分区以递增递增的顺序排列(可排成一个循环链表),每次分配内存的时候从上次查找结束的位置开始查找空闲分区链(or空闲分区表),找到大小能满足要求的第一个空闲分区。分页存储管理其实就是将一个进程的逻辑地址空间分成若干个大小相等的片;
这些片称为页或者页面 从零开始编号
相应的把内存空间分成与页面相同大小的若干个存储块 - 物理块/页框 同样 从0开始编号
回想我们之前所学的,进程在内存中连续存放的时候,OS是如何实现逻辑地址到物理地址的转换的呢?
是通过重定位寄存器去保存 装入模块存放的 起始位置 + 目标内存单元相对于起始位置的偏移量
我们就能得到该在物理地址(绝对地址)中,我们的目标存放的地址了,同理,我们把这种思想转移到了分页技术中地址的转换
以下面为例:
页号:逻辑地址 / 页面长度 取整 (本题中:页号=80/50 = 1业内偏移量:逻辑地址 % 页面长度 (本题中:页内偏移量=80 % 50 = 30页面在内存中的起始位置:OS用某种数据结构去进行记录的 (本题中一号页在内存中存放的起始位置=450为了方便计算页号、业内偏移量,页面大小一般取2的整数幂,那么接下来我们就来看看这种情况是什么样子的
红色部分前20位,表示了是第几页;(这的前值的是从左往右数,如果严格按照二进制来说应该是后20位
后面12位是偏移值(这的前值的是从左往右数,如果严格按照二进制来说应该是前12位
加入我们知道了N号页在内存中的起始地址(假设为X),那么我就知道我们已知的逻辑地址所对应的物理地址了
结论:若每个页面大小为2^KB,用二进制数表达逻辑地址,那么末尾K位是页面偏移量。其余部分代表页号;分页存储管理的逻辑地质结构由:页内偏移量和页号组成
若由K位表示页内偏移量,说明系统中一个页面大小是2^K个内存单元
若由M位表示页号,说明系统中,一个进程最多允许2^M个页面
上述我们有个遗留问题,我们如何知道页号对应页面 在 内存中的起始地址呢?
其实是通过页表知道的,下面看看什么是页表
一个进程对应一张页表
进程的每一页对应一个页表项
每个页表项由【页号】和【块号】组成
页表记录进程页面和实际存放的内存块之间的对应关系
物理内存大小 / 页面大小 = 内存块 / 页表项(多少个 也就是能分成多少页号)
页表项 × 页表项长度 = 页表的大小(页表在内存中占用的大小)
页表的大小 / 页面大小 = 页表项个数(页框个数)
每个页表项的长度是相同的,页号是隐含的
这句话是说明意思呢?意思是说 我们能够通过页表存放的起始地址和页表项长度,就可以找到各页号所对应的页表存放的位置
是不是说的很绕呢?那么可以这么理解:
页 - 一个数组
页的大小 表现为 2^N 这N位叫做页内偏移量 = 业内地址用N位来表示
页表项 - 数组中的元素 :存储的是物理地址的页框号
页表项 × 页表项大小 = 页表大小 等价于
数组个数 × 单个元素大小 = 整个数组大小
物理地址 / 页表大小 = 页数 / 页框号 等价于页框号
物理地址 / 数组大小 = 数组个数/ 最后一个数组下标 + 1
(页面大小为2的整数幂)
设页面大小为L,逻辑地址A到物理地址E的变换过程:
计算页号P和页内偏移量W(以十进制为例:**页号P = 逻辑地址A / 页面大小L ;页内偏移量W = 逻辑地址A % 页面大小L ** 但计算机实际运行的时候,逻辑地址结构是固定不变的,因此计算机硬件可以更快得到二进制表示的页号和页内偏移量)
比较页号P和页表长度M,如果P>=M,就产生越界中断,否则继续执行 (为什么等号也会算作越界中断呢?因为页号是从0开始的,而页表长度至少是1,因此P = M也会中断哦 类似于数组越界)
页表中页表P对应的页表项地址 = 页表起始地址F + 页号P * 页表项长度;取出该页表项内容b,即为内存块号。
页表长度:指的是这个页表中总共有几个页表项,就是有多少页
页表项长度:指的是每个页表项占据了多大的存储空间
页面大小:一个页面占多大的存储空间
计算物理地址E = 内存块号b * 页面大小L + 页内偏移量W ,用得到的物理地址E去访存
说了那么多,感觉是不是云里雾里的,知道了一堆名词,计算方式,但真正计算的时候仍然不会用?下面就来一道例题去实践一下
页号P = 逻辑地址A / 页面大小L = 2500 / 1024 = 2 ; 页内偏移量W = 逻辑地址A % 页面大小L = 2500 % 1024 = 452判断越界:页号P对应内存块号为8 没越界物理地址 E = 内存块号 * 页面大小 + 页内偏移量 = 8 * 1024 + 452 = 8644 就是最后结果了要去了解具有快表的地址变换机构前,首先要去理解一个概念 : 局部性原理
局部性原理 时间局部性:如果执行了程序中的某条指令,那么不久之后这条指令很有可能被再次执行;如果某个数据被 访问过,不久之后该数据很可能被再次访问。(程序中存在大量循环)空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问(许多数据在内存中是连续存放的)那么我们具有快表的地址变换机构与这局部性原理又有什么关系呢?我们上面介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。而由于局部性原理,可能连续很多次查到的都是同一个页表项;当我们认识到了局部性原理之后,我们能否结合这个特性减少访问在了解完局部性原理后,我们就可以正式开始学习快表了
快表,又称联想寄存器(TLB),是一种访问速度比内存块很多的高速缓冲寄存器(存放在更高速存储器中)
用于存放当前访问的若干项页表项,以加速地址变换的过程
与此对应,内存中的页表常被称为慢表(存放在内存中)
下面我们根据上方图,仔细理顺一下 引入快表之后,地址的变换过程
CPU给出逻辑地址,由某硬件算出页号、页内偏移量,由地址变换机构自动地将页号P送入高速缓冲寄存器,并将此页号与高速缓冲寄存器/快表中的所有页号进行比较若其中有与此相匹配的页号,说明要访问的页表项在快表中有副本,于是直接从快表中读出该页对应的内存块号/物理块号,再将内存块号与页内偏移量拼接成物理地址,然后访问该物理地址对应的内存单元。(一次)若其中没有与此匹配的页号,则需要访问内存中的页表,(第一次访问内存)找到对应的页表项,得到页面存放的内存块号/物理块号,访问该物理对应的内存单元(第二次访问内存);(找到页表项后,应同时将其存入快表的一个寄存器单元中,假如联想寄存器已经满了,OS必须找到一个老的且已经被认为是不再需要的页表项,将他换出来)发现了吗?如果快表命中了,则访问某个逻辑地址只需要一次访存。如果没命中,则需要两次访存有无快表的地址变换机构区别
我们为什么要引入新的存储管理方式呢?往往都是因为之前的多多少少存在一点问题,那么与两级页表对应的就是单级页表了,单机页表存在什么问题呢?
首先:页表必须连续存放,因此当页表很大的时候,需要占用多个连续页框
其次:没必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问几个特定页面
解决方案:将页表进行分组,使每个内存块刚好放入一个分组(比如,页面大小4KB,页表项大小4B,那么可以存1K个页表项,那么把连续的1K个页表项认定为一组),把各组离散存放到各内存块中
然后为离散分配的页表再额外建立一张页表,我们称之为:页目录表/外层页表/顶层页表*
我们把中间这个大页表拆分成了左边这个小页表 1024个页表项为一组
为什么要这么分呢?我们知道一个分页存储管理是把一个进程分成若干个页面然后存储 对吧?那么就有可能出现页号特别特别多的情况,因此我们把这些页号又再分一次,形成了一个二级页表;
我们现在知道了 二级页表的来由了 那么二级页表又是如何实现地址转换的呢?
按照地址结构将逻辑地址拆成三个部分 一级页号二级页号页内偏移量 从PCB中读取页目录起始地址,根据一级页号查页目录表,找到**下一级页表(二级页表)**在内存中存放的位置根据二级页号查表,找到最终想要访问的内存块号结合页内偏移量得到物理地址也就是需要3次的访存:第一次是访问内存中页目录表 第二次是访问内存中二级页表 第三次才是访问目标内存单元
有点套娃的意思;
下面来一道例题来看看一些细节问题
进程的地址空间:按照程序自身的逻辑关系划分成了若干个段,每个段有一个段名
编译程序会将段名转换为段号
每段从 0 开始编制
内存分配关泽:以段位单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻
分段细节分段系统的逻辑地址结构由段号和**段内地址(段内偏移量)**所组成
段号的位数:决定了每个进程最多可以分几个段
段内地址位数:决定了每个段的最大长度是多少
以上图为例:段号与段内地址都是16位,因此每个进程最多有216=61K个段**,每个段的最大长度是**216=61KB
上面我们学习了基本分页存储管理,有提到页表一概念,那么与之对应的,我们的基本分段存储管理是否也有段表一概念呢?答案是肯定的
段表:程序分成多个段,直接离散地装入内存,为了保证程序可以正常运行,就必须能从物理内存中找到各逻辑段存放的位置,为此,需要为每个进程建立一张段映射表. 这个段映射表就是我们的段表每个段在表中占有一个表项,记录了该段在内存中的起始位置(基址)和段的长度;各表项长度是相同的根据上方流程图我们来理一下地址变换的思路,其实是与分页存储管理的地址变换差不太多的
根据逻辑地址A得到段号S和段内地址W判断段号S是否越界,如果段号S≥段表长度M,就发生越界中断,否则继续查询段表,找到对应的段表项,段表项的存放地址为段表起始地址F+段号S×段表项长度检查段内地址W是否超过段号所对应的段长C,如果段内地址W≥段长C,则产生越界中断,否则继续计算得到物理地址 = 段基址b + 段内地址w访问目标内存单元页是信息的物理单位;分页的主要目的是为了实现离散分配,提高内存利用率;
分页仅仅是系统管理上的需要,完全是系统行为,对于用户是不可见的;
分页的用户进程地址空间是一维的,程序员只需要给出一个记忆符即可表示一个地址;
页的大小固定,且由系统决定。
段是信息的逻辑单位;分段的主要目的是更好地满足用户需求。
一个段通常包含着一组属于一个逻辑模块的信息。
分段对用户是可见的,用户编程的时候需要显示给出段名;
分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段名地址。
段的长度不固定,决定于用户编写的程序。
访问一个逻辑地址需要几次访问内存呢?
分页(单级页表):第一次访问内存——查询内存中的页表,第二次访问内存——访问目标内存单元分段:第一次访问内存——查内存中段表,第二次访问内存——访问目标内存单元分段比分页更容易实现信息的共享和保护
首先引入一个概念叫做纯代码,纯代码/可重入代码 是 不能被修改的代码;这种类型的代码是可以共享的,而可修改的代码是不能进行共享的(比如一个代码段有很多变量,多进程并发地同时访问会导致数据地不一致)
那么如果进程是分段存储管理的 几句可以很好的进行信息地共享了,因为只需要改变进程地段表项,指向同一个段即可;
看下面这张图是否感觉有点眼熟?感觉与二级页表类似;
只不过把中间的那个页表替换成了一个段表,借此机会我们再来理顺一次思路:
一个进程对应一个段表 一个段表对应多个页表 换而言之,进程对应了多个页表
像之前的两种存储管理的地址变换过程一样 ,我们也来理一次过程:
根据逻辑地址A得到段号S、页号P和页内偏移量W判断段号S与段表长度M关系,如果S≥M,发生越界中断,否则继续进行(第一次检查越界)查询段表,进行运算:段表项 = 段表始址F + S × 段表项长度 → 找到对应的段表项(第一次访问内存)判断页号P与页表长度的关系,如果页号P≥页表长度,发生越界中断,否则继续进行(第二次检查越界)根据页表存放块号和页号P计算得到查询页表,找到对应的页表项(第二次访问内存)页表项存储着该页所在的物理块号b,再利用物理块号b和页内偏移量W构成最终的物理地址最后访问目标内存单元(第三次访问内存)内存保护可采取两种办法
方法一:在CPU种设置一对上、下限寄存器,寄存进程的上、下限地址。进程的指令要访问某个地址的时候,CPU检查是否越界
方法二:采用重定位寄存器(基址寄存器)和界地址寄存器(限长寄存器)进行越界检查。其中,重定位寄存器中存放的是进程的起始物理地址,界地址寄存器中存放的是进程的最大逻辑地址。
覆盖是子啊同一个进程或程序中的
思想:将程序分为多个段(多个模块),常用的段常驻内存,不常用的段在需要的时候才调入内存
内存分为一个固定去和若干个覆盖区
需要常驻内存的段放在固定区中,调入后就不再调出
不常用的段放在覆盖区,需要用的时候调入内存,用不到的时候调出内存
如下图所示,假如程序A执行需要走如下几个程序B、C…B、C不能同时执行,我们就在物理内存中设置一个覆盖区 用于存放B或者C,其中其大小是B、C中较大内存决定同理D、E、F也不能同时执行,因此也设立一个覆盖区用于存储交换是在不同进程(作业)之间的
设计思想:内存空间紧张的时候,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)
这种技术其实在上面的处理机调度一节层次调度中的中级调度我们也有了解过了
暂时换出外存等待的进程状态为挂起状态(挂起态)
挂起态可以细分为就绪挂起和阻塞挂起两种状态 - 复习下7态模型吧
下面思考三个问题
应该在外存(磁盘)的什么位置保存被换出的进程?
答:具有兑换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分;
文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式
对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区,由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常采用连续分配方式
对换区的I/O速度比文件区的更快
什么时候应该交换?
答:交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。
应该换出哪些进程?
可优先换出阻塞进程可换出优先级低的进程为了放置优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间PCB会常驻内存,是不会被换出外存的而要解决传统存储管理的这些特征带来的问题,我们就引入了虚拟存储技术,而虚拟存储技术依靠【局部性原理】
关于局部性原理,在上述已经说过了,这里直接照搬上面的内容:
局部性原理
时间局部性:如果执行了程序中的某条指令,那么不久之后这条指令很有可能被再次执行;如果某个数据被 访问过,不久之后该数据很可能被再次访问。(程序中存在大量循环)空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问(许多数据在内存中是连续存放的)高速缓冲技术其实就算应用了局部性原理
高速缓冲技术的思想: 将近期会频繁访问到的数据放到更高速的存储器中,暂时用不到的数据放在更低速的存储器中;
虚拟内存/虚拟存储器,是指具有请求调入功能和置换功能,能从逻辑上(而不是物理上)对内存容量加以扩充的一种存储器系统。
那么虚拟内存是如何"增大"内存的呢?
基于局部性原理,在程序装入的时候,将程序中很快会用到的部分装入内存,暂时用不到的部分就留在外存,就可以让程序开始执行;
在程序执行的过程中,当所访问的信息不在内存时候,由OS负责将所需的信息从外存调入内存,然后继续执行程序;
假如内存空间不够,由OS负责将内存中暂时用不到的信息换出到外存;
你看 有用的在内存中,暂时用不到的在外存,是不是在用户看来是把整个程序塞入内存里了呢?
注意:
虚拟内存的最大容量由计算机的地质结构(CPU的寻址范围)确定的
比如某计算机地址结构为32位,按照字节编址,内存大小位512MB,外存大小为2GB;
则虚拟内存的最大容量 232B= 4GB
虚拟内存的实际容量 = min(内存和外村容量之和, CPU寻址范围)
依旧是上述例子
则虚拟内存的实际容量=min(232B, 512MB + 2GB)= 2GB +512MB;
我们知道,管理系统要实现虚拟性必须满足对换性和多次性,那么如何要满足对换性和多次性,又有什么要求呢?答案是必须建立在离散分配的内存管理方式基础上。那么现在有什么管理方式能实现虚拟存储器呢?
状态位P:该页是否已调入内存 0 表示没有被调入 1表示被调入
访问字段A:本页在一段时间内被访问的次数,供页面置换算法使用
修改位M:标识该页在调入内存后是否被修改过
外存地址:该页在外存上的地址,供调入该页时参考
缺页中断机构的大致流程如下:
缺页中断是因为当前指令想要访问的目标页面未调入内存而产生的,因此属于内中断
一条指令在执行期间,可能产生多次缺页中断
比如copy A to B,而A、B属于不同页面,那就有可能产生多次的中断:因为指令本身跨多个页面,A、B又是分别一个数据块;
中断的分类如下
由于逻辑地址到物理地址映射过程写过很多次了,这里不再赘述;
但这里需要注意的点是:
快表中有的页面一定是在内存中的,如果某个页面被换出外存,则快表中的相应表项也要删除,否则可能访问错误的页面;
找到对应的页表项之后,假如对应页面还没有调入内存,那就产生缺页中断,之后由OS的缺页中断处理程序进行处理
细节补充:
只有写指令才需要修改【修改位】,而且一般来说只需要修改快表中的数据,只有要将快表项删除的时候才需要写回内存中的慢表;
这样可以减少访存次数
缺页中断处理依旧需要保持CPU现场
换入/换出页面都需要启动慢速的I/O操作,可见,如果换入/换出太频繁,会有很大的开销
页面调入内存中,需要修改慢表,同时也需要将表项复制到快表中
请求分页管理存储与基本分页存储管理的主要区别:
程序执行的过程中,当所访问的信息不在内存的时候,由OS负责将所需要的信息从外存调入内存,然后继续执行
假如内存空间不够,就由OS负责将内存中暂时用不到的信息换出到外存
而由于我们的页面换入、换出需要磁盘I/O,有较大开销,因此好的页面置换算法的评价指标就是更少的缺页率
我们主要学习五种页面置换算法:最佳置换算法、先进先出置换算法、最近最久未使用置换算法、时钟置换算法、改进型的时钟置换算法
先进先出置换算法(FIFO):每次选择淘汰的页面是最早进入内存的页面
如何实现的呢?先进先出,学过数据结构的朋友们都清楚,应该使用队列这种数据结构。把调入内存的页面按调入的先后顺序排成一个队列,当新来的页面需要进行页面置换的时候,poll掉头页面即可;
ps:队列的最长长度取决于系统为进程分配多少内存块
Belady(贝莱迪)异常: 一般来说,缓存越大,命中率越高,缺页率越低。但有一个计算机学者,名字叫Belady。在1969年研究FIFO算法时,发现了一个反例,使用4个页框时的缺页次数比3个页框时的缺页多,因此这种奇怪的情况称为Belady异常。
这种异常的原因是对于FIFO算法来说,在同一时刻,使用4个页框时缓存中保存的页面并不完全包含使用3个页框时保存的页面,二者不是超集子集关系,造成都某些特殊的页面请求序列,4个页框命中率反而低。
只有FIFO算法会产生这个异常,此外,FIFO实现简单,但是有可能先进入的进程也会被经常访问,因此算法性能差。
最近最久未使用置换算法(LRU):每次淘汰的页面是最近最久未使用的页面
实现方法:赋予每个页面对应的页表项,用访问字段记录该页面自上次被访问以来所经历的时间t
如果需要淘汰页面,就选择现有的页面中t值最大的,即最近最久没有使用的页面
该算法需要专门的硬件支持(寄存器,栈),虽然算法性能好,但是实现也很困难,开销也很大
改进型CLOCK置换算法:我们将一个页面换出的时候,假如它是修改过的,那么其修改后的数据需要重新写回外存中,如果没有被修改过,就不需要拷回外存。
说人话就是:对于一个换出的页面,如果其被修改过,那么它换出的开销比没有被修改过的要大,那么换他出去就划不来了。
那我们以什么为指标去评判一个页面该不该被置换呢?显然易见:有没有被访问过 + 有没有被修改过
(访问位A, 修饰位M)(A = 0,M = 0)= 最近既没被访问 + 又没被修改过 ,被置换的优先级最高(A = 0,M = 1)= 最近没被访问 + 已经被修改了,并不是很好的置换选项,优先级第二(A = 1,M = 0)= 最近已经被访问了 + 但没被修改过 ,那么这个页面有可能还会被访问,优先级低三(A = 1,M = 1)= 最近已经被访问 + 已经被修改,被置换的优先级最低(第四)那这个访问位,修饰位凭什么作为依据呢?这与我们的扫描过程有关
第一轮去找(0,0),不改变标志位第一轮没找到,找第二轮,把扫描过的访问位置0,我们这轮找第一个(0,1)第二轮没找到,找第三轮,我们找(0,0),也不改变标志位第三轮没找到,我们找第四轮,找第一个(0,1)发现了吗,这与我们上面的优先级正好是吻合的,优先级低的相当于有多一次的免死金牌,多在内存里苟活一段时间;
由上面的概念我们可以发现:
如果我们的驻留集太小了,会导致缺页非常的频繁,而这会导致系统要花大量时间处理缺页,开销就很大,而且用于进程推进的时间就很少了;如果我们的驻留集太大了,又会导致多道程序的并发度下降,资源的利用率就降低了因此,不难发现,选择一个合适的驻留集大小就是非常必要的了,因此我们有了两种选择驻留集大小的分配方式
固定分配:OS为每一个进程分配一组固定数目的物理块,在进程运行期间不再改变;即,驻留集大小不变可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可以根据情况做适当的增加或减少;即,驻留集大小可变置换策略:
局部置换:发生缺页的时候只能选择进程自己的物理块进行置换全局置换:可以将OS保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程我们下面对✔的三种分配置换组合进行讲解:
固定分配局部置换:系统为每个进程分配一定数量物理块,在整个运行期间都不改变;若进程在运行过程中发生缺页,只能从该进程在内存中的页面选出一页换出,然后再调入需要的页面;
缺点:很难在刚开始就确定应该为每个进程分配多少个物理块才合理(采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)
可变分配全局置换:刚开始会给每个进程分配一定数量物理块。OS会保持一个空闲物理块队列,当某进程发生缺页的时候,从空闲物理块取出一块分配给该进程;
若没有空闲物理块,就选择一个未锁定(系统会锁定一些页面,这些页面中的内容不能被置换出外存 比如,重要的内核数据就可以设置为**“锁定”)的页面换出外存,再将该物理块分配给缺页的进程。使用这种策略的话,只要某个进程发生了缺页,都将获得新的物理块,仅当空闲物理块用完的时候,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程的页,因此这个被选中的进程所拥有的物理块**会减少,缺页率也会增加。
可变分配全局置换:刚开始会给每个进程分配一定数量的物理块。当某进程发生缺页的时候,只允许从该进程自己的物理块选出一个换出外存。
缺点:如果进程在运行中频繁地缺页,系统会给该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可以适当减少分配给该进程的物理块 也就是说 这点体现出了可变分配的概念
不难发现:
可变分配全局置换:只要缺页就分配新物理块可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块预调页策略:根据局部性原理(空间局部性),一次调入若干个相邻地页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将他们预先调入内存,但目前预调页的成功率仅仅只有50%;
故这种策略主要用于进程的首次调入(运行前调入),由程序员指出应该先调入哪些部分;
请求调页策略:进程在运行期间发现缺页的时候才将所缺页面调入内存(运行时调入),由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,每次调页都需要磁盘的I/O操作,因此I/O开销大。系统拥有足够对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出的速度很快;
在进程运行前,需要将进程相关的数据从文件区复制到对换区。
系统缺少足够对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出的时候是不必被写回磁盘的,下次需要的时候再从文件区调入即可。
对于可能被修改的部分,换出的时候需要写回磁盘对换区,下次需要的时候再从对换区调入。
UNIX方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可以从文件区调入。
若被使用过的页面需要换出,则写回对换区,下次需要的时候从对换区调入;
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出内存,这种频繁的页面调度行为称为抖动,或颠簸;
产生抖动主要原因是进程频繁访问的页面数量高于可用的物理块数
上面我们提到了,分配的物理块太少和太多带来的影响,因此为了研究每个进程分配多少个物理块这个问题,引入新概念工作集
驻留集:指的是请求分页存储管理中给进程分配的内存块的集合;驻留集:zhi的是某段时间间隔内,进程实际访问页面的集合;OS根据窗口尺寸来算出工作集
策略
驻留集:指请求分页存储管理中给进程分配的物理块的集合,在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小由上面的概念我们可以发现:
如果我们的驻留集太小了,会导致缺页非常的频繁,而这会导致系统要花大量时间处理缺页,开销就很大,而且用于进程推进的时间就很少了;如果我们的驻留集太大了,又会导致多道程序的并发度下降,资源的利用率就降低了因此,不难发现,选择一个合适的驻留集大小就是非常必要的了,因此我们有了两种选择驻留集大小的分配方式
固定分配:OS为每一个进程分配一组固定数目的物理块,在进程运行期间不再改变;即,驻留集大小不变可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可以根据情况做适当的增加或减少;即,驻留集大小可变置换策略:
局部置换:发生缺页的时候只能选择进程自己的物理块进行置换全局置换:可以将OS保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程我们下面对✔的三种分配置换组合进行讲解:
固定分配局部置换:系统为每个进程分配一定数量物理块,在整个运行期间都不改变;若进程在运行过程中发生缺页,只能从该进程在内存中的页面选出一页换出,然后再调入需要的页面;
缺点:很难在刚开始就确定应该为每个进程分配多少个物理块才合理(采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)
可变分配全局置换:刚开始会给每个进程分配一定数量物理块。OS会保持一个空闲物理块队列,当某进程发生缺页的时候,从空闲物理块取出一块分配给该进程;
若没有空闲物理块,就选择一个未锁定(系统会锁定一些页面,这些页面中的内容不能被置换出外存 比如,重要的内核数据就可以设置为**“锁定”)的页面换出外存,再将该物理块分配给缺页的进程。使用这种策略的话,只要某个进程发生了缺页,都将获得新的物理块,仅当空闲物理块用完的时候,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程的页,因此这个被选中的进程所拥有的物理块**会减少,缺页率也会增加。
可变分配全局置换:刚开始会给每个进程分配一定数量的物理块。当某进程发生缺页的时候,只允许从该进程自己的物理块选出一个换出外存。
缺点:如果进程在运行中频繁地缺页,系统会给该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可以适当减少分配给该进程的物理块 也就是说 这点体现出了可变分配的概念
不难发现:
可变分配全局置换:只要缺页就分配新物理块可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块预调页策略:根据局部性原理(空间局部性),一次调入若干个相邻地页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将他们预先调入内存,但目前预调页的成功率仅仅只有50%;
故这种策略主要用于进程的首次调入(运行前调入),由程序员指出应该先调入哪些部分;
请求调页策略:进程在运行期间发现缺页的时候才将所缺页面调入内存(运行时调入),由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,每次调页都需要磁盘的I/O操作,因此I/O开销大。系统拥有足够对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出的速度很快;
在进程运行前,需要将进程相关的数据从文件区复制到对换区。
系统缺少足够对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出的时候是不必被写回磁盘的,下次需要的时候再从文件区调入即可。
对于可能被修改的部分,换出的时候需要写回磁盘对换区,下次需要的时候再从对换区调入。
UNIX方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可以从文件区调入。
若被使用过的页面需要换出,则写回对换区,下次需要的时候从对换区调入;
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出内存,这种频繁的页面调度行为称为抖动,或颠簸;
产生抖动主要原因是进程频繁访问的页面数量高于可用的物理块数
上面我们提到了,分配的物理块太少和太多带来的影响,因此为了研究每个进程分配多少个物理块这个问题,引入新概念工作集
驻留集:指的是请求分页存储管理中给进程分配的内存块的集合;驻留集:zhi的是某段时间间隔内,进程实际访问页面的集合;OS根据窗口尺寸来算出工作集
