计算机操作系统

操作系统通过分页、分段和段页式管理内存,解决资源分配和碎片问题。分页管理中,逻辑地址到物理地址转换涉及页表、快表等;分段管理则按照信息逻辑模块分配内存,支持文件共享;段页式管理结合两者优点。内存分配策略包括首次适应、最佳适应、最坏适应等。同时,文件系统通过目录结构管理文件,实现文件的逻辑结构和物理结构映射,支持文件的共享和保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

操作系统作为底层硬件和上次用户之间的一个过度层,主要功能是对硬件资源进行管理,同时为上层用户提供方便易用的调用接口,所以研究操作系统主要需要研究的内容有进程、内存、文件、磁盘和IO。

概述

提到计算机,我们首先要明确几个概念,首先是计算模型,模型指的是对问题的描述,算法是解决问题的过程,计算时执行算法的过程,计算工具是执行计算任务的工具,早期没有现代计算机时,计算工具可能是手工计算也可能是算盘之类的简易计算工具。

现在我们在谈论计算机的时候一般指的都是现代计算机,在现代计算机的发展史中,不得不提的一个人物就是冯诺依曼,冯诺依曼是一个美籍匈牙利人,它通过观察计算的过程,总结出一个能够自动运行的计算机应该包括存储器、控制器、计算器、输入设备和输出设备五个部分,时至今日,计算机的总体架构还是遵循冯诺依曼计算机体系。

但冯诺依曼计算机体系只定义了硬件层面计算机的架构,如果让用户直接面向硬件去完成计算过程那几乎是不可能的,所以就需要一种软件,向下能够管理硬件资源,向上能够为用户提供操作这些资源的接口,而这个软件就是操作系统。

概念

概念控制和管理计算机的硬件和软件资源,合理组织计算机的计算工作和资源分配,同时给用户和上层软件提供方便的 接口和环境。它是计算机中最基础的系统软件
功能向下:资源管理 1.处理机 2.存储器 3.文件 4.外部社保;向上:提供操作资源的接口 1.GUI界面;2.命令行接口;3.程序调用接口
产品Windows/Mac/Linux/Android/IOS

特点 

并发性两个或多个事件在以同一时间间隔交替发生,在宏观上同时发生,在微观上交替发生
共享性即资源共享,系统中的资源可供内存中多个并发执行的进程共同使用,共享方式分为互斥共享(比如摄像头)和同时共享(网络资源)
虚拟性把一个物理实体划分成若干个逻辑上的对应物,物理实体是实际存在的,而逻辑上的对应物是用户感受到的
异步性在多道程序环境下允许多个程序并发执行,但由于资源有限,进程的执行不是一惯到底而是走走停停的,以不可预知的速度向前推进

分类与发展

手工处理阶段利用手工在纸袋上打孔的方式实现任务的输入和结果的输出,主要缺点是用户独占全机、人机速度矛盾导致计算机的资源利用率低下
单道批处理阶段引入了处理速度更快的磁带,将打孔纸袋的数据提前读入磁带,然后通过一个监督程序(操作系统的雏形)来控制输入到计算机,计算结果再反向输入到磁带上,这样就解决了人机速度差异,提高了资源利用率,但缺点在于计算机中仍然仅能有一道程序运行,并且cpu有大量的时间在空闲等待IO操作,资源利用率仍然很低
多道批处理阶段一次将多道程序读入磁带,通过监督程序输入到计算机后,计算机并发的执行(此时引入了中断技术),大幅度提高了计算机的使用效率,但缺点在于由于同时处理多道程序,对于某一个用户的响应时间会变长,并且没有人机交互功能,用户只能死等
分时操作系统计算机以时间片为单位,轮流为各个用户/作业提供服务,并且用户可以通过终端与计算机进行交互,但缺点在于分时操作系统在处理任务时所有任务都是平等的,不会区分紧急与非紧急任务
实时操作系统能够优先响应一些紧急任务,某些紧急任务不需要时间片排队
其他网络操作系统、分布式操作系统、个人计算机系统

运行机制

操作系统提供的功能分为内核功能和非内核功能,内核功能一般是操作系统提供的最基础、最重要的功能,比如时钟管理,中断处理以及元语等,有时还包括进程管理、存储器管理和设备管理,包含了进程、存储器和设备管理的内核叫做大内核,反之叫做微内核,微内核更精简,但在运行过程中用户态和核心态的切换会增加,而大内核虽然体积大,但运行时用户态和核心态的切换次数会更少,不同的操作系统,往往采用不同结构的内核组成。

两种指令特权指令 和 非特权指令,特权指令一般会对计算机的核心资源进行操作,只有内核程序才有使用权限,而非特权指令主要提供给用户程序使用
两种状态用户态和核心态,用户程序允许在用户态,内核程序允许在核心态,有些操作计算机没有开放给用户程序,此时就会涉及到用户态和核心态之间的相互转化
两种程序在操作系统内核中运行的程序叫做内核程序,操作系统之上的用户程序叫做应用程序,内核程序在核心态运行,既能执行特权指令也能执行非特权指令,应用程序在用户态运行,只能执行非特权指令

中断

操作系统的发展过程,大体上就是一个想方设法不断提高资源利用率的过程,而提高资源利用率就需要在程序并未使用某种资源时,把它对那种资源的占有权释放掉,而这一行为需要通过中断实现。

为了实现并发,计算机的设计者引入了中断机制,中断可以理解成一种特殊的信号,这种信号能够使得进程放弃cpu执行权,并交由操作系统进行后续处理,也就是说,发生中断时,cpu会从用户态进入到核心态,进而展开后续的工作,比如在分时操作系统中,当时钟触发cpu时间片轮转时,就会触发一个中断信号来终止当前进程的执行,然后cpu接到中断信号后,将cpu执行权交给其他进程。

中断分为两种,内中断和外中断,内中断一般指cpu在执行指令时出现了异常情况/终止,它来自于cpu内部,外中断指的是来自外部设备和用户调用等的中断请求信号,比如I/O的开始/完成会向cpu发出中断信号,让操作系统来进行后续处理,再比如在分时操作系统中的时间片切换时也会发出中断信号,或者人工干预等。

举例说明外中断的处理方式:

  1. 每条指令执行完成后,cpu会检查是否有外部中断信号;
  2. 若有外部中断信号,则需要保护被中断进程的cpu环境;
  3. 根据中断信号的类型转入相应的中断程序;
  4. 恢复被中断进程的cpu环境并退出中断,返回到原进程继续往下执行;

系统调用

系统调用指的是用户程序调用操作系统提供的用于操作系统资源的接口的过程,操作系统为了保证系统安全,会托管资源变更类型的操作,上层应用在使用的时候通过调用操作系统提供的接口来达到目标。

系统调用一般都会涉及到对资源的操作,比如设备管理(打印机、显示器等)、文件管理(磁盘)、进程控制、通信控制、内存管理等。进行系统调用时一般会触发中断,即使得操作系统从用户态转化为核心态。

进程

概念

为了实现并发,引入了进程的概念,进程是一种对数据和资源的封装,是分配资源和调度的实体,一个进程主要包含三个部分: 程序段、数据段和PCB,其中程序段是指需要cpu执行的指令集和,数据段存储进程执行过程中需要的数据,PCB(Process Control Block)可以理解成是进程的一个状态机,存放了进程的一些元数据以及状态数据。

状态与转换

Col1Col2
运行态✔️cpu执行权;✔资源资;
就绪态✔️资源阻塞状态❌cpu执行权;
阻塞态❌cpu执行权;❌资源创
创建态操作系统为新进程分配资源,创建PCB
终止态操作系统为进程回收资源,撤销PCB
就绪态 👉 执行态进程被调度
运行态 👉 就绪态时间片轮转或cpu执行权被其他进程抢占
运行态 👉 阻塞态等待系统分配资源或等待某个事件发生(进程的主动行为)
阻塞态 👉 就绪态获取到了所需资源或等待的事件已经发生(被动行为)
创建态 👉就绪态系统完成了创建进程的所有工作,等待cpu执行
运行态 👉 终止态运行结束或运行过程中发生了不可修复的错误

操作系统在对进程进行状态切换时,是通过原语实现的,因为切换进程状态可能涉及到多个步骤,需要保证过程不被中断,而这个原语是基于开启/关闭中断来实现的,也就是说,在一对开启/关闭中断中的指令集合属于一个事务性操作,要么全部成功,要么全部失败,元语操作主要有以下几种:

  • 进程的创建
  • 进程的终止
  • 进程的阻塞
  • 进程的唤醒
  • 进程的切换

进程间通信

在操作系统中,有时候进程和进程之间并不是完全独立的,而需要彼此之间进行交互,这就涉及到了进程之间的通信,进程间通信的方式主要有一下三种:

  • 基于共享内存
  • 基于管道
  • 基于消息传递

基于共享内存的通信方式是在进程所属的内存之外,开辟一个用于进程间通信的内存空间,进程之间需要通信时,就把共享数据放到这个空间内,但基于共享内存的方式在进行数据读写的时候,需要进行互斥处理,以保证数据的安全性。数据的组织方式有两种:

  • 基于特性数据结构的共享,数据结构是固定的;
  • 基于特性区域的共享,对数据结构没有限制,只要不超过特定的内存区域就可以;

基于管道的通信方式是基于一个或多个半双工的通道实现,一个进程向通道中写入数据,另一个进程从通道中读取数据,在基于管道的通信方式下,有两个条件限制:

  • ①写满时,不能再写,读空时,不能再读;
  • ②没写满,不能读,没读空,不能写;

而基于消息传递的通信方式的具体实现有两种,第一种是每个进程都一个消息缓是基于一个全局冲区,一个进程想要与另一个进程通信时,就向对方的消息缓冲区发送消息即可;第二种信箱,进程间通信时将消息发送到这个全局信箱中,最终再中转到目标进程中。

