1 OS发展历程
1.1 基本概述
手工操作阶段 将纸带上的孔作为1,五孔作为零。计算机处理速度快,而人输入输出的纸带速度慢,人机速度矛盾,效率低,且只能处理一名用户
批处理阶段 单道批处理系统 引入了脱机IO(外围机控制IO磁带完成),用外围机读取纸带,存入磁带,再输出到磁带,再到纸带。由监督系统控制这一过程,这也是操作系统的雏形。从磁盘中的一批任务中选一个进入主机运行
缺点:只能有一个程序运行中,而且cpu会闲下来等待IO完成
多道批处理系统 用磁带输入多道程序,此时操作系统诞生,有操作系统管理这一过程,引入了中断技术。单位时间运行程序多,吞吐量大。宏观上是并行,微观上其实是串行,是在交替运行,这称为并发。
缺点是缺少人机交互,在cpu执行过程中人只能等待。作业周转时间长,从作业进入系统到结束。
分时操作系统 共享主机,一个主机有多个终端。以时间片为单位轮流为各用户服务,比如先用50ms做接收一个操作,下一个时间片(50ms)能接收其它用户的服务。周期获得cpu使用权,有种独占的感觉。
Unix是分时操作系统的典型代表
缺点是都是排队处理,对于紧急任务不能优先处理
实时操作系统 任务需要在时间限定内完成,高优先级的任务能够打断正在执行的低优先级任务优先处理,及时可靠。有周期性实时任务,需要在下一个周期来临前完成。非周期任务有个截止时间
分为硬实时操作系统和软实时操作系统。前者更为严格,绝对不能超时,比如导弹系统。而后者偶尔可以超时,比如12306
其它系统 比如网络操作系统,分布式操作系统, 个人计算机操作系统
操作系统分类 实时 分时 批处理
1.2 思考
脱机IO技术的基本思想 输入输出不依赖主机控制,而是独立进行,能够减少IO速度对于主机效率的影响,也能使主机专门处理任务
微机操作系统的发展与分类
- 单用户单任务操作系统DOS
- 单用户多任务操作系统 只允许一个用户上机,但是允许用户把程序分为若干任务,使它们并发执行
- 多用户多任务操作系统 多个用户通过各自的终端,使用同一台及其,共享主机系统的资源,比如UNIX OS
嵌入式操作系统的特点 功耗小 操作方便 开放性的体系结构 强实时性 统一的接口 强稳定性,弱交互性
分布式操作系统的功能 计算存储在多台处理器上进行,能够用较少的成本处理复杂问题,并且可靠性强
大型机的使用范围 现在作为商用的数据库和大型的事务服务器使用,比如IBM的主机, 医院的主机,银行的主机等 大型机操作系统能够运行大型机运行运行程序,处理复杂数值和数据驱动的任务
服务器操作系统 服务器操作系统可以实现对计算机硬件与软件的直接控制和管理协调
服务器的分类
- 按体系 非×86服务器和×86服务器
- 外形 机架式 刀片式 塔式 机柜式
- 应用层次 入门级 工作组 部门级 企业级
多核芯片 一个处理器有多个内核 能够在较小体积中拥有更强大的处理性能
多处理机操作系统功能 多个处理机 增加系统的吞吐量;节省投资和提高系统的可靠性
PDA 又叫掌上电脑,能够帮助我们在移动中完成任务 有用户用的手机,也有工业级的POS机等
掌上计算机操作系统 掌上电脑最大的特点是具有开放式的操作系统,支持软硬件升级,集信息的输入、存储、管理和传递于一体
传感器节点网络 最初起源于战场监测等军事应用。而现今被应用于很多民用领域,如环境与生态监测、健康监护、家庭自动化、以及交通控制等。
传感器节点网络操作系统 每一个传感器节点都有一个操作系统,通常是事件驱动,响应外界发生的事件,也能够更具事件周期性测量外界。
智能卡 有微型芯片的塑料卡,以IC卡为核心,一卡通就是 操作系统能够更具读写器进行数据交互
1.3 题目
操作系统的设计目标 方便 有效 开放 可扩充性
2 操作系统概述
2.1 运行机制与体系结构
指令 指令是能够背计算机直接识别的机器语言01
特权指令 不能被用户程序运行,防止损坏计算机
处理器状态 由程序状态字寄存器(PSW)中的一个标志位来表示,有用户态和核心态,只有核心态能够运行特权指令
两种程序 内核程序能够运行特权指令 应用程序不能
内核 操作系统在有内核功能和非内核功能,比如计算机没有记事本功能也能运行,内核是最基本最核心的部分,实现内核的就是内核程序
内核又可以分为时钟管理(计时),中断处理和原语 (设备驱动,CPU切换等,离硬件最近,有原子性,运行后不能中断)对系统资源进行管理的功能(离硬件略远,在有的设备上不是内核)不包含后者的称为微内核,包含后者的称为大内核
大内核的优点是性能好,缺点是内核代码庞大,难维护。微内核需要在核心态和用户态间不断切换,性能低,但是结构清除,方便维护
2.2 中断和异常
中断系统的诞生 早期计算机只能有一道程序在运行中,结果完成之后才能有下一道程序。后来就引入了中断系统,多个程序在计算机中,到一定时间之后就会换一个程序执行,中断前面一个。中断就是由用户态转为核心态,由操作系统进行程序切换。程序要运行了再切换回来。
中断的概念和作用 中断是唯一能让cpu由用户态转为核心态的方式。而操作系统需要特权来管理工作。所以中断就意味着操作系统要开始介入干活了。
对于不同的中断信号,操作系统有不同的处理
核心态转为用户态只需要执行一个特权指令即可
中断的分类 中断可分为内中断和外中断,狭义的中断就是外中断。外中断是cpu外部发送的中断信号,内中断是cpu内部发送的。
外中断分为人为干预和IO中断请求,如需要输入一个字符,打印一个表。
内中断分为陷入,故障和终止。陷入是有意为之的系统指令,故障是还可能修复,终止是软件问题不可修复,比如你写了个程序溢出了。
外中断的处理过程 一个程序有多个指令,在用户态下运行,检测每一个指令有没有中断请求,当发现了有一个请求,比如输入一个数组,程序保护CPU环境(存档),核心态下处理,返回用户态。
2.4 操作系统的特征
并发 宏观上的并行,微观上的串行,并行是在微观上也是多道程序在运行,需要多个处理器。
程序是不能并发执行的,而是更小的单位,进程,能够并发执行
共享 系统中的资源能够为多个共享的资源使用。
分为互斥共享方式,和同时访问方式。前者是一段时间只能让一个进程访问,打印机,堆栈等。 后者在微观上也是交替执行的,如磁盘。
虚拟 通过某种技术,将一个实体变成多个逻辑上的对应物。IO设备,CPU,这些都只有一个,多台设备在使用,都有一种我一个人独占设备的感觉,但是实际上没有那么多的设备,这就是将这些设备虚拟化了。
还有就是存储器的虚拟存储。实际上机带RAM内存一般是16G,但是我们在使用的时候能够存储的东西很多,明显不只是16G,是用外存扩充内存。用磁盘存储没有在运行的,要用了再放到内存中去。
异步性 进程不是一气呵成的,运行速度是不可预见的,由于并发运行,所以可能会出现抢占打印机等情况打断进程,所以是不可预见的,而且有时候进程出错了,再运行一次或许就能够成功。
2.5 系统调用
分类 操作系统向上提供了一些接口,命令接口供用户直接使用,系统调用是程序员在代码中使用,通过程序间接使用的接口
作用 对于一些共享资源,如果可以直接由进程操作,那会很混乱,比如我在使用打印机的时候,你也要使用打印机,那会导致我的打印被中断。所以应用程序需要用系统调用的方式来使用共享资源,保证资源的稳定性和安全性
系统调用需要在核心态下进行
库函数 应用程序是能够直接使用汇编语言进行系统调用的,但现在用的都是高级语言,所以这些高级语言会把系统调用封装成库函数
不是所有的库函数都使用了系统调用
系统调用的过程 先传递系统调用参数,然后再执行陷入指令,执行系统调用相关服务,最后返回应用程序
陷入指令是在用户态下执行的,是唯一一个不能在核心态下执行的指令。执行陷入指令后会进行内中断,进入核心态。
3 进程
3.1 进程的定义
程序的每次执行就是一个进程
PCB 进程中断,恢复数据。多道程序,多个数据,需要找到对应数据。由PCB(进程控制块)记录进程的各种信息。
进程实体 PCB 数据段 程序段 数据段存放中间数据,如变量。程序段存放程序。
一个内存有多个进程,也就是由多个PCB 数据段和程序段。
进程实体一般就叫进程,刻意考察两者还是不同的,进程实体是静态的,而进程是动态的,进程是程序的一次运行。
3.2 进程的组织
方式 系统中有多个pcb,对这些pcb的管理有两种方式,链接模式和索引模式
链接模式 操作系统将pcb分为三个队列,拥有三个队列的指针。
执行指针:指向正在执行的pcb(运行态),只有一个pcb位于这一队列
就绪队列指针:指向位于就绪态的pcb,通常优先级高的会在队列前面
阻塞队列指针:位于阻塞态的指针
索引方式 和链接方式差不多,也有三个指针,执行指针同上,就绪表指针,指向就绪索引表的表头,而表中各项就是一个个的pcb,阻塞表指针同理
3.3 进程的特征
动态性:进程最基本的特征,进程是程序的一次执行过程
并发性:进程可并发执行
独立性:进程是能独立运行、获取资源,接收调度的基本单位。
异步性:进程按各自独立的,不可预知的速度向前推进
结构性:每个进程都由程序段、数据段、PCB组成
3.4 进程的状态
基本状态 进程拥有三种基本状态,运行态,就绪态,和阻塞态。
运行态 在单核处理机上,每一时刻最多有一个进程正在运行。
就绪态指已经拥有运行的条件,但是由于cpu没有空闲,所以在等待
阻塞态 不满足运行条件,比如在等待打印机,等待读取磁盘等
进程的另外两种状态
创建态,进程正在被创建,分配资源,初始化pcb
终止态:进程正在被撤销,有时是因为各种故障。操作系统会回收进程的资源和pcb
3.5 进程的转换
转换路线 系统完成创建,转为就绪态,进程被调度,转为运行态,时间片到或高优先级抢占处理机,转回就绪态。
进程用“系统调用”的方式请求资源或等待事件触发时,由运行态转为阻塞态,等资源分配到了,或者事件触发了,转为就绪态,不能直接到运行态。
运行结束或出错,转为终止态。
运行态到阻塞态是进程的主动行为,阻塞态到就绪态是一种被动行为
原语 操作系统用原语实现进程控制,当pcb从阻塞态到就绪态时,如果由于某种原因导致明明已经在就绪态了,但是pcb的信息还没有改过来,这种行为就很危险
原语具有原子性,原语的前后有关中断指令和开中断指令,在开中断指令执行之前,都不会对外部中断信号进行处理,这保证了原语的原子性。
原语需要运行在核心态
原语的用途 更新pcb的信息;将pcb插入到合适的队列;分配,回收资源;
相关的原语:进程的创建,终止,阻塞,唤醒,切换
3.6 进程通信
定义 进程通信是进程之间的信息交换,每个进程在内存中都有自己独有的地址空间,进程不能访问不是自己的地址空间,
共享空间 多个进程可以访问的地址空间,但是同一时刻只能有一个进程访问。
基于数据结构 只能在共享空间中放同一数据结构的数据,比如长度为10的数组,属于低级通信
基于存储区 数据的形式和位置都由进程控制,共享速度更快,高级通信
管道通信 管道是指一个共享文件,在内存中是一个大小固定的缓存区
属于半双工通信,某一时刻只能实现单向传输
访问管道同上是互斥的,同一时刻只能有一个访问
以字符流的形式写入,写满后write的系统调用阻塞,读空后,read被阻塞
没写满,不能读,没读空,不能写,管道只能有一个读
消息传递 以格式化的消息为单位,通过发送和接收消息两个原语实现数据交换
格式化的消息分为消息头和消息体,报文就属于一种格式化的消息。消息头中有发和收的id和消息类型长度等信息
有两种方式,直接通信方式,每个进程有一个消息缓冲队列,进程将消息放到队尾,等着被一个个读取
间接通信方式 消息发送到中间实体(信箱),从信箱中读取
3.7 线程
定义 一个qq,可以同时发文件,聊天和打电话,线程是程序执行流的最小单位,可以看成是一个轻量级的程序
进程与线程 在引入线程之前,cpu实现多道程序并发,需要在各个程序之间切换,而在有了线程之后,线程成了cpu的基本执行单位,是在每一个程序中的线程不断切换,使得线程也能并发执行
进程是资源分配的基本单位,资源是分配给进程的,线程是系统调度的基本单位,同一进程的线程共享进程的资源
线程切换也需要内核态,但同一进程中线程的切换不需要改变运行环境(拥有资源相同),不同进程需要改变运行环境,开销较大
线程的实现方式 用户级线程,线程的切换是由应用程序来控制的,在操作系统看了还是一个普通的进程。java的线程都是用户线程。
内核级线程 在内核空间能看到线程,需要在核心态下才能实现内核级线程的切换。在处理机的分配上,是以内核级线程为单位的,比如一个进程有两个内核级线程和三个用户级线程,在操作系统看来就只有两个,所以最多可以分配给两个处理机,即使是一个4核、
多线程模型 对于一个进程,内核级线程和用户级线程的数量,属性是不一样的
多对一,一个内核级。当一个用户级线程阻塞后,整个进程阻塞,并发度不高, 但是进程的切换效率高
一对一,一个用户级线程对应一个内核级线程,而多个内核级线程会影响应用程序的性能,而且线程调度效率低,但是并发度,可充分利用多核处理机
多对多,多个用户级线程对应少数的内核级线程 拥有上面的优点
4 调度
4.1 概念与层次
概念 调度是用某种规则,决定任务的执行顺序。 处理机调度,就是按照一定的算法,将一个进程选出来,分配处理机给这个进程
高级调度 又叫作业调度或者长程调度,高级调度是将处于后备队列的作业,调度到内存中来,给作业创建pcb。
高级调度是内存与外村的调度,只调入一次,调出一次,调出必然等到作业执行完毕才调出
高级调度只在多道批处理系统里。
中级调度 有了虚拟存储技术以后,可以将暂时无法运行的进程调至外存中等待,直到内存空间有空闲后再调回来,此时pcb仍在内存中,处于挂起状态。
比如一个程序正在运行,但是我觉得不对劲,就把他停下来修改一下,再让它继续运行。这个时候可以理解成挂起。挂起不一定是内存不足,挂起是为了提高内存运行效率和系统吞吐量
七状态模型 无状态模型是创建、就绪、运行、阻塞、终止,还有阻塞挂起和就绪挂起。
从创建、就绪、运行态转为挂起态就是就绪挂起,就绪挂起激活后是就绪态
阻塞态转为挂起态就是阻塞挂起,阻塞挂起激活后是阻塞态,但是可能在挂起的时候等待的事件出现,就会转为就绪挂起
低级调度 又叫进程调度,是从内存中选择一个进程,调度到cpu中运行。发生的频率很高,几十毫秒一次
4.2 进程调度
临界资源 一段时间只允许一个进程访问的资源
临界区 访问临界资源的代码
内核程序临界区 是特殊的临界区,用来访问内核数据结构的,比如就绪队列,当一个进程在使用就绪队列的时候,不允许其它程序访问,只能等到进程从临界区退出。
进程在内核程序临界区的时候不能进行调度和切换,没有释放cpu
普通的临界区,比如访问io设备,速度慢,这个时候不能让这个访问io设备的进程继续占据cpu,需要进行进程切换
进程调度的分类 非剥掉调度方式(非抢占式方式),只能等待进程主动放弃处理机(终止、异常、阻塞)
剥夺调度方式(抢占式方式):会被动地剥离处理机(高优先级,时间片用完等)
进程切换与调度 狭义的进程调度是从就绪队列中选一个进程进入处理机(可以是刚被停止的进程)
进程切换是让处理机的进程下来,换另一个上去。
广义的进程调度包括上面两个
进程切换的工作 对旧进程数据的保存与对新数据的恢复
所以我们不能频繁地使用进程切换,这会降低系统效率
4.3 调度算法的评价标准
cpu利用率 cpu忙碌的时间/总时间,忙碌的时间就是指占用cpu但是不做事情,比如等待打印机
系统吞吐量 完成的作业数量/总时间
周转时间 作业从提交到完成的时间,包括在外存中等待高级调度的时间
带权周转时间 周转时间/实际运行时间,我本来只需要干1秒就可以完事,但是你让我等了半个小时,用户满意度就会很低。
等待时间 就是总的等待时间,等待io设备完成不算,这个时候还是在工作的
响应时间 提出请求到首次响应的时间
4.4 调度算法
饥饿 某个进程或者作业长时间得不到响应
FCFS First Come First Serve 先来先服务,就是普通的排队
非抢占式的
缺点:对于短作业不利
SJF Sortest Job First 短作业优先算法
分为抢占式和非抢占式的,默认是非抢占式的,抢占式的短作业优先算法平均等待时间和平均周转时间是最短的
可能会导致长作业饥饿
HRRN Highest Response Ratio Next 高响应比优先算法。每次调度的时候计算各个就绪队列的响应比 =(等待时间+要求服务时间)/要求服务时间
非抢占式的,只有当当前的任务执行完了之后,才会重新计算响应比
折中的算法
时间片轮转 RR 按照一定的时间片进行
时间片不能过大,可能会导致一些进程能在时间片结束之间就完成任务了,就会退化成FCFS算法
时间片过小,会导致进程切换频繁,实际的进程执行时间少,系统效率低,平均响应时间短。
优先级调度算法 PSA priority sheduling algorithm 更具当前的进程优先级进行调度、
分为非抢占式的和抢占式的
就绪队列不止一个,可以更具优先级来组织,比如把优先级高的放在队头
如果需要动态改变优先级,可以分为静态优先级和动态优先级,
一般前台进程优先级高于后台进程,管理者进程高于用户进程,IO繁忙型(IO型进程,IO操作多的进程,可以使得IO设备快点工作,不要到时候IO设备忙不过来,影响整体效率)高于cpu繁忙型(计算型进程)
多级反馈队列调度算法 有多级队列,比如123,每个队列的时间片是不一样的,3多于2,2多于1.1的优先级高于2,2高于3,优先执行1里面的队列,如果在1中执行了一个时间片的长度还没执行完,就下放到2中。假如有2的队列在执行,来了一个新的队列进入1中,2的队列停止,返回到队列2,让1队列的先执行。
抢占式的算法,可能导致饥饿
5 进程同步
5.1 同步与互斥
进程异步性 进程的运行是不可预知的,有两个进程,运行多次,可能有的时候a在b前面运行完,有时候b在a前面运行完。但是有的情况是a必须在b前面运行完,比如管道通信中,写数据必须在读数据之前。那么如何保证这个情况呢。
临界资源 同一时间只能由一个进程访问的资源
进程互斥 进程必须互斥地访问临界资源,当有一个进程在访问的时候,其它进程必须等待。
对于访问临界资源的代码,可以分为四个部分:进入区,检查是否课进入临界区,如果可以进入,需要在进入的同时给临界区上锁(设置标志);临界区就是访问临界资源的代码;退出区,负责解锁的代码,将标志改回来;剩余区,做其它处理
互斥访问的原则 空闲让进:当临界区空闲的时候,允许一个等待的进程立即进入临界区
忙则等待:临界区有进程的时候,其它进程必须等待
有限等待:对于一个等待的进程,必须在有限的时间让它能够访问临界区(避免饥饿)
让权等待 当进程在等待的时候,应该立即释放处理机
5.2 进程软件实现方式
单标志法
p1
while(turn != 0);//进入区
critical section;//临界区
turn = 1;//退出区
remainder section;//剩余区
p2
while(turn != 1);
critical section;
turn = 0;
remainder section;
只有turn等于0的时候才能跳出p1的循环进入临界区,不然就是死循环,卡在进入区
当p1访问结束的时候,p2能够访问,能够在p1和p2直接不断地轮转,但是当p1不访问的时候怎么办
单标志法将更改标志的权限给了另一个进程,但是进程有可能不访问,这违反了空闲让进的原则;
双标志先检查法
p0
while(flag[1]);
flag[0] = 1;
critical section;
flag[0] = 0;
remainder section;
进入区的while中flag是一个bool数组,当哪一个进程需要访问临界区,就把对应的标志改为1,而进入区会判断其它的进程是否已经进入,没进程才运行改
这个时候更具进程的异步性,可能导致两个进程都一起访问临界区,比如先执行p0的while,通过,这时立即p1的while也紧跟着执行了,注意此时没有一个元素是1,还没改呢,就被绕过检测了。
不满足忙则等待
双标志后检查法
这种方式很简单,就是先改标志,再检查
如果还没检查,两个进程的标志位就改了,那么检查的时候就会发现都不能进入,因为标志在说有人了
违背了空闲让进和有限等待
peterson算法 对上面一个的改良
p0
flag[0] = 1;
turn = 1;
while(flag[1] && turn = 1);
critical section;
flag[0] = 0;
remainder section;
p1
flag[1] = 1;
turn = 0;
while(flag[0] && turn = 0);
critical section;
flag[1] = 0;
remainder section;
核心是在发现当后检查法的那种情况(两个标志都改为了1)的时候,会有一个改turn的步骤,走到这一步的时候就是表示我愿意让和我一起改进程的兄弟先走一步。
如果我到while了,你这个时候已经改了标志位,那让你先走,但是你走到turn的时候一定会让我一次,所以还是我先走
在这个都会且只会让一次的规则中,如果有两个进程进入,那谁先让(改turn),谁先进
但是这个算法还是没有解决让权等待的问题,因为p0确实是在等着,但是等待是有while实现的,而while是有cpu执行的,所以没有让权
5.3 硬件实现方式
中断屏蔽方式 与原语的实现逻辑相似,用开中断和关中断指令。这样在访问临界区的时候就不能被中断
不适用于多处理机,多处理可以两个同时访问。不适用用户进程,关中断和开中断是内核态的指令,不能被用户进程使用
TestAndSetLock 可以叫做TS或者TSL
这是用硬件实现的,所以具有原子性,而不会有其它进程影响。下面用c语言来描述这一过程
bool TestAndSetLock(bool *lock){
bool old = *lock;
*lock = true;
return old;
}
while(TestAndSetLock(&lock));
critical section;
lock = false;
remainder section;
当有一个进程到了进入区,有进入临界区的意愿的时候,执行while中的TSL函数,如果没有进程,lock为false,old=false,lock=true,那么其它进程到进入区的时候,就会是old=ture不能访问,直到那个进程访问完
缺点就是不能让权等待,到了进入区的时候,还会工作执行while的硬件逻辑
Swap swap有的地方叫exchange,这是一个有意思的东西,下面用伪代码描述
bool old = true;
while(old == ture){
swap(lock, old);
}
critical section;
lock = false;
remainder section;
swap和TSL的实现逻辑基本一致,但是个人认为swap更加巧妙,他是当lock为false,没人的时候,就将old和lock交换,将lock划为true,当lock为true的时候,就不断交换old和false,直到lock为false,再交换就使得lock为true
不满足让权等待
5.4 信号量机制
定义 用一对原语对信号量进行操作
信号量就是变量,记录资源的数量
一对原语是指wait和signal,也叫P和V操作,这源于荷兰语,信号量机制是荷兰人提出来的
整形信号量 对于信号量只有三种操作,初始化,P,V。初始化int a = 5,这个5就是可用的资源数量,
int a = 3;
void wait(int a){
while(a <= 0);
a--
}
void signal(int a){
a--
}
wait(a);
//
signal(a);
//
这种不符合让权等待,因为while还是在执行,但是这里有一个bug,就是既然wait是原语,那么在执行while的时候为什么可以切换到其它进程,切换就是中断。
记录型信号量 是对上一个的改版,S<0的时候代表等待队列中的数量
typedef struct{
int value;//剩余资源数量
struct process *L;//不足时的等待队列
} semaphore;
wait(semaphore *S){
S.value--;
if(S.value < 0){
block(S.L);//阻塞这个进程,进入这个信号量对应的阻塞队列中等待唤醒
}
}
signal(semaphore *S){
S.value++;
if(S.value <= 0){
wakeup(S.L);//唤醒这个进程
}
}
解决互斥问题
临界资源可以理解成一个数量为1的资源,只需要初始化为1即可
semephore mutex = 1;
p(mutex);
critical section;
v(mutex);
remainder section;
同步问题
就是按照一定的顺序执行代码,那么可以将前提抽象成一种资源,有这个资源,后者才能进行下一步。
打个比方,有一个工作是开门,一个工作是找钥匙,那么就需要先找钥匙,而钥匙就是一种资源
那么初始化为0,初始没有钥匙
等到前提完成,可以执行v操作,代表找到钥匙,再执行p操作,钥匙用了
前驱图
是同步问题的扩展,将两个扩展到多个,不过还好是1对1的图
v是获取,p是使用
5.5 信号量集
作用 信号量只能每次操作数量1,这样可能出现问题,所以设置了信号量集,只有达到一定数量的时候才能运行操作,而且一次操作的数量也不再是1,而是多个。
使用 如P操作wait(s1, l1, n1, s2, l2, n2);就是对两个信号量s1 s2,只有等到s1数量达到l1才操作,操作就是s1-n1,s2同理,需要两个信号量同时达到条件。
5.6 问题
生产者消费者问题
也叫做有界缓冲问题 两者中的产品就是数据,生产者和消费者共用一个大小为n的缓存区,满了就不能放,空了就不能拿
必须互斥访问,
信号量设置:互斥信号量1 空间也是一个资源,empty大小为n, 产品也是资源,full初始值为0
生产者
P(empty);
p(mutex);
//
v(mutex);
V(full);
消费者
P(full);
P(mutex);
//
V(mutex);
V(empty);
无论是生产者还是消费者,都不能更改两个P操作的顺序,如果是先P(mutex),后P(full)那么就有可能此时共享空间没有数据,需要等待生产者生产数据,于是消费者进入阻塞队列,但是当到生产者的时候,需要mutex这个资源,也就是异步资源,这个资源还没有被消费者释放,无法进入下一步生产数据
这种双方都在等待对方释放资源的现象就叫死锁
多生产者多消费者问题
有多个生产者,生产的不是同一个东西,消费者消费的也不是同一个东西,就需要对应的生产者给对应的消费者生产数据。
那么如何保证这个过程进行呢
semaphore plate = 1;
semaphore dataA = 0;
semaphore dataB = 0;
//plate是空间的容量,A B 是不同的数据
//生产者1
{
P(plate);
//
V(dataA);
}
//消费者2
{
P(dataB);
//
V(plate);
}
空间容量是一个互斥的资源,但是当它的容量为1的时候,有可能不需要使用互斥信号量,但是当它的容量大于1的时候必须使用
吸烟者问题
当有三个吸烟者,吸烟时需要的材料是不一样的,那么如何轮流供应三个人的材料,使得他们能够轮流吸烟
semaphore offer1 = 0;
semaphore offer2 = 0;
semaphore offer3 = 0;
semaphore finish = 0;
//分别代表材料123和抽烟是否完成
int i = 1;//实现轮流的变量
//供应者
while(1){
if(i == 1){
V(offer1);
}
if(i == 2){
V(offer2);
}
if(i == 0){
V(offer3);
}
i = (i + 1) % 3;
P(finish);
}
//消费者1
P(offer1);
V(finish);
读者写者问题
类似消费者与生产者,但是读者不会消耗产品,只会读取,所以可以让多个读者同时访问数据。
当有写者进入操作空间的时候,不能让其它进程访问空间,无论是写者还是读者。所以写之前要让所有的读者和写者退出
semaphore rw = 1;//write and read 是读写的互斥信号量,也可以说是共享空间的锁
int count = 0;//记录访问空间的进程数量
semaphore mutex = 1;//互斥信号量,用来互斥访问count
writer{
P(rw);
//写文件
V(rw);
}
read1{
P(mutex);//给count上锁
if(count == 0){//有读进程,先给共享空间上锁
P(rw);//给共享空间上锁
}
count++;
V(mutex)//对count的操作完成,解锁count.
//读文件
P(mutex);//上锁count
count--;
if(count == 0){
V(rw);
}
V(mutex);
}
上面的是读优先的算法,只要还有读进程,那么写进程就一直不能进入访问空间,一直处于阻塞队列
semaphore rw = 1;//write and read 是读写的互斥信号量,也可以说是共享空间的锁
int count = 0;//记录访问空间的进程数量
semaphore mutex = 1;//互斥信号量,用来互斥访问count
int w = 1;//用来实现读优先
writer{
P(w);
P(rw);
//写文件
V(rw);
V(w);
}
read1{
P(w);
P(mutex);//给count上锁
if(count == 0){//有读进程,先给共享空间上锁
P(rw);//给共享空间上锁
}
count++;
V(mutex)//对count的操作完成,解锁count.
V(w);
//读文件
P(mutex);//上锁count
count--;
if(count == 0){
V(rw);
}
V(mutex);
}
加了一个w的互斥信号量,当有一个读进程的后面有一个写进程的时候,这个读进程先给共享空间上锁,再在读文件前解锁,当解锁之后,写进程就会给w上锁进入P(rw)的步骤,下一个读进程到来,w没有解锁,所以读进程进入阻塞队列。
哲学家进餐问题
有5个围在一起的哲学家,每两个哲学家之间有一根筷子,共有5根筷子,只有当一个哲学家拿到一对筷子的时候才能吃饭。
那么当每个哲学家都只拿个一根筷子的时候就会发生死锁都,都在等待其他的哲学家放下筷子。
semaphore mutex = 1;//取筷子的互斥信号量,当有一个哲学家在取筷子的时候,不允许其他的哲学家动筷子
semaphore chopstick[5] = {1, 1, 1, 1, 1};//筷子的互斥信号量
PI{
P(mutex)//给取筷子上锁
P(chopstick[i])给哲学家左边的筷子上锁
P(chopstic[(i+1)%5]);//给右边的筷子上锁
V(mutex)//解锁取筷子
//吃饭
V(chopstick[i]);//解锁左边的筷子
V(chopstick[(i + !) % 5]);//解锁右边的筷子
}
5.7 死锁
死锁的条件 对临界资源的争夺;
资源不可剥夺,只能主动释放;
持有一个资源,同时请求另一个资源,但是另一个资源得不到,还不放自己的资源;
进程资源的循环等待条件。
预防死锁 对死锁的4个条件进行破坏,将互斥的信号量改为共享的资源,但是大部分的不能改
得不到另一个资源的时候,应该主动释放自己手里的资源,或者通过优先级将需要的资源强行剥夺。 这个方法的缺点是实现麻烦,会降低系统吞吐量,而且主动释放资源意味着之前的工作全部作废。而且有可能产生饥饿
静态分配方法,在资源不满足的时候不会运行,一旦运行那资源就一直是他的。但是资源利用率低下,可能饥饿
顺序资源等待法,给所有的临界资源编号,当需要多个资源的时候,只有当你需要的资源里的小号资源到位了,才能申请大号的资源,这会造成浪费,有的进程需要先运行大号的资源,但是必须先申请小号的资源,这就造成了浪费
避免死锁
动态序列:如果系统按照这个序列分配资源,那么一定是安全的。如果系统找不到安全序列,那么就会进入一个不安全状态。不安全状态可能死锁,安全状态一定不会死锁。
银行家算法:在答应进程的请求之前,需要先判断是否会进入不安全状态,如果会,那么就先拒绝此次请求
如A最多需要资源如下:X50,Y60,Z 70,B最多需要X20 Y40 Z70,你只有Z80,这个时候A请求了30,B请求了10,B此时又想请求20,你如果给了的话,就只剩下20,如果AB两个进程都需要达到最大需求才能完成此次的作业,那么就会造成你手里的资源哪个都不能满足
银行家算法需要4个数据结构:可利用资源向量 Available,Available[a]
表示a的可用资源还有多少;最大需求矩阵Max[p0][a]
表示po对a的最大需求是多少;分配矩阵 Allocation,已经分配了多少;需求矩阵Need,还需要多少;
Need = Max - Allocation;
预防与避免的区别 预防是将进入死胡同的路封了,避免是在走的过程中不让它走入死胡同。
检测与解除死锁的定义 当没有预防与避免死锁的时候,就需要使用检测和解除死锁的措施
死锁的检测:用一种数据结构来保存资源的信息,利用这种信息来判断是否进入死锁。
这种数据结构有两种结点和两种边。
结点是资源结点和进程结点。边是请求边和资源分配边。
资源结点中有资源数量的信息,当非阻塞进程退还所有的资源或者是资源直接就足够所有的进程使用的时候,就没有发生死锁,即为完全简化之后,可以消除所有的边,最终还有进程有线连着,那就是死锁进程。
解除死锁 剥夺,撤销和回退。剥夺即将一个进程暂时挂起,它的资源退还回去。撤销即是强行终止。回退是将进程回退到不会发生死锁的状态。
剥夺需要注意不能挂起太久,以避免饥饿。撤销有较大的代价,会功亏一篑。回退需要系统设置存档点,保存之前的信息
手段的执行对象的选择:优先级低的,已执行时间短的,还需很长时间才能执行完毕的,已经使用资源多的,非交互式的(批处理式的);
6 内存
6.1 一些基本概念与地址
内存的定义 外存的存取速度很慢,如果cpu要从外存中读取数据的话,就很慢,所以需要速度更快的内存
存储单元 内存从0开始编号,每一个地址对应的就是一个存储单元。存储单元的长度不是固定的,如果是按照字节(byte)编址的,1字节就是英文字符的长度,8位(bit)二进制。字长就是cpu一次性处理的二进制位数,16位字长就是一次性处理2字节。
内存按照字长(word length)编址需要看内存的字长具体有多长,那么一个存储单元的大小就是一个字长的位数
4GB的内存的地址长度的计算方式。1GB=2^30,4GB=2的32次方,即为32位
指令 对于x = x + 1 这个指令,先是编译成机械语言。比如(011,001, 002),就可以理解成011是放置数据的指令,001是需要放置的变量,002对应的内存中的地址。然后第二个(011,001,111)111代表的是寄存器,将x放到寄存器中。
逻辑地址 逻辑地址就是相对地址,对于内存中数据的存放位置,很难精确地定位到对应的绝对地址,而是直接使用相对地址。
写程序到执行程序的过程 编译:将写好的高级语言编译成机器语言,就是一个指令集合,即模块。
链接:将各个模块和所需的库函数链接在一起,形成一个大的装入模块。
装入:将装入模块放入到内存中。
装入时地址的装换 之前使用的都是逻辑地址,所以如果直接装入的话会报错,我们需要将逻辑地址改为物理地址,然后再装入到内存中
绝对装入:早就知道要放到那里,那么在链接的时候,直接将逻辑地址改为绝对地址。只适用于单道程序的环境,多道的时候由于异步性,你很难判断一个程序需要放到内存的哪个位置
静态重定位:一般采取的方式,在装入的时候,根据起始的物理地址将所有的逻辑地址加上其实地址。装入时需要空间一次满足,而且不能移动程序的位置
动态重定位:实际上是在程序实际执行的时候才重定位,需要一个重定位寄存器,存放当前的初始地址,然后在执行的时候加上这个地址即可
链接的三种方式 静态链接,就是将所有的模块链接到一起,然后重新分配逻辑地址
装入时链接:在装入的时候链接
运行是链接:这是一种更为灵活的方式,在运行的时候需要哪个模块,就加载哪个模块。
6.2 内存管理的概念
管理内容 内存的分配与回收,要知道哪些地方已经没了,哪些地方还有空间。将一个新的进程应该要插入到那个地方。如何回收一片地址
内存空间的扩展 一个游戏额度空间很大,但是为什么我们的内存只有那么小却可以直接运行在电脑中,这就是内存的扩展
地址装换 动态运行时装入是如今的操作系统使用的方式,绝对装入是单道的时期
6.3 内存保护
意义 进程不能访问其它进程的数据,不能私闯民宅
上下限寄存器方法 设置一对上下线寄存器,检测访问的物理地址是否超过了这个范围。
重定位寄存器 在地址转换的时候提到过,进程的首地址在重定位寄存器中保存,
界限在界寄存器中保存,界寄存器中的数值与逻辑地址比较,当逻辑地址超过这个数值的时候报错,没有超过加上重定位寄存器中的首地址就是物理地址
6.4 内存扩展
覆盖技术 分为固定区和覆盖区
如果需要一直执行的段,就放在固定区中,而如果是两个在逻辑上不可能同时存在的段,放在同一个空间,就是覆盖区。
缺点是用户编程实现复杂,因为需要用户实现哪个结构是覆盖区内容,已经被淘汰
交换技术 将部分进程暂时放到外存,也就是挂起状态,决定哪个进程被调回来就是中级调度
磁盘有对换区和文件区,对换区暂时存放挂起的进程,对换区只占磁盘小部分,IO速度比较快
PCB依旧在内存中,以保存信息。
优先换出低优先级的,阻塞进程,在内存中时间长的(防止低优先级的一直被换出而导致饥饿)
6.5 内存的分配与回收
分类 分为单一连续分配,固定连续分配,动态连续分配
外部碎片与内部碎片 外部碎片是 整个内存空间,分配给进程,到了不能分配的时候,还有剩余的空间
内部碎片是分配给一个进程一定的空间,但是这个进程没有用完这个空间
单一连续分配 内存中只有一个进程,将整个内存空间分配一个进程
实现简单,无外部碎片,但是
固定连续分配 将内存空间划分给固定大小的空间,每一个空间里放一个作业
这个固定大小可以都是等大的,也可以是有不同大小的以满足不同的需求
如果有多个不同的大小,需要有一个分区说明表,对内存的分区做一个记录,里面有大小、起始地址、是否已经被分配
无外部碎片,当程序太大的时候,需要使用覆盖技术,会产生内部碎片,使用于存储多个相同对象。如
动态连续分配 有多大用多大的空间
空闲分区表:包括空闲分区的信息,区号,大小,起始地址
空闲分区链:将所有的空闲分区用双向链表的形式存储,每一个节点包括节点分区的大小
以上是两种常用的存储空闲信息的方式,用哪一个空闲分区存放新的进程是一个大的命题,动态分区分配算法
插入到新的空闲分区的时候,如果当前的分区空间大于新进程大小,就直接在表中减少这个分区的大小,如果刚好等于,就去掉这个分区
如果一个进程执行结束,将前后的空闲分区和在一起,如果没有相邻的空闲分区,新增一个空闲分区
会产生外部碎片,可以用拼凑的技术减少。用动态重定位的方式,将进程全部凑在一起,空出一个大的空闲分区来使用
6.6 动态分区分配算法
首次适应算法 每次从低地址查找,找到能放的就放那
最佳适应算法 每次从最小的容量开始查找,找到能放的就放了。
会造成很多的碎片
最坏适应算法 从最大的容量开始查找
缺点是大的分区很快就会没有
优点是查找的次数少
邻近适应算法 这是首次适应的一个升级版本
首次适应需要查找的次数多,临近适应算法是从上一次查找到的地方开始找起。
缺点是大分区也可能被用完
6.7 非连续动态分配算法
定义 之前的都是需要将用户进程作为一个整体,把所有的数据放在一起
非连续分配是将用户进程拆分成若干个部分,到内存中的空闲的地方去
分页存储管理 将内存分成若干个内存块,将用户进程也分成和内存块一样大的页。页号从0开始
地址转换 分页之后的逻辑地址到物理地址的转换就成了一个问题。
这一页的起始地址加上偏移量就是物理地址,页号=逻辑地址/页面长度 偏移量=逻辑地址%页面长度
那么还需要知道这一页在内存中的起始地址。看页表即可知道
页面大小用二的整数幂的含义 假如页面大小是2的12次方,那么用二进程表示的逻辑地址中,最后12位就是页面的偏移量,剩下的就是页号
页表 页表是页号与块号的对应表,哪一页放在哪一块上
如何得知页号对应的起始地址,需要将对应块号乘以内存块的大小即可
页表可以不放页号信息,只需要知道页表起始地址和每一项的长度,就可以算出来
快表 存放在cache中的存取更快的表。能够提高操作系统的性能
普通的分页技术需要访问两次内存,一次访问页表,再访问页所在的内存。
快表存放的是最近访问过的数据,是页表的一部分,同时多一个状态的表项,访问页表时会先看快表中有没有这个元素
访问快表的行为和判断是否页号越界是可以同时进行的
反向页表 内存中有多个进程,多个页表,占用内存
不用逻辑页号得到页表项,用物理地址加进程ID hash得到一个号码,找到对应页表项,通过指针得到物理块,内存中只要一个页表即可。
使用反向页表
6.8 多级页表
定义 将页表离散放在内存中
多级页表 假如有一个按字节编制的内存,40位逻辑地址,有4kb大小,页表项是4b。
一个页面能放2的10次方页表项。所以需要10位的二进制来表示页号。需要12位二进制来存放页。那么还剩下28位,可以有三级页表。8 10 10
缺点 二级页表相比与普通的页表来说,需要先访问顶级页表,再访问二级页表,最后访问内存,所以需要三次,多了一次
6.9 分段管理
分段 用户程序会按照自身的逻辑,将程序划分成若干段,就比如在main函数是一个段,一个子函数是一个段
段名是程序员自身定义的,可读性提高,但是实际上会将段名转化成段号
段表 与页表不同的是,每一段的长度是不固定的,所以需要我们存放段的长度。与每段的起始位置,也就是基址。
段表项的长度是固定的,所以我们可以不用存放段号。
分段的优点 程序员能够知道自己的程序被分段。
不可被修改的代码被称为可重入代码,就是说没有变量名,可以被共享
可重入共享代码是一个段,当需要共享访问这一段代码的时候,就直接指向同一个共享的段即可
缺点 会有外部碎片
6.10 分页与分段的区别
分页是一维的,只需要知道一个逻辑地址即可,而分段是二维的,需要段号和段内逻辑地址两个参数
分段的逻辑地址是可以不连续的,每段的逻辑地址都是从零开始。
段长是不固定的,页大小是固定的。
6.11 段页式管理
分段和分页的结合,就类似两级页表,将段当成是顶级页表,可以实现模块化的同时,实现将页作为基本存储单元
段页式管理和分页一样不存在外部碎片
6.12 虚拟内存
传统内存管理方案的缺点 必须一次性将所有的进程装入内存,作业很大的时候不能一起运行
所有的进程在运行完前都会一直运行,知道进程结束,浪费
高速缓存 高速缓存是快于内存,慢于寄存器的,将近期需要频繁访问的数据放在告诉缓存中可提高效率
虚拟内存定义 将暂时不需要访问的信息放在外存,如果访问的时候发现有需要的数据没有,就到外存中调回来。
实现一个看似很大的内存,就是虚拟内存
虚拟内存的容量 虚拟内存的最大容量一般由cpu的寻址地址决定,还可能由内存和外存之和决定,取小的那个。
寻址地址是对应的内外存位置,如果没有那么多的位数,再大的外存也不能参与交换。
请求调页 虚拟存储技术允许作业可以分多次调入内存。
当发现需要某个页的时候,需要从外存中调到内存就叫请求调页
这是指请求分页管理技术,如果是请求分段,名称随之改变
页面置换
当内存满了的时候,需要调出一部分页面。
需要有调入内存后是否被修改的信息,如果需要将一个页面调入外存,那么新调进来的就会直接覆盖调出的位置,如果调出页面没有被修改过就什么都不做就可以了。
覆盖
6.13 请求分页管理
定义 将虚拟存储技术与分页管理相结合。
页表 基本页表只需要有隐含的页号信息和所在的内存块信息即可
请求分页的页表还需要知道:是否调入内存,最近使用次数(-供置换算法使用),调入后是否被修改过,在外存中的地址
缺页中断 需要请求调页的时候会有缺页中断,属于内中断,进程进入阻塞队列,等待操作系统将缺失的页调入内存。
如果分配的内存块还有空闲的,那么就请求调页,如果没有,直接置换即可
6.14 页面置换算法
缺页率 缺页中断次数/访问页次数
比较置换算法性能的一个标准
Belady异常 为一个进程分配的块多了,缺页率反而下降的现象,也是一种评价标准
最佳置换算法 OPT 淘汰以后用不到的,或者是很长时间用不到的
这是一种理想的算法,性能是最好的,但是无法预测未来需要哪些页,所以只能作为比较性能的参照标准
先进先出算法 FIFO 调出最先进来的页
将调入的页组成一个队列,从队头取出。
实现起来简单,但是性能不好。
第二次机会算法 依旧用FIFO的
最近未使用算法 LRU(least recently used) 将闲置最久的页调出
新增一个标志位,记录一个页有多久没被访问了。
性能好,实现开销大,需要多一个标志位和比较闲置时间。
时钟置换算法 CLOCK 按顺序找,找到的第一个最近没有使用的页置换出去
设置一个访问位,将所有的页面组成一个队列,访问过的就设置成1,那么怎么变成0。
指针从现在的位置开始扫,扫访问位是0的就置换出去,是1就把1换成0
如果都是1,开始第二轮扫,必然会有0了。
改进型的时钟置换算法 以往的时钟算法没有考虑一个页面是否被修改过,改进后置换多参考了修改位。
第一轮:扫到了00的就置换
第二轮:扫到了01就置换,将访问过的访问位都改为0
再扫一次00,再扫一次01.
访问位的优先级是要高于修改位的
改进型最多扫4次,旧版的最多2次
6.15 页面分配策略
驻留集 分配给一个进程的所有物理块
一般小于进程,但是不能太小,太小会导致置换次数多
固定分配与可变分配 驻留集是否可变
局部置换和全局置换 全局置换是在所有内存块中找一个可以换的,局部置换是指换分配给的该进程的块中找
全局置换只存在于可变分配
全局置换会使被置换的内存缺页率增加
预调页策略 预调页策略 预测可能会用到附近的页,就提前将附近的页调进来,批量调降低开销,但是预测成功率只有50,一般用于进程初次调入
调换位置 外存有交换区和文件区,如果将页放在外存,就可以提高效率
如果交换区不够大,可以将不需要修改,只需要读,而不会写的页放在文件区
抖动 刚调入的页被调出,这是分配的内存块不够的表现
工作集 某一段时间内,访问的页面数量
窗口大小:访问4个页后,访问的不同的页数量,如果访问了两次页3,窗口大小是4,工作集是3
可以更具工作集动态分配内存块
7 文件
7.1 逻辑结构
物理结构与逻辑结构 物理结构是实际存放方式
逻辑结构是用户角度结构,比如链表逻辑结构是表,逻辑结构是多个离散内存与指针。
有结构文件 无结构也被叫做流式文件,比如txt文件。
有结构文件是指记录式文件,类似excel表格,有关键字,记录和记录项
物理结构上分为可变长文件和定长文件,区别在记录的长度是否固定,我们重点讨论的就是可变长文件。
逻辑结构又可分为顺序文件,索引文件,顺序索引文件。
顺序存储 将记录用顺序表或链表形式存储,可变长也可定长。
顺序存储还分为串结构和顺序结构,即记录是否有序,串结构是无序的,比如按文件名排序。
可变长的顺序存储需要储存每一条记录的长度,以定位下一个记录。所以不能实现随机存取,即不能直接获取第三个记录。
顺序结构的顺序存储可以通过关键字快速定位到记录。
顺序文件一般是指顺序结构的顺序存储
顺序文件的缺点是增删的效率比较低。
索引结构 可变长文件查找需要从第一个开始往后,所以可建立一张索引表,找到对应的记录。
索引表中存放的是索引号(0,1,2,3),长度(m1,m2),指针(指向对应记录物理地址);
索引顺序可与关键字相关,这样可以快速检索到,而且可以随机存取。
也可以根据不同的关键字建立相应的索引表,比如按姓名,按学号,实现不同功能。
索引表提高了索引速度,但占用内存。
索引顺序结构 索引顺序结构是将多个记录组成一个索引项,索引表中一项指向的是一组的记录.
索引表中可以不按关键字来排列,以提高插入删除的速度。
多级索引表:如果就是这样的索引顺序结构,那么检索的速度其实没有多大的提高,我们需要保证索引的速度不受太大影响。
可以用第一级放年份,第二级放对应月份
7.2 文件目录
逻辑结构 文件目录存放的是文件,是一个目录表,一个记录里放了文件名,对应权限,修改时间,物理地址等信息。
最重要的是名字与对应的物理地址,实现按名查找。
读取流程 从文件目录中根据文件名找到对应文件,根据记录找到物理地址。
FCB file control block 文件控制块,文件目录记录的别称。
发展 单级目录,一个目录放了所有的文件
两级目录,一个目录放了用户名与对应文件目录信息,另一张放了所有该用户的文件信息。
多级目录,也叫树形环形目录,在两级目录的基础上,能够让用户将文件分类。
多级用户缺点 共享文件复杂。
解决方案 设置一个共享结点,两个目录里面都有指向这个文件的pcb,共享结点自身有计数,记录有多少目录在使用自己,当没人使用了结点删除。
pcb改进 将一个记录除了名字的信息放在一个结点中,目录中实现名字和对应结点的映射
为什么这样算改进,明明增加了一个指针。
假如一个目录中有100个文件,一个磁盘块存放20个记录,需要查找的文件放在最后,那么需要读取5次磁盘块
如果一个磁盘能放100个,只需要读取一次。记录中只有名字和指针,瘦身了pcb,提高索引效率。
7.3 物理结构
磁盘块 磁盘中分为和内存块大小相等的磁盘块,方便存取。
进程页与内存块的关系,就相当于文件块与内存块的关系一样。
文件分块放在磁盘块中。
连续分配 一个文件块放在连续的磁盘块中。
文件目录中pcb只需要记录起始块号和总共有几块,就能够实现直接读取。
每一个文件块有逻辑块号,起始块号加逻辑块号就能找到对应磁盘块。
读取速度是最快的,一般用于存放大型系统文件
缺点:文件变大时,相邻的物理块没空隙的时候,需要把整体移动,比较麻烦,而且产生碎片。
隐式链接 文件块离散存放,文件目录存放开始块与结束块信息。
每一个文件块有一个指针,指向下一个物理块。查找一个块从开始块找到结束块即可。
相当于链表。
显式链接 也是离散存放,文件目录只存放其实块。
有一个表,存放的是下一个块的信息,相当于顺序表。
优点是无需读取磁盘找下一个块,只需要从表中查找即可,读取速度优于隐式链接。
缺点是表会占用空闲空间。
索引 有一张类似页表的索引表,放着文件块与物理块的关系
这种方式能够支持随机存取,但是索引表占用一定空间。
索引表过大的解决方案 隐式链接:用指针指向下一个索引表的地址,那当读取下一张索引表的项时,需要读取前面所有索引项,不能随机存取,没有了索引表的优势。
多级索引:与多级页表类似,当顶级索引表没有在内存中时,读取k级索引表中的项,需要读取磁盘k+1次
混合索引:一些的索引项是一级的,直接的,一些是多级的,二级,三级,或者更多级。当小文件时,只需要使用一级的索引即可,当有大文件时才会用到后面的多级索引。而小文件居多。
7.4 分配与回收管理方案
分区 磁盘分为多个区CDE,每一个都有一个文件区和一个目录区(放文件目录和fcb),一个区可以是多个磁盘合并
空闲表 第一个空闲块号与相邻有多少个块数。
连续分配使用
分配:分配策略与分页相似,有首次适应,最佳适应,最坏适应
空闲链表 分为空闲盘块链表与空闲盘区链表,盘块时把块链接在一起,盘区指的是多个相邻的盘块。
分配:操作系统存放链头与链尾,盘块像队列一样,出队,盘区先用首次适应等算法找到一个大小合适的分配,如果没找到再出队。分配多个块的时候,盘区的效率比盘块高。
回收:盘块回收入队,盘区回收合并相邻,没有相邻盘区再当做新盘区入队。
位示图法 相当于用bool二维数组存放,0表示空闲。用多个字表示块号,字号是横坐标,每一个字有多位(8 16等),位号是纵坐标。
比如第一个字的第一位就相当于arr[0][0]
如果字号和位号都是从零开始,块号=字长*字号+位号 (块号是从零开始的),字号=块号/字长,位号=块号%字长。
分配:按顺序找到一个合适的相邻的空闲块,如果是需要k个块的大小,就找相邻k个0
回收重置为1即可。
成组链接法 是unix使用的,有一个超级块,在开机就放入内存。
超级块里放着指向下一组的指针和那一组的大小。一组就是多个空闲块,每一组有上限。
第一组的第一个块相当于第二个超级块,放的是第二组的指针和第二组大小。
分配:从超级块开始,读取下一组块大小,找到合适大小分配。如果第一组整组都被分配没了, 把第二组作为第一组,超级块放第二组信息。
回收:看第一组是否满了,没满,放第一组,满了,就把回收回来的作为第一组,改超级块信息。
文件基本操作
create 系统调用,主要参数是名字和路劲以及大小,找到对应位置,找到空间块,修改文件目录
delete 参数主要是名字和路径
open 参数主要是名字和路径以及操作类型,操作类型用于判断权限是否允许打开。
打开文件表 打开操作之后会把目录项复制到打开文件表,这个表里放了所有的打开文件。
系统打开文件表有一个索引号(也叫文件描述符),以及一个计数器,记录有多少进程在使用。
每个进程有一个打开文件表,有读写指针,就是读写到哪了,还有访问权限,对应系统索引表。
close 删除对应进程打开文件表,系统打开文件表计数器-1;
read 将文件内容读入内存
write 参数是写入大小和打开文件索引号,write肯定会先open,只需在打开中找即可。
文件共享 第一种是之前提到的共享结点,第二种是基于符号链的软共享。
软共享就相当于快捷方式,保存的是共享文件的地址,这个不计入共享结点的计数器,所以当硬共享都删除结点的时候,软共享指向的文件就失效了。
7.5 文件保护
口令 文件pcb或者索引表中有一个字符串,当输入正确的时候就能使用,但是口令是放在系统内部不够安全。
优点是简单,开销小
加密 将文件以一种形式形成乱码,只有特定的密码能够译码成原本的文件
缺点是加密解密需要时间
访问控制表 在fcb中加一个访问控制表,记录了每一个用户对于这一个文件的权限
缺点是用户多了占用空间多,所以可以将用户分成组,每组共用一个表。
优点是能应对复杂情况,实现灵活
8 磁盘
8.1 磁盘结构
磁盘定义 表面有磁性物质,能存放二进制数据
结构 磁盘是一个多层的圆柱体,每一层叫盘片,盘片可以有两个盘面。一个面上有多个圈,一个圈就是一个磁道。盘面上又分为多个扇形区,叫扇区。扇区大小一般是512字节
簇和块是同一个东西,扇区是固定的物理结构,太小,一般不作为分配的基本单位,所以操作系统用磁盘块作为分配单位.
磁头 每一个盘面有一个磁头,用来读取数据的,磁臂控制磁头沿着磁道转都,一个磁臂控制所有的磁头,所以磁头只能共进退
读取流程 块号就是柱面号,盘面号,扇区号。根据这三个控制激活那个磁头,与磁头在哪个道上,以及读取哪个区域。旋转磁盘就能读取
分类 固定头磁盘和移动头磁盘,前者每一个磁道上都有一个磁头。
三级存储体系 cache高速缓存,主存,外存()
8.2 磁盘调度
磁盘调度时间 寻找时间,启动磁臂,移动磁臂的时间,磁头移动到对应的磁道的时间
延迟时间:转速r 1/r就是转一周需要的时间,找到扇区的时间平均是0.5*(1/r);
传输时间:读取字节/磁盘字节 * (1/r)
后两个时间都只与转速有关,所以磁盘调度算法的评价标准就是寻找时间。
先来先服务FCFS 找最先请求的,这是最公平的算法。
最短 找最近的
Scan扫描算法 从最左边的磁道扫到最右边,再从右扫到左,
C-Scan扫描算法 扫到最外面后回到最里面不处理,快速回到最里面,为了平均每个磁道的处理频率,但是平均时间比上一个算法大
c-look算法 不扫描到最外面的访问时间,只需要扫描请求的磁道中最外面的,再返回到请求的当中最里面的,返回过程也不调度。
8.3 检测延迟时间的方式
交替编号 当需要连续读取234号扇区,在读取2号时,需要一定时间处理,所以不能立即读取3号,只能再转一圈。
可以用04152637这样的交替编号方式给予一定的时间处理。
柱面号在盘面号之前 磁盘的编号是柱面盘面扇区的方式,如果让盘面号在柱面号之前,连续访问磁盘的时候,跨柱面访问,需要移动磁臂,这个耗时较多。
错误命名 当跨盘面连续读取的时候,上一个盘面读完,访问下一个盘面的0号扇区,如果采用04152637这样的编号方式,上一个盘面刚读完7,这时要读取这个盘面的0,但是需要一定时间处理。
所以将两个相邻的盘面交替命名,这个盘面改为70415263
8.4 磁盘管理
开机的时候需要对磁盘进行初始化
初始化 低级初始化:将各个磁道划分成扇区,扇区有头尾和数据区域,管理结构在头尾,还有校验码(冗余码,奇偶校验等差错检测)
分区:
逻辑格式化:初始化文件系统等工作
自举程序 开机会进行初始化工作,这个工作保存在一个自举程序中,自举程序如果保存在ROM(只读,无法修改)中,那么自举程序就无法更新
所以将自举程序放在启动块中/引导块/启动分区
自举装入程序放在ROM中,自举装入程序找到启动盘,找到自举程序。
坏块 对于坏块的管理,可以用一个表和链表来保存信息,标记哪些不可用,也有一种方案是使用备用块。
8.5 RAID 磁盘阵列
定义 将多个磁盘拼接在一起。Redundant Array of Independent Disks 独立磁盘冗余队列
关键概念 1镜像,会将数据镜像放在多个磁盘中。
2是数据条带,将一个数据拆分放在不同磁盘,可以在读取时并发读取多个磁盘,提高读取速度。
3是数据校验
优势 高性能,大容量,可靠性高,可管理性强。
分级 JBOB Just a Bunch Of Disks,不属于标准等级,没有控制系统,顺序存取,且没有数据保护
RAID0:0-6都是标准等级,0是性能最高的,不要各种安全检测,只使用数据条带,离散存放数据。
1是无校验的镜像
2使用海明码校验。
3是有专用校验位的数据条带。
4是专用块检验的数据条带。
5是最常用的,01的折中。
6是代价最高的,双重分散检验
还有各种组合等级,比如01 10等
9 IO
9.1 IO控制器
定义 IO控制器也叫设备控制器,就是接收cpu发出的指令,执行对应的指令
组成 接口,IO逻辑,接口是有cpu接口和设备接口,IO逻辑是IO控制器的核心,用于实现各种功能。
cpu接口有三条线,数据地址和控制,前两条链接寄存器,最后一个链接IO逻辑,
9.2 控制方式
直接控制 轮询,cpu不断查看设置是否闲置,优点是实现简单,缺点是cpu忙等,一直的等待,每次读写一个字的数据。
中断驱动程序 阻塞进程,等到io完成,发出中断信号,cpu再读入一个字扥而数据。
这种方式cpu在面对大量的数据时,读取频率高,需要读取的字数多,中断的处理时间就长。
DMA drect memory access 对块设备的控制,每次cpu说明读取写入的大小等信息,等到io完成的时候再发出中断信号。
通道控制方式 相当于是弱化的cpu,cpu将指令放在内存中,通道慢慢读取指令,处理完了之后再发出中断信号,这里的cpu的指令相当于是一个程序,通道相当于一个cpu。
通道控制字(Cannel Address Word)就是通道指令。第一个指令是CAW通道寻址。
9.3 假脱机技术
定义 假脱机技术是在模拟脱机技术,脱机技术就是将cpu和io分离开,假脱机技术又叫Spooling技术
输入井和输出井 是磁盘中模拟早期的磁带的东西,从内存中得到的放在输出井,输入的内容先被放在输入井然后放在缓冲区,当缓冲区满了,cpu再读取。
共享打印机实现 当有多个进程需要使用打印机的时候,会在输出井中申请多个空间给这些进程使用,并有一个打印队列,里面存放的是关于打印信息的数据。
9.4 中断
每次执行一条指令都会判断是否有中断信号
中断有优先级与中断屏蔽,有的中断信号可被屏蔽,有的cpu必须无条件执行。
中断优先级比较高的是硬件故障,外部和IO
9.5 软件层次
分层用户层软件,设备独立性(设备无关性),设备驱动程序,中断处理程序,硬件
用户层软件 主要提供与用户的接口
设备独立性 这一层里是独立于设备的IO软件,与用户软件有所不同,它们几乎处理了所有的功能,如安全管理,缓冲区,差错管理,分配回收
设备驱动程序 不同厂家,或者不同型号可能都需要不同的驱动,设备里面的硬件特性不同,所以需要一个驱动程序来解读cpu传过来的指令,对其做出处理。
9.6 设备分配
分类 安全分配,IO操作的时候需要阻塞等待完成
不安全分配,发送指令后继续执行。
静态分配:运行前分配所有的资源
动态分配:运行中动态申请
结构 一个通道控制多个设备控制器,一个设备控制器可控制多个设备。
数据结构 DCT(device control table)设备控制表,队列指针,设备状态等。
COCT控制器控制表
CHCT通道控制表,通道状态,通道控制的第一个控制器指针
SDT系统设备表 所有的设备信息都在里面
分配流程 物理地址名SDT->DCT->COCT->CHCT从分配设备,分配控制器,到通道。如果没有空闲的,就阻塞进程
LUT从物理地址名找设备不方便,所以设置了一个逻辑设备名,LUT就是逻辑设备名表,映射逻辑名与物理名
常用算法 先来先服务算法、高优先级算法
9.7 缓冲区
作用在内存中放一个缓冲区,大小一般是一个块大小。等到缓冲区满了,cpu再读取,cpu放在缓冲区中,输出设备慢慢读取。
单缓冲 单缓冲是只有一个缓冲区,缓冲区不空就不能取,只能满了之后一次性读取。
双缓冲 管道通信中的管道就是缓冲区。
循环缓冲队列 就是一个循环队列,多个缓冲区
缓冲池 有三个队列,空缓冲队列,输入队列,输出队列,输入没有缓冲区就从空缓冲中取。