操作系统面试知识大盘点

前言

这篇文章南国根据自己的理解 写一篇在互联网公司面试中操作系统常问的知识点。本篇博客许多内容都是在前人的基础上进行的总结和概括,所以这篇博客 南国还是那他归纳为转载。干货很多,马上到~

1.基本概念

1.1 并行和并发

这个概念在之前的博客中,我也讲过多次了。之类再做个简单的概述。
并发是指宏观上在一段时间内能同时运行多个程序(按时间片交叉运行),而并行则指同一时刻能运行多个指令。

并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。
操作系统通过引入进程和线程,使得程序能够并发运行。

1.2 共享

共享是指系统中的资源可以被多个并发进程共同使用。

有两种共享方式:互斥共享和同时共享

互斥共享的资源称为临界资源,例如打印机等,在同一时间只允许一个进程访问,需要用同步机制来实现对临界资源的访问。

1.3 虚拟

虚拟技术把一个物理实体转换为多个逻辑实体。

主要有两种虚拟技术:时分复用技术和空分复用技术。

多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。

虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。

1.4 异步

异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。

2.操作系统的基本功能

  • 进程管理(有些书里面也将他说为处理机管理)
    进程控制、进程同步、进程通信、死锁处理、处理机调度等。

  • 内存管理
    内存分配、地址映射、内存保护与共享、虚拟内存等。

  • 文件管理
    文件存储空间的管理、目录管理、文件读写管理和保护等。

  • 设备管理
    完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率。
    主要包括缓冲管理、设备分配、设备处理、虛拟设备等。

后面的内容大多会围绕这几个方面进行展开论述。

3.进程管理

3.1 进程和线程

进程是资源分配的基本单位

进程的组成部分有:
①程序。作用:描述进程要完成的功能。
②数据集合。作用:程序在执行时所需要的数据和工作区。
③程序控制块。作用:包含进程的描述信息和控制信息。它是进程存在的唯一标志。

进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。

线程是独立调度的基本单位。一个进程中可以有多个线程,它们共享进程资源。

例如:QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。

3.2进程和线程的区别(重要!!)

  • 拥有资源
    进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
  • 调度
    线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
  • 并发
    不仅进程之间可以并发执行,多个线程之间也可以并发执行,提高吞吐量。
  • 系统开销
    由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
  • 通信方面
    线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。

3.3 进程的状态

在这里插入图片描述

3.4进程调度算法

不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。

1. 批处理系统

批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。

1.1 先来先服务 first-come first-serverd(FCFS)
按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。

1.2 短作业优先 shortest job first(SJF)
按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。

1.3 最短剩余时间优先 shortest remaining time next(SRTN)
按估计剩余时间最短的顺序进行调度。

2. 交互式系统

交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。

2.1 时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。

时间片轮转算法的效率和时间片的大小有很大关系:

  • 因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。
  • 而如果时间片过长,那么实时性就不能得到保证。
    在这里插入图片描述
    2.2 优先级调度
    为每个进程分配一个优先级,按优先级进行调度。
    为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。

2.3 多级反馈队列
一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。
多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,…。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。
在这里插入图片描述

3. 实时系统

实时系统要求一个请求在一个确定时间内得到响应。
分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。

3.5 进程同步

多进程虽然提高了系统资源利用率和吞吐量,但是由于进程的异步性可能造成系统的混乱。进程同步的任务就是对多个相关进程在执行顺序上进行协调,使并发执行的多个进程之间可以有效的共享资源和相互合作,保证程序执行的可再现性

同步机制需要遵循的原则:

  1. 空闲让进:当没有进程处于临界区的时候,应该许可其他进程进入临界区的申请
  2. 忙则等待:当前如果有进程处于临界区,如果有其他进程申请进入,则必须等待,保证对临界区的互斥访问
  3. 有限等待:对要求访问临界资源的进程,需要在有限时间呃逆进入临界区,防止出现死等
  4. 让权等待:当进程无法进入临界区的时候,需要释放处理机,边陷入忙等
    经典的进程同步问题:生产者-消费者问题;哲学家进餐问题;读者-写者问题