线程

在引入线程之前,进程是cpu调度的基本单位,同时也是资源分配的基本单位,一个进程往往代表了一个应用程序,随着应用程序越来越复杂,一个应用程序以串行的方式执行越来越不能满足功能要求(比如在一个软件应用中同时执行多个功能),引入了线程后,线程就成了cpu执行的最小单位,而进程仅仅作为资源分配的最小单位,这即增加了并行度,提高了系统资源利用率,同时也提高了多线程环境下的运行效率(因为在同一进程内的多个线程间的切换的代价要远远小于进程间切换的代价)。

Col1Col2
资源分配①传统的进程机制中,进程是资源分配和调度的基本单位;②引入线程后,进程是资源分配的基本单位,线程是调度的基本单位
并发度①传统的进程机制中,只能进程间并发;②引入线程后,线程间并发,提高了并发度
系统开销①传统的进程机制中,需要切换进程的运行环境,系统开销很大;②线程间并发后,如果是同一进程内的线程切换,则不需要切换进程环境,系统开销小;③引入线程后,并发所带来的系统开销减小;

关于线程的一些说明:

  • 线程是处理机调度的基本单位;
  • 在多处理机计算机中,各个线程可以占有不同的CPU;
  • 每个线程都有一个线程ID和一个线程控制块(TCB);
  • 线程也有就绪、阻塞、运行三种状态;
  • 线程几乎不拥有系统资源(进程是系统资源的载体);
  • 同一个进程中的不同线程,共享进程资源;
  • 同一个进程中线程的切换,不会引起进程的切换;
  • 不同进程中线程的切换,会引起进程的切换;
  • 切换同进程内的线程,开销比较小;
  • 切换进程,开销比较大;

在实现上,一个线程可以分为用户级线程和内核级线程两部分,用户级线程指的是用户看到的线程,而内核级线程指的是操作系统内核线程,一般用户线程需要依附在一个内核线程上,所以线程的实现模型就有三种:

Col1Col2
多对一模型多个用户线程对应一个系统线程;优点:进程管理的开销小;缺点:一个线程阻塞会导致进程内的所有线程阻塞
一对一模型一个用户线程对应一个系统线程;缺点:进程管理开销大;优点:各个线程可以分配到多核处理机并发执行,并发度高
多对多模型即m个用户线程对应n个系统线程,一般m > n;优点:集二者之长

处理机调度

所谓调度,就是在资源有限的情况下,通过某种规则来选择需要执行的任务的过程,对于计算机来说,调度就是从数量众多的线程中选择一个交给cpu来执行。cpu的任务调度可以从三个层面来看:

Col1Col2Col3Col4频率Col6
高级调度作业调度按照某种策略,从外设后备队列中选择合适的作业调度到内存中,并为其创建进程外存→内存,面向作业最低无→创建→就绪态
中级调度内存调度按某种规则从挂起队列中选择合适的进程将其调入内存外存→内存,面向进程中等挂起态→就绪态(阻塞挂起→阻塞态)
低级调度进程调度按某种规则,从就绪队列中选择一个合适的进程为其分配处理机内存→CPU最高就绪态→运行态

这三种不同层次的调度针对的实体不一样,高级调度和低级调度比较好理解,中级调度中涉及到一个挂起状态(就绪挂起和阻塞挂起),这里涉及到了虚拟内存技术,也就是说,实际的可用内存要比硬件层面的内存要大,这是由于计算机把一部分外设临时作为内存使用了,一些暂时不会被执行的进程会被计算机从主内存转入到虚拟内存,此时这个进程就处于挂起状态。

引入挂起状态的主要目的是腾出更多的主内存空间来运行任务。那么在什么时候操作系统会对任务进行调度呢,总的来说就是发生中断的时候,即当前运行的任务主动或被动的让出执行权的时候:

Col1Col2
主动放弃①进程正常终止;②运行过程中出现异常而终止;③发生阻塞(IO等)
被动放弃①时间片轮转;②有更高优先级的任务进入就绪队列

但也有一些不能进行任务调度的情况:

  • 在处理中断的时候(引入中断本身是触发任务调度的入口,中断过程本身不能被中断);
  • 进程在操作系统内核临界区的时候(比如对系统资源进行操作的时候);
  • 元子操作的时候(比如前面说到的各种元语);

调度从狭义上理解,就是将一个实体从一个范围内按照某个规则转移到另外一个范围内,比如对进程的三个层次的调度,而切换指的是在调度的过程中,如果是对不同实体的调度,就会发生切换。广义上的调度包含了狭义的调度和切换。对于进程的调度是需要系统开销的,并不是调度的越频繁,并发度就越高,有可能正好相反。

进程调度的最终目标是占有cpu的执行权,推进任务的执行,而占有cpu的方式也有两种:

Col1Col2
抢占式可由操作系统剥夺当前进程的cpu执行权
非抢占式只能等待正在运行的进程主动放弃cpu执行权

调度算法评价指标

Col1Col2
CPU使用率CPU忙碌时间/总时间
系统吞吐量总共完成的作业数/花费的总时间
周转时间作业完成时间 - 作业提交时间
平均周转时间各作业的周转时间之和 / 作业数
带权周转时间作业周转时间 / 实际运行时间
平均带权周转时间各个作业的带权周转时间之和 / 作业数量
等待时间作业或进程等待被服务的时间的综合
响应时间用户从提交请求到收到响应的时间的总和

调度算法 

先来先服务、短作业优先、高响应比优先、时间片轮转、优先级调度、多级优先反馈队列。

先来先服务算法

Col1Col2
算法思想从公平的角度考虑,类似于现实生活中的排队买东西
算法规则按照作业/进程到达的先后顺序进行服务
调度用于作业调度时,考虑的是哪个作业先进入后备队列;用于进程调度时,考虑的是哪个进程先进入就绪队列
是否可抢占
优点公平、算法实现简单
缺点排在长作业后面的短作业需要等待更长的时间,对短作业来说用户体验不好
是否会导致饥饿

短作业优先算法

Col1Col2
算法思想运行所需时间越短的作业优先级越高;追求最少平均等待时间
算法规则短作业优先得到服务
调度即可用于作业调度,也可用于进程调度
是否可抢占否;但也有抢占型的算法
优点能够达到最短的平均等待时间
缺点不公平,对短作业有利,对长作业不利
是否会导致饥饿

高响应比优先算法

算法思想综合考虑作业的等待时间和要求服务的时间
算法规则每次调度时,计算作业的响应比,响应比高的优先运行
响应比(等待时间+要求服务的时间)/ 要求服务的时间
调度即可用于作业调度,也可用于进程调度
是否可抢占否,只有当前运行的作业主动放弃处理机时,才需要调度,才需要计算响应比;
优点
是否会导致饥饿综合考虑了等待时间和需要运行的时间,要求服务的时间相同时,等待时间越长的越会被优先执行,等待时间相同的,要求运行的时间月短越会被优先执行
是否会导饥饿

时间片轮转算法

Col1Col2
算法思想每个作业在一定的时间间隔内都可以得到服务
算法规则按照进程到达就绪队列的顺序,轮流让各个进程执行一个时间片,若进程未在一个时间片内执行完,则剥夺处理机,将进程重新放到就绪队列的队尾进行排队
调度用于进程调度
是否可抢占若进程未能在一个时间片内运行完成,将被强行剥夺处理机的使用权,因此时间片轮转调度算法属于抢占式算法,由时钟装置来发出时钟中断通知cpu时间片已到
优点公平、响应快,适用于分时操作系统
缺点由于高频率的进程切换,因此有一定的开销;不区分任务的紧急程度;
是否会导致饥饿

优先级调度算法

Col1Col2
算法思想根据作业的紧急情况来决定作业的处理顺序
算法规则选择优先级高的作业优先执行
调度即可用于作业调度,也可用于进程调度
是否可抢占抢占式和非抢占式都有,非抢占式只在进程主动放弃处理机时进行调度;抢占式还需要在就绪队列发生变化时,检查是否会发生抢占;
优先级用优先级区分紧急程度、重要程度,适用于实时操作系统,可以灵活的调整各种作业/进程的偏好程度
缺点若源源不断的有高优先级进程到来,则可能会导致低优先级进程的饥饿
是否会导致饥饿

多级优先级队列

Col1Col2
算法思想对其他调度算法的折中权衡
算法规则①设置多级就绪队列,各级队列优先级从高到低,时间片从小到大;②新进程到达时,先进入第一级队列,按照FCFS原则排队等待被分配时间片,若用完时间片后进程还没有结束,则进程进入下一级队列的队尾,,如果此时已经是在最下级的队列,则重新放回到该队队尾;③只有第k级队列为空时,才会为k+1级队列的进程分配时间片
调度用于进程调度
是否可抢占抢占式算法。在k级队列的进程运行过程中,若更上级的队列中进入了一个新的进程,则由于新进程处于优先级更高的队列中,因此新进程会抢占处理机,原来运行的进程会放回k级队列的队尾
有点对各类型的进程相对公平;每个新到达的进程都可以很快得到响应;短作业只用较少的时间就可以完成;
是否会导致饥饿

同步与互斥

互斥,指的是对共享资源的互斥访问;
同步,指的是在多线程环境下让线程按照一定的先后顺序执行;

互斥访问时,会涉及到以下四个区域:

  • 进入区:检查是否可以进入临界区,若可进入,则需要上锁;
  • 临界区:访问临界资源的代码;
  • 退出区:负责解锁;
  • 剩余区:其余代码部分;

互斥访问时要遵循以下几个原则:

  • 空闲让进:临界区空闲时,应允许一个进程访问;
  • 忙则等待:临界区正在被访问是,其他试图访问的进程需要等待;
  • 有限等待:要在有限的时间内进入临界区,保证不会有饥饿;
  • 让权等待:进不来临界区的进程,要释放处理机,防止忙等;

互斥的软件实现

单标记法

算法思想:两个进程在访问完临界区后会把使用临界区的权限交给另一个进程,也就是说每个进程访问临界区的权限都是由另外一个进程赋予的。

int turn = 0; //表示当前可以进入临界区的进程号

P0:                         P1:                         
while(turn != 0);           while(turn != 1);
critical selection;         critical selection;
turn = 0;                   turn = 1;
remainder selection;        remainder selection;

turn表示当前运行进入临界区的进程号,而只有当允许进入临界区的进程访问了临界区之后,才会修改turn的值,也就是说对于临界区的访问一定是按照P0 → P1 → P0 → P1这样轮流访问。这种必须轮流访问带来的问题是,如果此时允许进入临界区的进程是P0,但P0一直不访问临界区,那么虽然此时临界区空闲,但是并不允许P1进行访问,这违背了“空闲让进”的原则。

双标记法

算法思想:设置一个布尔类型的数组flag[],数组中各个元素用来标记进程想进入临界区的意愿,比如flag[0]=true表示P0想进入临界区,每个进程在进入到临界区之前会检查当前有没有别的进程想要进入临界区,如果没有,则把对应自身的标志位flag[i]设置为true,之后开始访问临界区。

bool flag[2];    //表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false;  //设置刚开始两个进程都不想进入临界区

P0:                           P1:
while(flag[1]);      ①         while(flag[0]);     ⑤//如果此时P0想进入临界区,P1就一直循环等待
flag[0]=true;        ②         flag[1] = true;     ⑥//标记为P1 想进入临界区
cricital selection;  ③         critical selection; ⑦  //访问临界区
flag[0]=false;       ④         flag[1] = false;    ⑧  //访问完临界区后,将P1访问临界区的意愿改为false
remainder selection;           remainder selection;

如果按照1/5/2/6/3/7的顺序执行的话,P0和P1会同时进入临界区,这违背了“忙则等待的原则”,其原因在于进入临界区之前的 检查 和 上锁 这两个步骤不是一气呵成的,检查后 和 上锁前 可能发生线程切换。

双标记后检查法

双标志后检查法就是将双标志检查法中的②和⑥放到①和③之前,但还是由于可能发生线程切换,就会造成两个进程都卡在while循环处无法进入临界区,这违背了“空闲让进”和“有限等待”的原则,因此各进程都会 因为长期无法访问临界资源而发生饥饿。

Perteron算法

在双标志后检查法中,两个进程都争着想要进入临界区,谁也不让谁,最后谁都无法进入临界区,Perterson算法想到了一种方法,如果双方都想进入临界区,那可以让进程尝试主动让对方先使用临界区。

bool flag[2];      //表示进入临界区意愿的数组
int turn = 0;      //表示优先让给哪个进程进入临界区
P0:
flag[0] = true;
turn = 1;
while(flag[1] && turn == 1);
critical selection
flag[0] = false;
remainder selection;

P1:
flag[1] = true;
turn = 0;
while(flag[0] && turn = 0);
critical selection
flag[0] = false;
remainder selection;

双表示后检查法会出现两个进程一直等待进入临界区,但Perterson中加入了一个谦让的过程,这样,最后执行谦让动作的进程就会打破另一个等待进入临界区的进程的while循环条件,从而打破两个进程同时等待的尴尬局面。

互斥的硬件实现

  • 中断屏蔽法
  • TestAndSet算法
  • Swap指令

信号量

用户进程可以通过使用操作系统提供的一对原语和信号量来操作,从而方便的实现进程的互斥和同步。信号量其实就是一个变量(可以是整型的变量也可以是记录型的变量),可以用一个信号量来表示系统中某种资源的数量,比如系统中有一台打印机,就可以设置一个初始值为1的信号量。

原语是一种特殊的程序段,其执行只能一气呵成,不可被中断,原语是由关中断和开中断指令实现的。软件的解决方案的主要问题是由于进入区的各种操作无法一气呵成,因此如果能把进入区、退出去的操作都用原语来实现,使这些操作能一气呵成,就能避免问题。

一对原语:Wait(s)原语和Signal(s)原语,可以把原语理解成我们自己写的函数,函数名分别未wait和signal,括号里的信号量s其实就是函数调用时传入的一个参数。

整型信号量

不满足让权等待原则,会发生忙等的问题。

//初始化整型信号量s,表示当前系统可用的打印机资源数量
int S = 1;
//wait原语相当于进入区
void wait(int S) {
    //如果资源数量不够就一直等待,这里不满足“让权等待”原则,会发生忙等待的问题
    while( S <= 0);   
    //如果资源数够,就占用一个资源
    S = S - 1;
}
//signal原语相当于退出区
void signal(int S) {
    //使用完资源后,在退出区释放资源
    S = S + 1;
}

业务进程:
wait(s);   //当有一个进程调用了wait原语后,后续的调用者就会在wait内部的while处阻塞等待
使用打印机资源;
signal(s);

记录型信号量

能够解决整型信号量的忙等问题。

//记录型信号量的定义
typedef struct{
    int value;
    struct process *L;
} semaphore;

//某进程需要使用资源时,通过wait原语申请
void wait(semaphone S) {
    S.value --;
    if (S.value < 0) {
        //如果剩余资源不够,使用block原语使进程从运行状态
        //进入到阻塞状态,并把其挂到信号量S的等待队列中
        block(S.L);    
    }
}

//进程使用完毕后,通过signal原语释放
void signal(semaphone S) {
    S.value ++;
    if (S.value <= 0) {
        //释放资源后,若还有别的进程在等待这种资源,则使用weakup原语
        //唤醒等待队列中的一个进程,该进程从阻塞态变成就绪态
        weakup(S.L);    
    }
}

用信号量实现互斥

在使用信号量实现互斥时需要注意:

  • 对不同的临界资源,需要设置不同的互斥信号量;
  • P、V操作必须成对出现;
//初始化信号量=1,实现互斥访问
semaphore metux = 1;

p1() {
    p(metux);   //使用临界资源时需要枷锁
    临界区代码;
    v(metux);   //使用临界资源后需要解锁
}

p2() {
    p(metux);
    //临界区代码;
    v(metux);
}

用信号量实现同步

同步就是要让并发进程按照要求有序推进执行进度。

  • 设置同步信号量s,初始值为0
  • 在“前操作”之后设置v(s)
  • 在“后操作”之前设置p(s)
//信号量实现同步,初始化信号量为0
//程序的目标执行顺序是 代码4要在代码2之后执行
semaphone S = 0;
p1() {
    代码1;
    代码2;
    v(S);   //在 “前操作” 之后设置v(s)
    代码3;
}

p2() {
    p(S);  // 在后操作之前设置 p(s)
    代码4;
    代码5;
    代码6;
}

死锁

死锁,就是各个进程之间互相等待对方手里的资源,导致各个进程都进入阻塞状态,无法向前推进的现象。与死锁的现象相类似的还有饥饿和死循环管,但他们之间有着本质的区别:

Col1Col2
死锁死锁一定是循环等待对方手里的资源导致的,如果出现死锁,那至少有两个或两个以上的进程进入到了死锁状态,另外,发生死锁的进程一定是处于阻塞状态
饥饿可能只有一个进程发生饥饿。发生饥饿的进程既可能是处于阻塞状态(如长期得到不到需要的IO设备),也可能处于就绪状态(长期得不到处理机)
死循环可能只有一个进程发生死循环。死循环的进程可以上处理机执行,但无法像预期那样顺利推进,死锁和饥饿问题是由于操作系统资源分配策略不合理导致的,但死循环是由于代码逻辑导致的

产生死锁的四个必要条件:

Col1Col2
互斥条件只有对必须互斥使用的资源的争抢才会发生死锁
不剥夺条件进程同所获得的资源在未使用完成时,不能由其他进程强行夺走,只能主动释放
请求和保持条件进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已持有的资源保持不放
循环等待存在一种进程资源的循环等待链,链中的每个进程已获得的资源同时被另外一个进程所请求

想要解决死锁问题,就要从产生死锁的四个必要条件入手,打破这四个条件中的一个就能够轻松的解决死锁问题,死锁的解决策略主要有一下三种:

  • 预防死锁
  • 避免死锁
  • 死锁的检查和解除

防止死锁

预防死锁的思路主要是从产生死锁的四个必要条件出发,然后从设计上避免出现这四种问题。

破坏互斥条件

如果把只能互斥访问的资源改造成允许共享使用,则系统就不会进入到死锁状态。比如SPOOLing技术,操作系统可以采用SPOOLing技术把独占设备在逻辑上改造成共享设备。但这种策略的缺点在于并不是所有的互斥使用的资源都能改造成共享使用的资源,并且为了系统安全,很多地方必须互斥访问,因此,很多时候都无法破坏互斥条件。

破坏不剥夺条件

方案一:当某个进程的新资源得不到满足时,他必须立即释放保持的所有资源,等以后需要时再重新申请。也就是说某些资源尚未使用完成也要主动释放,从而破坏不剥夺条件。
方案二:当某个进程需要的资源被其他进程占有时,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级。

这种策略有很多缺点:

  • 实现起来比较复杂
  • 释放已经获得的资源可能造成前一段的工作失效,因此这种方法一般只适合于易保存和恢复状态的资源
  • 反复的申请和释放资源会增加系统开销,降低系统吞吐量
  • 如果采用方案一,可能会导致某些进程出现饥饿

破坏请求条件和保持条件

采用顺序分配法,给系统中的资源进行编号,规定每个进程必须按照编号递增的顺序请求资源,同类资源一次申请完。
缺点:

  • 不方便增加新的设备,因为可能需要重新分配资源编号
  • 进程实际使用资源的顺序和编号递增顺序可能不一致,会导致资源浪费
  • 必须按规定的顺序申请资源,用户编程麻烦

避免死锁