1. 临界区
对临界资源进行访问的那段代码称为临界区。

为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。

2. 同步与互斥
同步:多个进程按一定顺序执行;
互斥:多个进程在同一时刻只有一个进程能进入临界区。

3. 信号量
信号量(Semaphore)是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。

  • down : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0,进程睡眠,等待信号量大于 0;
  • up :对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。

down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。

如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。

typedef int semaphore;
semaphore mutex = 1;
void P1() {
    down(&mutex);
    // 临界区
    up(&mutex);
}

void P2() {
    down(&mutex);
    // 临界区
    up(&mutex);
}

使用信号量实现生产者-消费者问题
问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。

因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。

为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品。

注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,消费者就无法执行 up(empty) 操作,empty 永远都为 0,导致生产者永远等待下,不会释放锁,消费者因此也会永远等待下去。

#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;

void producer() {
    while(TRUE) {
        int item = produce_item();
        down(&empty);
        down(&mutex);
        insert_item(item);
        up(&mutex);
        up(&full);
    }
}

void consumer() {
    while(TRUE) {
        down(&full);
        down(&mutex);
        int item = remove_item();
        consume_item(item);
        up(&mutex);
        up(&empty);
    }
}

4. 管程
使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。

c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。

monitor ProducerConsumer
    integer i;
    condition c;

    procedure insert();
    begin
        // ...
    end;

    procedure remove();
    begin
        // ...
    end;
end monitor;

管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否者其它进程永远不能使用管程。

管程引入了条件变量以及相关的操作:wait() 和 signal() 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。

使用管程实现生产者-消费者问题

// 管程
monitor ProducerConsumer
    condition full, empty;
    integer count := 0;
    condition c;

    procedure insert(item: integer);
    begin
        if count = N then wait(full);
        insert_item(item);
        count := count + 1;
        if count = 1 then signal(empty);
    end;

    function remove: integer;
    begin
        if count = 0 then wait(empty);
        remove = remove_item;
        count := count - 1;
        if count = N -1 then signal(full);
    end;
end monitor;

// 生产者客户端
procedure producer
begin
    while true do
    begin
        item = produce_item;
        ProducerConsumer.insert(item);
    end
end;

// 消费者客户端
procedure consumer
begin
    while true do
    begin
        item = ProducerConsumer.remove;
        consume_item(item);
    end
end;

生产者和消费者问题前面已经讨论过了。

1. 读者-写者问题
允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。

一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。

typedef int semaphore;
semaphore count_mutex = 1;
semaphore data_mutex = 1;
int count = 0;

void reader() {
    while(TRUE) {
        down(&count_mutex);
        count++;
        if(count == 1) down(&data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问
        up(&count_mutex);
        read();
        down(&count_mutex);
        count--;
        if(count == 0) up(&data_mutex);
        up(&count_mutex);
    }
}

void writer() {
    while(TRUE) {
        down(&data_mutex);
        write();
        up(&data_mutex);
    }
}

在这里插入图片描述

3.6 进程通信(重要!!)

进程同步与进程通信很容易混淆,它们的区别在于:

  • 进程同步:控制多个进程按一定顺序执行;
  • 进程通信:进程间传输信息。

进程通信就是指进程间的信息交换,交换信息可以使一个状态,也可以是很多的byte。进程间同步互斥也存在信息的交换,因此也属于是一种IPC,属于是低级通信。该低级通信存在的问题:1)通信的数据量太少;2)通信对用户不透明(数据的传递或者同步互斥都需要程序员实现)