所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成,只要能找出一个安全序列,系统就是安全状态,当然,安全序列可能有多个。如果分配资源后,系统找不出任何一个安全序列,系统就进入了不安全状态,这意味着之后可能所有进程都无法顺利执行下去,当然如果有进程提前归还了一些资源,那系统有可能重新回到安全状态,不过我们再分配资源之前总是要考虑最坏的状态。

如果系统处于安全状态,就一定不会发生死锁。如果系统处于不安全状态,就有可能发生死锁,因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求,这也就是“银行家算法”的核心思想。

银行家算法是荷兰学者Dijkstra为银行系统设计的,以确保银行再发放现金贷款时,不会发生不能满足客户的情况,后来该算法被用在操作系统中,用于避免死锁。

银行家算法的核心思想:再进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态,如果会进入不安全状态就暂时不答应这次请求,让该进程先阻塞等待。

内存

概述

内存,也就是常说的RAM,内存的主要作用是用来存储程序运行时的一些数据,解决磁盘读写速度和cpu运算速度之间的矛盾。前面说过的一个进程包含程序段、数据段和PCB,这些都是存储在内存当中的。为了方便对内存进行管理,整个内存按照存储单元进行划分,一个存储单元的大小为一个字节(即8个二进制位),每个存储单元都有一个编号,即内存地址。

2^10 字节 = 1kb
2^20 字节 = 1mb
2^30 字节 = 1gb

一个程序最终会转换成一个进程,进程包含程序段、数据段和PCB,其中,程序段中存储了这个程序运行对应的指令集合,进程获取到cpu执行权后,就会依次执行程序段中的指令,cpu从数据段中根据指定的内存地址把数据读取到cpu高速缓存中进行计算,计算完成后在写回到主内存中。而程序段中的指令是通过编译、链接、装载三个步骤从编码变成指令并最终被装配到内存中的。

应用程序从源代码到运行的进程需要经过编译、链接、装入三个过程,其中编译会将源码编译成机器码,链接是将多个机器码进行合并,组成一个完整的装入模块,在链接过程中被组织到一起的指令中涉及的内存地址实际上是逻辑内存地址,因为此时指令集合还没有被装在到内存中,最后经过装入过程,将准备好的指令集合加载到内存中,这个过程会将逻辑地址直接或间接的转换成物理地址。

链接的三种方式:

Col1Col2
静态链接在程序运行之前,先将各个目标模块及他们所需的函数库链接成一个完整的可执行文件(装入模块),之后不再拆开
装入时动态链接将各目标模块装入内存时,以边装入边链接的方式执行
运行时动态链接在程序执行中需要该目标模块时,才对他进行链接,优点是便于修改和更新,便于实现对目标模块的共享

装入的三种方式:

Col1Col2Col3
绝对装入在编译时,如果知道程序放到内中的哪个位置,编译程序将产生绝对地址的目标代码,装入程序按照装入模块中的地址,将程序和数据装入到内存中绝对装入只适用于单道程序
静态装入又称可重定位装入。编译、链接后的装入模块的地址都是从0开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存中的适当位置,装入时对地址进行重定位,将逻辑地址变成物理地址(地址变换是在装入时一次完成的)静态重定位的特点是一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入到内存后,在运行期间不能再移动,也不能再申请内存空间
动态装入又称动态运行时装入。编译、链接后的装入模块地址都是从0开始的,装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到真正执行时才进行。因此装入内存后,所有的地址依然是逻辑地址,这种方式需要一个重定位寄存器的支持。采用动态重定位时,允许程序在内存中移动位置,并且可以把程序分配到不连续的存储区中;在程序运行前,只需要装入部分代码既可投入运行,然后在程序运行期间,根据需要动态分配内存

内存管理

Col1Col2
内存的分配与回收操作系统要记录哪些内存区域已经被分配出去了,哪些又还有空闲;很多位置都可以放,那应该放在哪里;当进程运行结束之后,如何将进程占用的内存空间回收;
内存空间的扩展
地址转换为了使编程更方便,程序员写程序时应该只关注指令、数据的逻辑地址,而逻辑地址到物理地址的转换(地址重定位)应该由操作系统负责,这样就保证了程序员写程序时不需要关注物理内存地址的实际情况
存储保护操作系统要保证进程进程在各自的存储空间内运行,互不干扰

覆盖与交换

交换和覆盖是内存空间扩充的技术。

早期的计算机内存很小,比如IBM推出的第一台PC机最大只支持1MB大小的内存,因此经常会出现内存大小不够的情况,后来人们引入了覆盖技术,用来解决程序大小超过物理内存总和的问题。覆盖技术就是将程序分为多个段(多个模块),常用的段常驻内存,不常用的段在需要时调入内存,然后内存分为一个固定区和若干个覆盖区,需要常驻内存的段放入“固定区”中,调入后不再调出,不常用的段放在“覆盖区”,需要用到的时候调入内存,用不到的时候调出内存。但如果想使用覆盖技术,就必须由程序员声明覆盖结构,操作系统才能完成自动覆盖。这种技术的缺点是对用户不透明,增加了用户的编程负担。覆盖技术只用于早期的操作系统中,现在已成为历史。

交换技术的思路是当内存空间紧张时,系统将内存空间中的某些进程暂时 换出 到外存,把外存中某些已具备运行条件的进程换入到内存,进程在内存与外存之间动态调度,而这个外存一般来说就是基于磁盘实现的虚拟内存,在虚拟内存中的进程的状态就是挂起状态,从虚拟内存换入到主内存的过程实际上就是一次中级调度。

交换技术的思路是当内存空间紧张时,系统将内存空间中的某些进程暂时 换出 到外存,把外存中某些已具备运行条件的进程换入到内存,进程在内存与外存之间动态调度,而这个外存一般来说就是基于磁盘实现的虚拟内存,在虚拟内存中的进程的状态就是挂起状态,从虚拟内存换入到主内存的过程实际上就是一次中级调度。
理解交换技术需要明确以下三个问题:

  • 应该在外存(磁盘)的什么位置保存被换出的进程?
  • 什么时候应该交换?
  • 应该换出哪些进程?

在具有交换功能的三个系统中,通常包磁盘空间分为 文件区 和 交换区 两个部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散的分配方式;交换区空间只占用磁盘空间的一小部分,被换出的进程数据就存放在交换区中。由于交换的速度直接影响操作系统的整体运行速度,因此,对交换区空间的管理主要追求换入和换出速度,因此通常交换区采用连续分配的方式,总之,交换区的IO速度要比文件区更快。

交换通常在有许多进程运行且内存吃紧的时候进行,而在系统负荷降低时暂停。比如,发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一部分进程,如果缺页率明显下降,就可以暂停换出。

可优先换出阻塞进程;可优先换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存中的驻留时间。

连续分配管理方案

单一连续分配

在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关的数据。内存中只能有一道用户程序,这个用户程序独占整个用户空间。

单一连续分配方式的优点在于实现简单、无外部碎片、可以采用覆盖技术扩充内存;但缺点在于只能用于单用户、单任务的操作系统,会存在内部碎片(分配给某进程的内存区域中,如果有些部分没有用上,就是内存碎片),而且存储器的利用率也比较低。

固定连续分配

20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。

进一步的,固定分区分配方式又分为分区大小相等的分区方式 和 分区大小不等的分区分配方式,分区大小相等的分配方式相对来说缺乏灵活性,但是很适合用于一台计算机控制多个相同对象的场合,比如炼钢厂有n个相同的炼丹炉,就可以把内存分为n个大小相等的区域存放n个炼钢炉控制程序;而分区大小不等的分区分配方式相对来说灵活性更好,可以满足不同大小的进程,具体的分配方式可以根据常在系统中运行的作业的大小情况进行划分,比如划分多个小分区,适量的中等分区,和少量的大分区。

操作系统需要建立一个数据结构,即分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按照分区大小排列,每个表项包括对应分区的大小、起始地址、状态(是否已分配)。

当某个用户程序装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为已分配。这种分配方式的优先是实现简单,无外部碎片;但缺点在于,当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决这个问题,但这又降低了系统的性能,另外就是会产生内部碎片,内存利用率比较低。

动态分区分配

动态分区分配又称为可变分区分配,这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态的建立分区,并使得分区的大小正好适合进程的需要,因此分区的大小和数目是可变的,比如,某计算机内存大小为64mb,系统区为8mb,用户区为56mb。

采用动态分区分配方式时,需要明确以下三个问题:

  • 系统要用什么样的数据结构记录内存是使用情况?
  • 当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
  • 如何进行分区的分配与回收操作?

针对第二个问题,当把一个作业装入内存时,必须按照一定的动态分区分配算法,从空闲分区表或空闲分区链中选出一个分区分配给该作业,由于分配算法对系统性能有很大的影响,因此人们对他进行了广泛的研究,并形成了一套动态分区分配算法。

针对于第三个问题中的分配问题,在分配时会涉及到两种情况,第一种情况是当空闲分区的大小大于进程所需要的大小时,这种只需要调整空闲分区表中该表项的大小及其地址既可,也就是扣除分配给进程的地址空间,第二种情况是如果空闲分区大小与进程所需空间大小一致,那么就直接将这块内存分配给该进程,然后将该空闲分区在空闲分区表中的表项删除,而如果没有足够的空间满足进程的需要,那该进程就不会被装入到内存中,针对于回收问题,第一种情况是回收区的后面有一个相邻的空闲区域,被回收区域要和后面相邻的空闲区域进行合并;第二种情况是回收区的前面有一个相邻的空闲分区,被回收区域要和前面相邻的空闲区域进行合并看;第三种情况是回收区的前后都有相邻的空闲区域,被回收区要和前后两个空闲分区进行合并,将原来的两个空闲分区和一个待回收区合并为一个大的空闲区域;第四种情况是回收区的前后都没有空闲分区,此时待回收区被回收后就会多出来一个空闲分区,也就是分区表中的一个表项。

内部碎片指的是分配给某个进程的内存区域中,没有被进程用上的部分,外部碎片指的是在内存中某些空闲区域由于太小而难以利用,动态分区分配方式不会产生内部碎片。

动态分区分配算法:

首次适应算法每次都从低地址开始查找,找到第一个能满足大小的空闲分区空闲分区以地址递增的次序排列,每次分配内存时,顺序查找空闲分区表,找到大小满足要求的第一个空闲分区
最佳适应算法由于动态分区分配是一种连续的分配方式,为各个进程分配的空间必须是连续的一整片区域。因此为了保证当大进程到来时有连续的大片空间,可以尽可能的留下大片的空闲区,即优先使用更小的空闲区空闲分区按照容量递增次序链接,每次分配内存时顺序查找空闲分区表,找到大小满足要求的第一个空闲分区每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块,即会产生很多外部碎片
最坏适应算法为了解决最佳适应算法的问题,即留下太多难以利用的小内存碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用空闲分区按照容量递减次序链接,每次分配内存时顺序查找空闲分区表,找到大小能满足要求的第一个空闲分区每次选最大的分区进行分配,虽然可让分配后留下来的空闲区更大,更可用,但这种分配方式会导致较大的连续空间被迅速用完,如果之后有大进程到达,就没有内存分区可用了
临近适应算法首次使用算法每次都从链头开始查找,这可能导致低地址部分出现很多小的空闲分区,而每次分配查找时,都经过这些分区因此会增加查找开销,如果每次都从上次查找结束的位置开始检索,就能解决上述问题空闲分区以地址递增的顺序进行排列(可以排成一个循环链表)。每次分配内存时,从上次查找结束的位置开始查找空闲分区链,找到大小能满足要求的第一个空闲分区首次适应算法每次都用从头查找,每次都需要检索低地址的小分区,但是这种规则也决定了当低地址部分有更小的分区可满足需求时,会更有可能利用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳算法的优点)。邻近适应算法的规则可能导致无论低地址、高地址部分的空闲分区都有相同的概率被使用到,也就导致了高地址部分的大分区可能被使用,被划分为了小分区,最后导致无大分区可用(最大使用算法的缺点)

分页存储管理方案

固定分区的分配方式缺乏灵活性,同时又会产生大量的内部碎片,内存的利用率很低,而动态分配方式又会产生很多外部碎片,也会降低内存的利用率,虽然可以通过紧凑技术来处理,但是紧凑的时间代价很高。分区分配方式造成内存利用率不高的主要原因在于它规定一个进程必须连续的获得一整块分区或一整块分区的一部分,所以,如果能将一个进程分散的装入到许多不相邻的分区中,便可以充分的利用内存,而无需再进行紧凑处理,基于这种想法,就产生了“非连续分配方式”,或者称为“离散分配方式”。

Col1Col2
连续分配方式为用户进程分配的必须是一个连续的内存空间
非连续分配方式为用户进程分配的可以是离散的内存空间

基本分页管理

假设进程A大小为23MB,但是每个分区大小只有10MB,如果进程只能占用一个分区,那显然放不下。解决思路是,如果运行进程占用多个内存分区,那么可以把进程拆分为10MB+10MB+3MB三个部分,再把这三个部分分别放到三个分区中,而这些分区不要求连续。此时进程A的最后一个部分是3MB,放入分区后会产生7MB大小的内部碎片。如果每个分区大小为2MB,那么进程A可以拆分为11*2MB+1MB供12个部分,只有最后一部分1MB占不满分区,产生1MB的内存碎片。

很显然,如果把分区大小设置的更小一些,内存碎片会更小,内存利用率会更高。基本分页存储管理的思想就是把内存分为一个个相等的小分区,再按分区大小把进程拆分成一个个的小分区。

将内存空间分为一个个大小相等的分区(比如每个分区4KB),每个分区就是一个页框,或称页帧、内存块、物理块。每个页框有一个编号(或者内存块号、页帧号、物理块号),页号从0开始。将用户进程的地址空间也分为与页框大小相等的一个个区域,称为页或页面。每个页面有一个编号,即页号,页号页是从0开始。操作系统以页框为单位为各个进程分配内存空间,进程的每个页面分别放入一个页框中,也就是说,进程的页面与内存的页框是一一对应的关系。各个页面不必连续存放,也不必按先后顺序来存放,可以用到不相邻的各个页框中。

进程地址空间分页后,操作系统如何实现逻辑地址到物理地址的转换?

前面说过逻辑地址到物理地址的转换方式有三种,即绝对装入、静态重定位和动态重定位。以动态重定位为例,在装入阶段不会进行地址转换,也就是装入内存中的指令中的地址为逻辑地址,在重定位寄存器中存放着装入模块时存放的起始位置,然后在cpu执行指令的时候,根据逻辑地址+重定位寄存器中的起始地址就能计算出真实的物理地址,也就是模块在内存中的起始地址+目标内存单元相对于起始位置的偏移量。

在通过分页管理内存后,进程按照操作系统页的大小被分为多个页,而这个页属于逻辑页,实际装入后,会被离散的分配到不同的内存也空间中,当cpu执行指令时,需要动态的将当前指令中的逻辑地址转换成物理地址,那么在转换的过程中需要做以下四个事情:

  • 要计算出逻辑地址对应的页号(页号 = 逻辑地址 / 页面长度);
  • 要知道该页号对应的页面在内存中的起始地址(重定位寄存器);
  • 要计算出逻辑地址在页面内的偏移量(页内偏移量 = 逻辑地址 % 页面长度);
  • 物理地址 = 页面起始地址 + 页内偏移量;

分页存储管理的逻辑地址结构如下所示(32位操作系统):

Col1Col2
31 ...1211 ... 0
页号p页内偏移量w

地址结构包含两个部分,前一部分为页号,后一部分为页内偏移量,以32操作系统为例,地址长度为32个比特,其中0-11位为业内偏移量,或页内地址,12-31位位页号。再次来回答在分页存储管理中,如何实现地址转换:

  • 要算出逻辑地址对应的页号(31-12位);
  • 要知道该页号对应页面在内存中的起始地址(由内部数据结构记录,重定位寄存器);
  • 要算出逻辑地址在页内的偏移量(11-0位);
  • 物理地址 = 页面起始地址 + 页内偏移量;

如何记录进程的逻辑页号对应的物理起始地址?

为了能知道进程的页面在内存中存放的位置,操作系统要为每个进程建立一张页表:

  • 一个进程对应一张页表
  • 进程的每一页对应一个页表项
  • 每个页表项由页号和块号组成,其中页号是进程的逻辑页号,块号是内存物理地址块的编号
  • 页表记录进程页面和实际存放的内存块之间的对应关系

为什么每个页表项的长度是相同的,页号是隐藏的?

假设某系统的物理内存时4gb,页大小为4kb,则每个页表项至少应该为多少个字节?

4gb = 232 b;4kb=212 b

因此,4gb内存总共被分为2^20个内存块,因此内存块号的范围应该是2^20-1,因此至少要20个二进制位才能表示这么多内存块号,因此至少需要3个字节才够用(24个比特位)。各页表项会按顺序连续的存放在内存中,如果该页表在内存中存放的起始位置是x,则m号页对应的页表项一定是存放在内存地址为x+3*m,因此页表项中的页号可以是隐藏的,只需要知道页表存放的起始位置和页表项的长度,既可找到各个页号对应的页表项存放的位置。

地址变换机构

地址变换机构可以借助进程的页表将逻辑地址转换成物理地址。通常会在系统中设置一个页表寄存器,存放页表在内存中的起始地址F和页表长度M,进程未被cpu执行时,页表的地址和页表的长度是存储在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放在页表寄存器中。

设页面大小为L,逻辑地址A到物理地址E的变换过程如下:

  • 计算页号P和页内偏移量W(如果用十进制数手算,则P=A/L,W=A%L;但在计算机实际运行时,逻辑地址的结构是固定的,因此计算机硬件可以更快的得到二进制表示的页号和页内偏移量);
  • 比较页号P和页表长度M(在页表寄存器中),如果P>=M,则产生页表越界中断,否则继续执行(注意:页号是从0开始的,而页表长度至少为1,因此P=M时页是越界);
  • 页表中页号P对应的页表项地址 = 页表起始地址F + 页号P * 页表项长度,取出该页表项的内容b,即为内存块号b;
  • 计算E = b * L + W,用得到的物理地址E去访问内存。(如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址);

具有快表的地址变换机构

int i = 0;
int a[100];
while(i < 100) {
    a[i] = i;
    i ++;
} 

上面的程序在执行时,由于存在一个while循环,所以会频繁的访问某些内存块。

  • 时间局部性:如果执行了程序中的某条指令,那么不久后这条执行很有可能再次访问,如果某个数据被访问过,之后不久该数据很可能再次被访问,因为程序中存在着大量的循环。
  • 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也有可能被访问,因为很多数据在内存中都是连续存放的。

在基本地址变换机构中,每次要访问一个逻辑地址,都要查询内存中的页表,由于局部性原理,可能连续很多次查到的都是同一个页表项,既然如此就可以利用这个特性减少访问表的次数。快表是一个高速缓存,引入快表后,地址的变换过程:

  • cpu给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较;
  • 如果找到匹配的页号,说明要找的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将块号偏移量与页内偏移量拼接成物理地址,最后访问该物理地址对应的内存单元,因此,若快表命中,则访问某个逻辑地址仅需要一次访存既可
  • 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应的页表项,得到页面对应的内存块号,再将内存块号语页面偏移量拼接成物理地址,最后访问地址对应的内存单元,因此若快表未命中,则访问某个逻辑地址需要两次访存(在找到页表项后,应同时将其存入快表,以便后面可能的再次访问,但若块表已满,则必须按照一定的算法对旧的页表项进行替换);

由于查询快表的速度要比查询页表的速度快很多,因此只要快表命中,就可以节约很多时间。