高级通信机制(高级通信的通信细节被OS隐藏,因此使用起来增加方便而且可以传送大量的数据,尤其是管道通信):

  • 1.共享内存(最快的方式)
    相互通信的进程共享某些数据结构或者是存储区,进程之间可以通过这些共享空间进行通信。分为:1)基于共享数据结构的通信,如生产者消费者系统中的有界缓冲区;2)基于共享存储区的通信,可以传输大量数据,通信的进程之间可以像读写普通存储器一样读写共享存储区
  • 2.消息传递系统
    进程间通信采用的是格式化的消息,可以直接使用OS提供的消息发送或者接受原语进行通信。由于隐藏了通信细节,所以简化了通信程序的复杂性
  • 3.管道通信
    管道是连接两个一个读进程和一个写进程之间用于实现数据交换的一个共享文件。为了协调管道通信双方,需要管道机制实现如下功能:1)互斥:统一时刻只能有一个进程对管道进行读写;2)同步:当读端发现管道为空的时候需要睡眠等待,直到有数据时候被唤醒,相应的写端也是在管道已满的时候等待直到被唤醒;3)确定对方的存在性:只有同时有读端和写端,管道才有存在意义
  • 4.信号量
    进程间通信处理同步互斥的机制。信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。(关键就是通过PV操作)

3.7 孤儿进程和僵死进程

(1)孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

危害:由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统

(2)僵死进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程

危害:如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

危害的场景:例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了

4.死锁

4.1 死锁的必要条件和处理方法

定义:死锁是指多个进程在运行过程中,因为争夺资源而造成的一种僵局,如果没有外力推进,处于僵局中的进程就无法继续执行。

死锁原因:

  1. 竞争资源:请求同一有限资源的进程数多于可用资源数
  2. 进程推进顺序非法:进程执行中,请求和释放资源顺序不合理,如资源等待链

死锁产生的必要条件:

  1. 互斥条件:进程对所分配的资源进行排他性的使用
  2. 请求和保持条件:进程被阻塞的时候并不释放锁申请到的资源
  3. 不可剥夺条件:进程对于已经申请到的资源在使用完成之前不可以被剥夺
  4. 环路等待条件:发生死锁的时候存在的一个 进程-资源 环形等待链

死锁处理:

  1. 预防死锁:破坏产生死锁的4个必要条件中的一个或者多个;实现起来比较简单,但是如果限制过于严格会降低系统资源利用率以及吞吐量
  2. 避免死锁:在资源的动态分配中,防止系统进入不安全状态(可能产生死锁的状态)-如银行家算法
  3. 检测死锁:允许系统运行过程中产生死锁,在死锁发生之后,采用一定的算法进行检测,并确定与死锁相关的资源和进程,采取相关方法清除检测到的死锁。实现难度大
  4. 解除死锁:与死锁检测配合,将系统从死锁中解脱出来(撤销进程或者剥夺资源)。对检测到的和死锁相关的进程以及资源,通过撤销或者挂起的方式,释放一些资源并将其分配给处于阻塞状态的进程,使其转变为就绪态。实现难度大

死锁定理:S为死锁状态的充分条件是,当且仅当S的资源分配图是不能完全简化的。

4.2 说说进程的饥饿和死锁的区别?

它们两者都是进程与进程之间存在的两种资源竞争的状态:

死锁:如果一个进程集合中的每个进程都在等待只能由该组进程中的其他进程才能引发的事件,那么,该组进程是死锁的。

饥饿:主要就是指存在某些进程永远得不到服务的机会。比如多个文件进行请求打印机设备分配打印机问题。如果首先分配给小文件,那么大文件可能永远得不到打印。对于这问题,比较好的解决方法就是通过先来先服务方式。

5.内存管理

5.1 虚拟存储器出现的原因以及其主要的思想?

出现的原因:遇到程序太大,以至于内存容纳不下,而必须进行分段进行载入内存
基本思想:程序,数据和堆栈的总大小可能超过可用的物理内存的大小。由操作系统把程序当前使用的那部分保留在主存中,而把其他的部分保存在磁盘上。这样就实现用小的内存大小来运行大的程序。

5.2 物理地址和虚拟地址分别指什么?

物理地址:就是内存中实际存在的地址,也就是我们电脑中内存条中所表示的地址。

虚拟地址:被分成虚拟页号(高位)和偏移量(低位)是一种虚拟化的地址,是由程序产生的地址。虚拟地址与物理地址不是一一对应关系,它们之间存在映射关系。而转换就是通过MMU(其中MMU(内存管理单元):作用为把虚拟地址映射为物理内存地址)来进行。

5.3 什么叫做虚拟内存?虚拟存储器的特征是什么?

如果存在一个程序,所需内存空间超过了计算机可以提供的实际内存,那么由于该程序无法装入内存所以也就无法运行。单纯的增加物理内存只能解决一部分问题,但是仍然会出现无法装入单个或者无法同时装入多个程序的问题。但是可以从逻辑的角度扩充内存容量,即可解决上述两种问题,所以出现了虚拟内存;

虚拟存储器定义:就是具有请求调入功能和置换功能,可以从逻辑上对内存容量加以扩充的一种存储器系统。虚拟存储器都是建立在离散内存管理的基础上。

虚拟存储器的特征

  1. 多次性:一个作业可以分多次被调入内存。多次性是虚拟存储特有的属性
  2. 对换性:作业运行过程中存在换进换出的过程(换出暂时不用的数据换入需要的数据)
  3. 虚拟性:虚拟性体现在其从逻辑上扩充了内存的容量(可以运行实际内存需求比物理内存大的应用程序)。虚拟性是虚拟存储器的最重要特征也是其最终目标。虚拟性建立在多次性和对换性的基础上行,多次性和对换性又建立在离散分配的基础上

5.4 页表是什么?页表的表项有哪些结构?页面的抖动和颠簸又是指什么?

页表:把虚拟地址映射到页帧。(每个进程都有自己的页表,因为每个进程都有自己的虚拟空间)
页表的结构:(1)高速缓存禁止位:标识是否被缓存
(2)访问位:标识是否被访问,主要是作用于页面置换
(3)修改位:为了记录页面的使用状况,用于标识是否被修改过
(4)保护位:指出一个页面允许什么类型的访问
(5)"在/不在"位:标识是否处于内存中,如果不在,就需要从磁盘进行载入内存
(6)页帧号

页面抖动:刚刚换出的页面可能又被接下来访问。
页面颠簸:每执行几条程序语句,就会发生页面失效(缺页)的情况。

5.5 页面置换算法有哪些?

(1)最佳置换算法:只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。
(2)先进先出置换算法:简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面
(3)最近最久未使用算法LRU:算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)
(4)时钟算法clock(也被称为是最近未使用算法NRU):页面设置一个访问为,并将页面链接为一个环形队列,页面被访问的时候访问位设置为1。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面
(5)改进型Clock算法:在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问为何修改位都是0的页面,其次是访问位为0修改位为1的页面。
(6)最少使用算法LFU:设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。存在问题是该访问寄存器并不能真正反映当前页面访问次数,因为访问速度比较快,所以在更新寄存器的时间间隔内访问1次和访问100次都是一样的。另外,LFU和LRU是很类似的,支持硬件也是一样的,但是区分两者的关键在于一个以时间为标准,一个以次数为标准(例如对于寄存器 pa 001111 和pb 111000,两个页面,如果采用LRU,那么被淘汰的是pa,如果采用LFU那么被淘汰的是pb)。
(7)页面缓冲算法PBA:置换的时候,页面无论是否被修改过,都不被置换到磁盘,而是先暂留在内存中的页面链表(已修改页面链表和未修改页面链表,也可以不区分)里面,当其再次被访问的时候可以直接从这些链表中取出而不必进行磁盘IO,当链表中已修改也难数目达到一定数量之后,进行依次写磁盘操作(相当于将多次IO合并为一次)

5.6 操作系统在什么情况下会做调页有关的工作?

(1)进程创建:操心系统要确定程序和数据最初的大小,并为它们创建一个页表。
(2)进程执行:必须为新进程重置MMU(内存管理单位)和刷新TLB(快表),以清除以前进程所留下的痕迹。
(3)页面失效:操作系统必须通过读硬件寄存器来确定哪个虚拟地址造成页面失效,并且通过计算磁盘哪个页面需要被换入。
(4)进程销毁:操作系统必须释放进程的页表,页面和页面在磁盘所占有的空间。

5.7内存管理方式