Col1变换过程访存次数
基本地址变换机构1.算页号、页内偏移量;2.检查页号合法性;3.查页表,找到页面存放的内存块号;4.根据内存块号与页内偏移量得到物理地址;5.访问目标内存单元两次访存
具有快表的地址变换机构1.算页号、页内偏移量;2.检查页号合法性;3.查快表。若命中,即可知道页面存放的内存块号,可直接进行5,若没命中 则进行4;4.查页表,找到页面存放的内存块号,并将页表项复制到快表中;5.根据内存块号与页内偏移量得到物理地址;6.访问目标内存单元;若块表命中,一次访存;若快表未命中,两次访存;

二级页表

单级页表存在以下两个问题:
问题一:页表必须连续存放,因此当页表很大时,需要占用真多连续的页框;
问题二:没必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面;

举例来说,如果某计算机系统按字节寻址,支持32位的逻辑寻址,采用分页存储管理,页面大小为4kb,页表项长度为4b,4kb = 2^12b,因此页内地址要用12位表示,剩余20位表示页号,因此该系统中用户进程最多有2^20个页,相应的,一个进程的页表中,最多会有2^20个页表项,所以一个页表最大需要2^20 * 4b = 222b的空间,一个页大小为4kb,即2^12,所以就需要为进程分配2^10=1024个连续页。

根据页号查询页表的方法:K页号对应的页表项存放位置 = 页表起始地址 + k * 4,要在所有页表项都连续存放的基础上才能用这种方法找到页表项,根据局部性原理可知,很多时候进程在一段时间内只需要访问几个页面就可以正常运行了,因此没有必要让整个页表都常驻内存。

思考:我们是如何解决进程在内存中必须连续存储的问题的?

将进程地址空间分页,并建立一张页表,记录各页面的存储位置,同样的思路也可以用于解决“页表必须连续存放的问题”,把必须连续存放的页表再分页。可将长长的页表进行分组,使每个内存块刚好可以放入一个分组,比如页面大小为4kb,每个页表项为4b,每个页可以存储1k个页表项,因此每1k个连续的页表项为一组,每组刚好占一个内存块,为了离散分配页表,再建立一张页表,称为页表目录,或称外层页表、顶层页表。这样二级页号就从2^20变成了2^10,一级页表的页号也是2^10。

Col1Col2Col3
31 ... 2221 ... 1211 .. 0
一级页号二级页号页内偏移量
  • 按照地址结构,将逻辑地址拆分为三个部分,即一级页号、二级页号和页内偏移量
  • 从PCB中读出页目录表的起始地址,再根据一级页号查页目录表,找到下一级页目录表在内存中存放的位置
  • 根据二级页号查表,找到最终想访问的内存块号
  • 结合页内偏移量得到物理地址