答:内存管理方式出现的原因:由于连续内存分配方式(单一连续分配,固定分区分配,动态分区分配,动态重定位分区分配)导致的内存利用率偏低以及内存碎片的问题,进而引出离散的内存分配方式。离散内存分配可以从OS的内存管理角度引出页式(离散分配的基本单位是页)管理,也可以从程序编制角度引出段式(离散分配的基本单位是段)管理。

(1)页式
基本分页存储管理中不具备页面置换功能(即没有实现虚拟内存的功能),因此需要整个程序的所有页面都装入内存之后才可以运行。因为程序数据存储在不同的页面中,而页面又离散的分布在内存中,因此需要一个页表来记录逻辑地址和实际存储地址之间的映射关系,以实现从页号到物理块号的映射。由于页表也是存储在内存中的,因此和不适用分页管理的存储方式相比,访问分页系统中内存数据需要两次的内存访问(一次是从内存中访问页表,从中找到指定的物理块号,加上页内偏移得到实际物理地址;第二次就是根据第一次得到的物理地址访问内存取出数据)
为了减少两次访问内存导致的效率影响,分页管理中引入了快表(或者联想寄存器)机制,包含快表机制的内存管理中,当要访问内存数据的时候,首先将页号在快表中查询,如果查找到说明要访问的页表项在快表中,那么直接从快表中读取相应的物理块号;如果没有找到,那么访问内存中的页表,从页表中得到物理地址,同时将页表中的该映射表项添加到快表中(可能存在快表换出算法)。
在某些计算机中如果内存的逻辑地址很大,将会导致程序的页表项会很多,而页表在内存中是连续存放的,所以相应的就需要较大的连续内存空间。为了解决这个问题,可以采用两级页表或者多级页表的方法,其中外层页表一次性调入内存且连续存放,内层页表离散存放。相应的访问内存页表的时候需要一次地址变换,访问逻辑地址对应的物理地址的时候也需要一次地址变换,而且一共需要访问内存3次才可以读取一次数据。

(2)段式
分页是为了提高内存利用率,而分段是为了满足程序员在编写代码的时候的一些逻辑需求(比如数据共享,数据保护,动态链接等)
分段内存管理当中,地址是二维的,一维是段号,一维是段内地址;其中每个段的长度是不一样的,而且每个段内部都是从0开始编址的。由于分段管理中,每个段内部是连续内存分配,但是段和段之间是离散分配的,因此也存在一个逻辑地址到物理地址的映射关系,相应的就是段表机制。段表中的每一个表项记录了该段在内存中的起始地址和该段的长度。段表可以放在内存中也可以放在寄存器中。
访问内存的时候根据段号和段表项的长度计算当前访问段在段表中的位置,然后访问段表,得到该段的物理地址,根据该物理地址以及段内偏移量就可以得到需要访问的内存。由于也是两次内存访问,所以分段管理中同样引入了联想寄存器。

分段和分页的对比:
(1)对程序员的透明性:分页透明,但是分段需要程序员显示划分每个段。
(2)地址空间的维度:分页是一维地址空间,分段是二维的。
(3)大小是否可以改变:页的大小不可变,段的大小可以动态改变。
(4)出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。

(3)段页式:
先将用户程序分为若干个段,然后再把每个段分成若干个页,并且为每一个段赋予一个段名称。这样在段页式管理中,一个内存地址就由段号,段内页号以及页内地址三个部分组成。

段页式内存访问:系统中设置了一个段表寄存器,存放段表的起始地址和段表的长度。地址变换时,根据给定的段号(还需要将段号和寄存器中的段表长度进行比较防止越界)以及寄存器中的段表起始地址,就可以得到该段对应的段表项,从段表项中得到该段对应的页表的起始地址,然后利用逻辑地址中的段内页号从页表中找到页表项,从该页表项中的物理块地址以及逻辑地址中的页内地址拼接出物理地址,最后用这个物理地址访问得到所需数据。由于访问一个数据需要三次内存访问,所以段页式管理中也引入了高速缓冲寄存器。

参考资料:
1.汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001.
2. CS-Notes
3. 手把手教你如何玩转面试(操作系统)

猜你喜欢

转载自blog.csdn.net/weixin_38073885/article/details/88778241