对于第二个问题,可以在需要访问页面时才把页面调入内存(虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页面是否已经调入内存。

Col1Col2Col3
一级页号内存块号是否在内存中
03
Col1Col2Col3
二级页号内存块号是否在内存中
02

注:若采用多级页表机制,则各级页表的大小不能超过一个页面。

基本分段管理

  • 进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址;
  • 内存分配规则:以段为单位进行分配,每个段在内存中占用连续的空间,但各段之间可以不相邻;
    分段系统的逻辑地址结构由段号和段内地址组成,比如:
  • Col1Col2
    31...1615...0
    段号段内地址

段号的位数段决定了每个进程最多可以分几个,段内地址位数决定了每个段的最大长度。在上面的例子中,若系统是按照字节进行寻址的,则段号占16位,因此在系统中每个进程最多有216=64k个段位,段内地址占16位,因此每个段的最大长度是216=64k。

问题:程序分多个段,各段离散的装入内存,为了保证程序的正常运行,就必须从物理内存中找到各个逻辑段的存放位置,为此,需要为每个进程建立一张段映射表,简称“段表”。

  • 每个段对应一个段表项,其中记录了个该段在内存中的起始位置和段长度;
  • 各个段表项的长度是相同的;

每个进程对应的段表的起始位置F和段表的长度M 在进程没有被运行时 是存放在PCB中的,当运行被CPU执行时,会将其加载到段表寄存器中。而段表项中包含段号、段长和段起始地址。

采用分段存储时逻辑地址和物理地址的转换:

  • 根据逻辑地址得到段号S和段内地址W;
  • 判断段号是否越界,若S>=M,则产生越界中断,否则继续执行;
  • 查询段表,找到对应的段表项,段表项的存放地址为F+S*段表项的长度;
  • 检查段内地址是否超过段的长度,若W>=该段的长度,则产生越界中断,否则继续执行;
  • 计算物理地址,物理地址 = 段起始地址 + 段内地址;
  • 访问目标存储单元;

分段与分页管理的对比:

  • 页是信息的物理单位,分页的主要目的是为了实现离散分配,提高内存利用率。分页主要是系统管理上的需要,完全是系统行为,对用户不可见;
  • 段是信息的逻辑单位,分段的主要目的是更好的满足用户需求,一个段通常包含一组或属于一个逻辑模块的信息,分段对用户是可见的,用户编程时需要显示给出段名
  • 页的大小固定,且由系统决定,段的长度不固定,决定与用户编写的程序
  • 分页的用户进程地址空间是一维的,程序员只需要给出一个记忆符号既可表示一个地址;
  • 分段的用户进程地址空间是二维的,程序员在标识出一个地址时,既要给出段名,页要给出段内地址;
  • 分段比分页更容易实现信息的共享与保护;

访问一个逻辑地址需要访存的次数:

Col1Col2Col3
分页管理第一次访存查内存中的页表,第二次访存查目标内存单元两次
分段管理第一次访存查内存中的段表,第二次访存查目标内存单元两次

注:与分页系统类似,分段系统也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少访问一次内存。

段页式管理

Col1优点缺点
分页管理内存空间利用率高,不会产生外部内存碎片,只会产生少量的页内碎片不方便按照逻辑模块实现信息的共享与保护
分段管理很方便按照逻辑模块实现信息的共享与保护如果段长过大,为其分配很大的连续空间会很不方便,另外,段式管理会产生外部碎片

段页式管理 = 分段 + 分页,将进程按照逻辑模块进行分段,再将各段进行分页,再将内存空间分为大小相同的内存块,进程运行前将各页面分别装入各个内存块中。段页式管理的逻辑地址结构:

Col1Col2Col3
31...1615...1211...0
段号页号页内偏移量

段号的位数决定了每个进程最多可以分几个段,页号的位数决定了每个段最多有多少个页,页内偏移量决定了页大小是多少。在上面的例子中,若系统是按照字节寻址的,则段号占16位,因此在该系统中每个进程最多有2^16=64kb个段位,页号占四位,因此每个段最多有24=16页,页内偏移量占12位,因此每个页面的大小时2^12=4kb。

每个段对应一个段表项,每个段表项由段号、页表长度和页表存放的块号(即页表的起始地址)组成,每个段表项长度相等,段号是隐藏的。每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐藏的。

页段式管理的地址转换过程:

  • 根据逻辑地址得到段号、页号、页内偏移量;
  • 判断段号是否越界,若越界则产生越界中断,否则继续执行;
  • 查询段表,找到对应的段表项,段表项的存放地址为 段表起始地址 + 段号 * 段表长度(段表项中存放了页表的长度 和 存放页表的内存块号);
  • 检查页号是否越界,如果页号>=页表长度,则发生越界中断,否则继续执行;
  • 根据页表存放的块号和页号找到页表,并找到对应的页表项(页表项中记录了内存块号);
  • 根据内存块号、页内偏移量得到最终的物理地址;
  • 访问目标内存单元;

文件

文件的定义

文件是一组有意义的信息的集合。

文件的属性

属性描述
文件名名称
类型操作系统可以根据类型使用不同的应用程序打开
大小占用空间的大小
存放路径操作系统读取数据时会用到
创建时间
修改时间
所有者
访问控制信息

操作系统需要提供的功能

操作描述
open读取文件数据前需要对文件执行open
close对文件操作完成后需要执行close,释放掉对文件的I/O
read读取文件数据
write向文件中写入数据
delete删除文件

文件的内部结构

文件的内部结构主要分为流式结构记录型结构,其中流式结构又分为字节流结构和字符流结构,对应的文件有文本文件、音频和视频等,记录型结构指的是文件内部的数据是按照一定的规则存储的,比如word文件、excel文件等等。

不管是流式结构还是记录型结构,他们最终都是以二进制010101的形式存储在磁盘上的,所以前面在结构上的区分实际是对逻辑结构的划分。

与内存类似,磁盘是以块的形式存储数据,即磁盘块,也就是说,文件最终会被拆分成多个比特块存储在磁盘块上,一般情况下,磁盘块的大小和内存页的大小保持一致,这样可以提高内存页的使用率。另外,文件在存储时被分块的同时,它也分为逻辑块和物理块,逻辑块用来实现文件的管理,对文件进行操作时,需要将逻辑块转成物理块。

文件的逻辑结构

所谓的逻辑结构,就是以用户视角看到的文件的结构,即前面说的流式结构和记录型结构,与逻辑结构对应的是物理结构,也就是前面说的按比特和磁盘块来存储文件数据。文件逻辑结构的具体划分如下:

  • 无结构文件
  • 有结构文件
    • 顺序结构文件
    • 索引结构文件
    • 索引顺序文件

无结构文件

无结构文件就是前面说的流式结构文件,从用户视角看,文件内部的数据没有明显的逻辑关系,都是以字节的形式存在的,比如文本文件、音频文件和视频文件等。

有结构文件

有结构文件就是整个文件内部的数据是按照一定的规则组织在一起的,一般有结构文件由一组记录组成,所以又称为记录型文件,每一条记录就是一个记录项,每条记录项会包含一个关键字用来定位当前条记录项,此外,按照每条记录项的长度是否固定又可以把记录分为定长记录和非定长记录。有结构文件在逻辑上一般是按照一定的规则去组织各个记录项

顺序结构

由于有结构文件是由一条条记录组成的,所以就可以按照先后顺序以此组织这些记录,这些记录即可以是定长的也可以使变长的。

但对变长记录进行顺序存储时,无法对文件内容进行随机访问,当要访问文件的中间部分内容时,就只能从头开始一次向下寻找,这样严重影响操作的效率。

另外,按照记录是否有序,顺序结构又可以分为以下两种:

  • 串结构:没有顺序,记录依次向后排列
  • 顺序结构:按照某个关键字进行排序,然后按排序后的顺序组织记录

索引结构

在顺序结构中,如果记录项的大小是可变的,就会造成无法进行随机访问的问题,也就降低了数据读写等操作的效率,为了解决这个问题,引入了索引结构,在索引结构中会引入一个索引表,在索引表中记录第i条记录对应的物理地址,这样就避免了从头开始检索文件数据的问题,提高了检索效率。

在索结构的基础上,还可以进一步优化索引表,让索引表按照某个关键字进行排序,比如文件名称,这样就可以通过比如折半查找之类的算法来进一步加快对索引表的检索速度。

但由于引入了索引表,会额外的占用一些存储空间,尤其是对于一些小文件,即使文件本身比索引表项还小,但也仍然需要一个完整的索引表项。

索引顺序结构

索引顺序结构是通过同时引入索引表和顺序结构来解决上面两种结构存在的问题,在索引顺序结构中,首先文件记录项时按照某个关键字顺序存储的,再次基础上再引入一个索引表,但这个索引表并不是为每一个记录项建立一个索引项,而是为一组记录项建立一个索引项,比如以为以A开头的文件建立一个索引项,然后由于这组以A开头的记录项是按照关键字排序的,在查找的时候就可以利用折半查找算法加快查找速度,就类似于kafka中的分段索引文件。

文件目录

目录也是一种特殊类型的结构性文件,每一个记录项都代表着目录下的一个文件,也就是文件控制块(FCB),在这个FCB中包含了文件的基本信息(名称、逻辑地址、物理地址等)、控制信息(是否可读、是否可写)、使用信息(创建时间、修改时间),其中最重要最基本的是文件的名称和文件存放的物理地址。

单级目录

早期的操作系统并不支持多级目录,整个系统只有一个根目录,也就是只有一个根目录文件(根目录文件中记录了目录下的文件的FCB),所以不允许有重名文件。

两级目录结构

早期的多用户操作系统采用两级目录结构,分为主文件目录和用户文件目录,主目录文件下包含了不同用户及指向该用户对应的根目录文件(包含了当前用户下的所有文件对应的FCB),两级目录虽然解决了多个用户之间文件不能重名的问题,但同一个用户下的文件名称仍然不能重复且不能对文件进行分类。

多级目录结构

多级目录结构又称为树型目录结构,是以“文件路径作+文件名称”作为文件的标识符,从根目录开始的目录叫做绝对目录,从当前目录开始的目录叫做相对目录(相对目录的目标是提高检索效率,避免每次都从根目录开始去加载每一级的目录项列表)。

多级目录结构虽然解决了文件分类的问题,当仍然没有解决文件共享的问题。

有向无环图结构

为了解决多级目录无法实现文件共享的问题,在多级目录的树结构的基础上,增加了一些指向头一个文件/目录节点的指针,使得整个目录结构由树形结构变成了有向无环图结构,即可以让不同的文件(比如windows中的快捷键和linux中的软连接)指向同一个目录或文件,这样就很方便的实现了文件共享。

但在实现文件共享的时候,有时候还需要给这个共享节点对应的文件/目录增加一个引用计数,比如当删除这个文件的时候,可以判断这个计数是否为0,如果不为0就说明这个文件还在被其他的文件所引用(但一般的操作系统在删除文件的时候没有这个限制,只是在通过快捷方式或软连接访问被删除文件的时候会提示文件/目录不存在)。

索引节点

由于目录是由一个FCB列表组成的,在FCB中存储了很多关于文件的信息,当目录下的文件数量比较多时,势必会造成目录表过大,又由于磁盘是以磁盘块的方式存储数据,较大的目录表就会被分多磁盘块存储,这样在检索目录列表的时候,就会造成磁盘IO数量增加,所以引入一个目录的索引表,这个索引表中只包含用于检索文件必须的一些信息,比如文件名称,文件路径和对应的物理地址,这样就可以尽量减少索引表占用的磁盘块的数量,进而减少磁盘I/O次数。

文件的物理结构

不管文件的逻辑结构是字节流还是有结构的,最终都是以0101的形式存储在磁盘上,而磁盘在存储数据的时候是以磁盘块为基本单位,为了让计算机内存的利用率最大化,磁盘块的大小通常和内存页大小一致。不同的物理存储结构会影响对文件的访问效率,文件的物理结构主要有以下几种:

  • 连续分配
  • 链式分配
  • 索引分配

连续分配

连续分配方式要求要为文件分配一组连续的磁盘块。这种分配方式在进行地址转换时,首先找到这个文件对应的FCB,确定文件的起始块物理地址,然后在根据起始块号和逻辑块号计算出真实的物理块号。这种分配方式的好处在于:

  • ①能够对磁盘块(即文件内容)进行快速的随机访问;
  • ②磁盘的磁头能快速找到对应的磁盘块,但缺点是需要连续的磁盘块空间,如果文件较大的话可能找不到合适的连续空间,另外就是在扩展磁盘块(调整文件大小)时也会造成找不到合适的空间的问题,以及会产生磁盘碎片。

链式分配

链式分配方式允许将文件数据离散的分配到磁盘块上,每个磁盘块之间通过显式或隐式的方式相连接。

隐式链接

在FCB中存储了文件的起始磁盘块号和结束磁盘块号,磁盘块与磁盘块之间(除了结束磁盘块)通过一个指针相连接,这个隐式链对用户是透明的。地址转换的过程是首先加载文件对应的FCB,然后从起始块开始,根据逻辑块号 通过 起始块号+逻辑块号的方式找到第i个逻辑块号对应的物理块号。这种存储方式的优点在于:①不会产生磁盘碎片,能够最大化磁盘空间利用率;②方便文件在空间上进行扩展;但缺点是由于是通过链表来组织的所以不支持随机访问;

显式链接

显示链接方式通过一个FAT表记录所有磁盘块及其下一个磁盘块的关系,每个磁盘对应一个FAT表,当计算机启动的时候这个FAT表会被加载到内存中。地址的转换过程是首先找到文件对应的FCB确定其对应的起始物理块号,然后再以这个物理块号为起点,查找FAT表,根据FAT表中记录的指针顺序的找到要访问的逻辑块号。由于FAT表时常驻内存的,所以通过FAT访问物理块时不需要进行多次的磁盘I/O,也不会产生磁盘随便,文件扩展也很方便。

索引分配

索引分配同样允许将文件内容离散的存储在磁盘块上,但和FAT不同的是这种分配方式会为每一个文件建立一个索引表,在索引表中记录了逻辑块号到物理块号的关系。地址转换的过程是首先找到文件对应的FCB,定位到索引表对应的物理地址,然后再通过查询索引表找到对应的物理块号。

当文件较大时,索引表占用的空间可能会不止一个磁盘页,这时就需要通过某种途径将多个索引表对应的磁盘块组织到一起,主要有三种方案:链接方式、多层索引和混合索引。

链接方式

链接方式就是,当文件的索引表大小超过了一个磁盘块时,就通过指针的方式将多个磁盘块进行串连,但在这种方式下,查找某个逻辑地址对应的物理块号时,就需要通过这个指针顺序的查找所有索引文件对应 磁盘块,效率比较低下。

多层索引

多层索引的思路是当某个文件对应的索引表需要占用多个磁盘块时,就为每个磁盘块再建立一层索引。比如一个磁盘块能够存放16个索引表项,某个大文件的索引表需要占用16个磁盘块,如果使用链接的方式,那就是把这16个磁盘块进行串连,但如果采用多层索引的方式,就是为这16个磁盘块再建立一层索引表,在这个索引表中的表项记录了这16个磁盘块对应的物理起始地址,也就是对索引表中的内容再进行一次分段索引。

但这种方式有一个弊端是,当某个小文件对应的索引表只需要占用一个磁盘块时,系统也会为他建立两级索引,也就是会占用两个磁盘块。这会在一定程度上影响磁盘的空间使用效率。

混合索引

混合索引方式是将多种分配方式结合起来使用,最大化磁盘的使用效率,即包含直接地址索引,也包含一级间接索引和二级间接索引,在进行逻辑地址到物理地址的转换时,根据不同的索引类型进行查找。比如直接地址索引就是直接指向了数据对应的物理地址,一级间接索引就是指向了单层索引表,二级间接索引就是指向了二级索引表。

存储空间的管理

存储空间的划分与初始化


在存储空间的划分上,通常会为磁盘进行分区,比如windows操作系统中的C盘、D盘等,每个分区分为目录区和文件区两个部分,目录区主要用来存储FCB和一些其他用于文件管理的数据,文件区存储真实的文件数据。

管理方式

管理方式主要针对的是磁盘空闲块的管理。

空闲表法

空闲表法是用一张表格来记录闲置的磁盘块,表中包含两项:第一个空闲的磁盘块号,和空闲块的数量(即相邻的空闲磁盘块的数量)。

与内存管理一样,为某个文件分配磁盘块时,可以采用首次适应、最佳适应和最坏适应算法来决定把文件分配到哪里。而在磁盘块回收方面,也是可以参考内存回收算法,要考虑以下四种情况:①回收区前后没有相邻的空闲磁盘块;②回收区前面有空闲的磁盘块;③会收取后面有空闲的磁盘块;④回收区前后都有空闲的磁盘块;总之,在回收时要注意空闲表项的合并问题。

空闲链表法

空闲链表法分为两种,一种是空闲盘块链,一种是空闲盘区链,空闲盘块链式将磁盘块用指针的方式串联起来,空闲盘区链是把由多个空闲磁盘块组成的空闲盘区用指针的方式串联起来。

在空闲盘块链的方式中,操作系统持有整个空闲链的头节点和尾节点,各个空闲磁盘块通过指针的方式连接起来。分配的时候从链头开始摘除空闲的磁盘块分配给文件,回收的时候将被回收的磁盘块加入到链尾。

在空闲盘区链的方式中,每一组连续的空闲磁盘块会组成一个空闲区,然后通过指针的方式把这些空闲分区连接起来,操作系统同样会持有这个空闲分区链的链头和链尾。与空闲盘块链不同的是 空闲盘区链的指针是存在空闲盘区的第一个位置。由于空闲盘区链中每个节点是由多个空闲的磁盘块组成的,所以在进行空间分配的时候会从链头开始向后寻找,然后利用首次适应、最佳适应以及最坏适应等算法去选择一个合适的空闲区分配给文件。对磁盘空间进行回收时,首先要看被回收区临近有没有空闲区,如果有,直接进行合并就行,如果没有再讲这个回收区作为空闲盘区链上的一个空闲区挂在待链表的尾部。

位图法

位图法实际上是通过一个位示图来标记磁盘上每一个磁盘块的空闲情况,比如空暇磁盘块在位示图上的位置被标记成0,否则标记成1。在分配磁盘空间的时候通过扫描位示图来确定哪些磁盘块是空闲的,然后将这些空闲块分配给文件使用。在回收时,同样是首先确定待回收的磁盘块在位示图中的位置,然后将其标志位设置为0。

其他

文件共享

  • 基于索引节点的共享方式(硬连接)
    索引节点时目录项(FCB)的一种瘦身结构,只存储了文件名和文件对应的物理地址,基于索引节点的硬连接共享指的是每个用户可以创建一个指向共享文件的目录项,这个目录项对应的物理地址是这个文件的索引节点。
  • 基于符号连接的共享方式(软连接)
    相比于基于索引节点的硬链接模式,基于符号连接的软连接实际上就是创建一个实现实际文件的“快捷键”,然后将硬链接中索引节点的地址修改成指向这个“快捷键”,区别在于,软连接的方式下,删除源文件并不会造成索引节点的删除,因为索引节点指向的是“快捷键”这个特殊的文件,而并不是真实的文件。

文件保护

  • 口令保护
  • 加密保护
  • 访问控制
保护方式说明
口令保护为文件设置一个口令,用户请求访问该文件时必须提供“口令”
加密保护使用某个密码对文件进行加密,在访问文件时需要提供正确的“密码”才能对文件进行正确的解密
访问控制在每个文件对应的FCB中增加一个访问控制列表,该表中记录了各个用户可以对该文件进行哪些操作

磁盘

磁盘的结构

磁盘、磁道、扇区

磁盘表面由一些磁性物质组成,可以用这些磁性物质来记录二进制数据,磁盘的盘片被划分成一个个的磁道,一个圈就是一个磁道,一个个磁道又被划分为一个个扇区,每个扇区就是一个磁盘块,各个扇区存放的数据量是相同的,比如1kb,由于内侧磁道上的扇区面积最小,所以数据密度是最大的。

读取数据

需要把磁头移动到想要读写的扇区所在的磁道,磁盘会转起来,让目标扇区从磁头下划过,就能完成对扇区的读、写操作。

盘面、柱面

一个磁盘可以有多个盘面,每个盘面对应一个磁头,所有的磁头都是连在同一个磁臂上的,因此所有磁头只能共进退,所有盘面中相对位置相同的磁道组成了柱面。

磁盘的物理地址

可以用(柱面号、盘面号、扇区号)来定位任何一个磁盘块,然后就可以根据这个地址读取一个磁盘块:

  • ①根据柱面号移动磁臂,让磁头指向指定的柱面;
  • ②激活指定盘面对应的磁头 ;
  • ③磁盘旋转的过程中指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读写;

磁盘的分类

  • 活动头磁盘(每个盘面对应一个磁头);
  • 固定头磁盘(每个磁道对应一个磁头);
  • 可换盘磁盘;
  • 固定盘磁盘;

磁盘调度算法

一次磁盘读写需要的时间

  • 寻道时间:即寻道时间,在读写数据前,将磁头移动到指定磁道所花费的时间;
  • 延迟时间:通过旋转磁盘,使磁头定位到目标扇区所需要的时间;
  • 传输时间:从磁盘读出,或向磁盘写入所花费的时间;
  • 延迟时间和传输时间都和磁盘的转速有关,且为线性关系,而转速是硬件的固有属性,操作系统无法优化延迟时间和传输时间;

调度算法

先来先服务:根据进程请求访问磁盘的顺序来进行调度。

  • 优点:公平。如果请求访问的磁道比较集中的话,算法性能还过得去;
  • 缺点:如果有大量的进程竞争的使用磁盘,请求访问的磁道很分散,则性能就会很差,寻道时间就会很长;

最短寻找时间优先:优先处理的磁道是与当前磁头最近的磁道。可以保证每次寻道的时间最短,但是并不能保证总的寻道时间最短。

  • 优点:性能较好、平均寻道时间短
  • 缺点:可能会产生饥饿现象

扫描算法(SCAN):只有磁头移动到最外侧磁道的时候,才能往内移动,移动到最内侧磁道的时候才能往外侧移动,也被称为电梯算法。

  • 优点:性能较好,平均寻道时间较短,不会产生饥饿
  • 缺点:①只有到达最边上的磁道时才能改变磁头移动的方向;②对于各个位置磁道的响应频率不平均
  • 优化:
    • Look调度算法:如果在磁头移动的方向上没有别的请求了,可以立即改变磁头移动的方向

循环扫描算(C-SCAN):返回时直接快速移动到起始端,而不处理任何请求

  • 优化:
    • C-Look算法:Look + C-SCAN

减少磁盘延迟时间的算法

假设要读取同一个磁道上的三个连续的扇区 2/3/4,磁头读取一块磁盘块之后,需要一小段时间进行处理,而盘片又在不停的旋转,因此,如果2/3号扇区相邻排列,则读完2号扇区后无法连续的读取3号扇区,必须等盘片继续旋转,3号扇区再次划过磁头时,才能完成对3号扇区数据的读入,所以,磁头读入一个扇区后,需要一小段时间进行处理,如果逻辑上相邻的扇区在物理上也相邻,则读入几个连续的逻辑扇区时,可能就需要更长的延迟时间。为了解决这个问题,引入了交替编号的办法,即让逻辑上相邻的扇区在物理上有一定的间隔,可以使得读取连续的逻辑扇区所需要的延迟时间更小。

磁盘地址的设计

为什么磁盘的物理地址时(柱面、盘面、扇区),而不是(盘面、柱面、扇区)?
假设某磁盘有8个磁道,4个盘面,8个扇区,可用3个二进制位标志柱面,2个二进制位表示盘面,3个二进制位表示扇区,若物理地址结构是(柱面、盘面、扇区),且连续读取物理地址(000,00,000)到(000,01,111),则在读取01号盘面的时候,磁头不需要物理移动,只需要激活就可以,若物理地址是(盘面、柱面、扇区),且连续读取物理地址(00,000,000)到(00,001,111),则需要磁头在0号磁道和1号磁道之间进行切换,这个是物理过程,非常耗时,所以,采用(柱面、盘面、扇区)的地址结构可以减少磁头移动消耗的时间。

错位命名

由于磁盘地址采用的是(柱面、盘面、扇区),相邻盘片之间通过激活磁头的方式来读取下一个盘片,但由于磁头读完数据后需要一定的时间处理,如果让相邻盘片的相对位置一样的话,就需要下一个盘片多转一圈,为了解决这个问题,让相邻两个盘片错开一个扇区的大小,这样就能够实现在哭啊盘片读取时的连续性。

磁盘管理

磁盘初始化

  • 低级格式化(物理格式化):将磁盘的各个磁道划分为扇区,一个扇区通常可以分为头、数据区、尾三个部分,管理扇区所需要的各种数据结构一般存放在头、尾两个部分,包括扇区校验码等
  • 将磁盘分区,每个分区由若干个柱面组成,比如C盘、D盘
  • 进行逻辑格式化,创建文件系统,包括文件系统的根目录,初始化存储空间管理所需的数据结构

引导块

  • 计算机开启的时候,需要执行一系列的初始化工作,这些初始化工作是通过执行初始化程序(自举程序)完成的,初始化程序可以放在RAM中,RAM中的数据出厂的时候就写入了,并且不能修改。
  • 初始化程序如果在RAM中,万一需要更新自举程序将很不方便,那该如何解决呢,在RAM中不存储自举程序,而是存储自举程序的装入程序,完整的自举程序放在磁盘,启动块上,启动块位于磁盘的固定位置,开机时,计算机先运行自举装入程序,通过执行该程序就可以找到引导块,并将完整的自举程序读入内存,完成初始化。

坏块的管理

  • 坏了,无法正常使用的扇区就是坏块,这属于硬件故障,操作系统是无法修复的,应该将坏块标记出来,以免错误的使用到他。
  • 对于简单的磁盘,可以在逻辑格式化的时候(建立文件系统时),对整个磁盘进行坏块检查,标明哪些扇区是坏扇区,比如在FAT表中标明。
  • 对于复杂的磁盘,磁盘控制器(磁盘设备内部的一个硬件部件)会维护一个坏块链表,在磁盘出厂前进行低级格式化时就将坏块链进行初始化。会保留一些备用扇区,用于替换坏块,这种方案被称为扇区备用,且这种处理方式中,坏块对操作系统透明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

echo20222022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值