软件开发搞定操作系统

文章目录

    • 1. 前言
    • 2. 操作系统的演进
    • 3. 操作系统相关概念
    • 4. 进程管理
      • 4.1 进程
      • 4.2 线程
      • 4.3 进程状态
      • 4.4 同步
      • 4.5 Linux进程
    • 5. 作业管理
      • 5.1 进程调度
      • 5.2 死锁
    • 6. 存储管理
      • 6.1 内存分配与回收
      • 6.2 内存回收
      • 6.3 内外碎片
      • 6.4 存储的管理
        • 6.4.1 页式存储管理
        • 6.4.2 段式存储管理
        • 6.4.3 段页式存储管理
      • 6.5 虚拟内存
      • 6.6 Linux存储管理
        • 6.6.1 Buddy内存分配算法
        • 6.6.2 Linux交换空间
    • 7. 文件管理
      • 7.1 文件逻辑结构
      • 7.2 辅存的存储空间分配
        • 7.2.1 辅存分配方式
        • 7.2.2 存储空间管理
      • 7.3 目录管理
      • 7.4 Linux文件
    • 8. 设备管理
      • 8.1 广义的IO设备
      • 8.2 IO设备的缓冲区
      • 8.3 SPOOLing技术

1. 前言

操作系统是管理计算机硬件和软件资源的计算机程序。比如管理配置内存、决定资源供需顺序、控制输入输出设备,提供用户交互等。

操作系统并不局限于存在在计算机,手机也是有操作系统的。常见的操作系统有:Android、iOS、Windos、Linux、MacOS。

我们不可能直接操作计算机硬件,而且像以前的计算机中编写的程序并不是可以在任何计算机上可以运行,所以就需要一个通用的环境,提出了操作系统。有了操作系统,普通人也可以方便使用。

软件开发搞定操作系统_第1张图片

2. 操作系统的演进

1.无操作系统的时代

  • 人工操作:就是人工把编写的代码或数据转成二进制给计算机,然后使用穿孔纸带(带孔的设为1,无孔设为0),经过光电扫描输入电脑。要是有一个错误或者想改写代码/数据就得重新打印纸带。

    其实那时候一般都是专业程序员搞的,并不是普通人就可以玩的。而且每次读取一条纸带就得占用一台计算机。CPU等人工操作。每次纸带读取完还得人工去拿下一条纸带给它读取,人不能离开计算机。资源利用率很低。

2.批处理系统的时代:

  • 批处理是指用户将一批作业提交给操作系统后就不再干预,由操作系统控制它们自动运行,再输出结果,从而减少作业建立和结束过程中的时间浪费。也就是无需等待人工操作,计算机这时可以自动、成批的执行程序。
  • 批量输入任务。数据可以批量输入到计算机中,但是并不是一次性把批量输入都处理了,早期的计算机的操作系统可以称为单道批处理系统,即内存中只允许存放一个作业。
  • 资源利用率相对于之前有提高。但是一个作业单独进入内存并独占系统资源,直到运行结束后下一个作业才能进入内存,当作业进行I/O操作时,CPU只能处于等待状态,所以CPU利用率还是较低,尤其是对于I/O操作时间较长的作业。

多道程序设计。后来才提出的,此时的操作系统可以称为:多道批处理系统。即在内存中可同时存在若干道作业,说白了就是让批处理系统可以一次性处理多个作业,但其实是快速的穿插运行,使得看起来可以处理多个作业,因此CPU的利用率显著地提高了。

软件开发搞定操作系统_第2张图片
3.分时系统的时代:

  • 人-机交互,也就是人工可以干预计算机,及时调试程序。
  • 多用户共享计算机资源。
  • 资源利用率大幅度提高.

3. 操作系统相关概念

  • 并发性:指的是两个或多个事件可以在同一个时间间隔发送。
  • 并行性:指的是两个或多个事件可以在同一个时刻发送。

软件开发搞定操作系统_第3张图片

当提到单处理器时实际上指的是并发性,提到多处理器指的是并行性。

软件开发搞定操作系统_第4张图片

  • 共享性:操作系统的资源可以供给多个并发的程序共同使用。共享性可分为:互斥共享(当资源被程序A占用时,其他程序就只能等待程序A使用完释放后才可以使用),同时访问(某种资源在一段时间内并发地被多个程序访问,可以宏观地看该资源可以被同时访问,因为很快)

  • 虚拟性:把一个物理实体转变为若干个逻辑实体,物理实体是真实存在的,比如硬件设备,逻辑实体是虚拟存在的,虚拟的技术主要有时分复用技术和空分复用技术。

    • 时分复用技术:资源在时间上进行复用,不同程序并发使用。
      • 虚拟处理技术:借助多道程序设计,为每个程序建立进程,然后多个进程分时复用处理器。
      • 虚拟设备技术:物理设备虚拟为多个逻辑设备(能进行逻辑运算的设备),每个程序占用一个逻辑设备,多个程序通过逻辑设备并发访问。比如后面要学的spooling用的就是虚拟设备技术。
    • 空分复用技术:来实现虚拟磁盘、虚拟内存等。
      • 虚拟硬盘技术:物理磁盘虚拟为逻辑磁盘,比如把一个机械硬盘分区。
      • 虚拟内存技术:在逻辑上扩大程序的存储容量,使用比实际内存更大的容量,提升编程效率。
  • 异步性:在多道程序设计的环境下,允许多个进程并发执行,进程在使用资源时可能需要等待或放弃。比如上面的互斥共享。进程的执行并不是一下子对程序的代码执行到底,而是以走走停停的形式推进。比如进程A释放资源,进程B,C目前在抢夺资源,此时B或者C谁能抢到是不知道的,这也体现异步性。

  • 操作系统的五大功能:进程管理、存储管理、作业管理、文件管理、设备管理。

4. 进程管理

4.1 进程

进程(Process):是系统进行资源分配和调度的基本单位。进程作为程序独立运行的载体,保证了程序正常执行。我们的桌面应用当点击启动时就会变成进程。

为什么需要进程?早期没操作系统,那么资源就只能分配给当前运行的程序;当有了操作系统,引入多道程序设计后,进程就出现,来合理隔离每个程序占用的资源。

进程在内存是一段连续的内存空间,这段空间称为进程控制块(PCB,一个数据结构),每一个进程一定有一个PCB,而进程控制块的构成如下:

软件开发搞定操作系统_第5张图片

解释:

  • 标识符:唯一标识一个进程,用于区别其他进程。
  • 状态:状态一共有三种(运行,等待,阻塞,当然也可以说5种,还有创建和终止)。有一张很重要的进程状态转换图,后续再说。
  • 优先级:表示获得CPU控制权的优先级大小
  • 程序计数器(PC):进程即将被执行的下一条指令的地址。
  • 内存指针:可能是程序代码、或进程数据相关指针。
  • 上下文数据:进程执行时处理器存储的数据。
  • IO状态信息:被进程IO操作所占用的文件列表。像要删除某个文件夹,发现删不了,一看原来是文件夹里面的文件没有关闭,一直占用。
  • 记账信息:进程使用CPU时间、时钟数总和等。

而上面可以总结分成四个种:进程标识符、处理机状态、进程调度信息、进程控制信息。

因为PCB是操作系统进行调度经常被读取的信息,所以PCB是常驻内存的,系统中有专门开辟一块PCB区域来存储所有进程的PCB。

4.2 线程

线程(Thread):是系统进行资源分配和调度的最小单位。进程是由系统分配,线程是由CPU分配,线程包含在进程中的,是进程中实际运行工作的单位,比如计算一个数据,是利用线程来进行计算。一个进程至少有一个线程,每当启动程序就先创建进程再创建一个线程。

一个进程可以并发多个线程,每个线程执行不同的任务。进程中的线程共享进程的资源,比如一个线程计算出的结果可以保存再进程中某个区域,然后另一个线程可以从进程中拿去到这个结果。

总结进程和线程的区别:

软件开发搞定操作系统_第6张图片

4.3 进程状态

也称为进程的生命周期。

1.就绪状态

  • 当进程被分配到除CPU以外的所有必要资源后,只要再获得CPU的使用权,就可以立即运行。
  • 在一个系统中多个处于就绪状态的进程通常排成一个队列,称为就绪队列。

2.运行(执行)状态:

  • 进程获取CPU,其进程就称为运行状态。
  • 在单处理器中,在某个时刻只能有一个进程是处于运行状态(并发)。

3.阻塞状态:

  • 进程因某种原因,如其他设备未就绪而无法继续执行。从而放弃CPU的状态就称为阻塞状态。
  • 像我们去打印时,在进程中已经向打印机发出打印请求,但发现打印机打印的可能还不是我们的,此时我们的进程就可以称为阻塞状态或等待中。
  • 系统中可能有一个或多个阻塞进程,所以也有阻塞队列这个概念。

4.创建状态:创建进程时拥有PCB但其他资源尚未就绪的状态称为创建状态。

5.终止状态:进程结束由系统清理或者归还PCB的状态称为终止状态。

进程状态转换图:

软件开发搞定操作系统_第7张图片

4.4 同步

引入生产者-消费者问题:有一群生产者进程在生产产品,并将这些产品提供给消费者进程进行消费,生产者进程和消费者进程可以并发执行,在两者之间设置了一个具有n可缓冲区的缓冲池,生产者进程需要将所生产的产品放到一个缓冲区中,消费者进程可以从缓冲区取走产品消费。

软件开发搞定操作系统_第8张图片

当生产者生产一个产品,缓冲区就+1,当消费者拿取一个产品,缓冲区就-1。

来到计算机,一般一个操作缓冲需要三个步骤:

register = count; // 先从缓冲区拿取产品数
register = register + 1; // 然后生产,对其加1
count = register; // 把更新的值归还到缓冲区

但是在计算机中这样是有问题的,因为计算机中是并发执行的,比如:

软件开发搞定操作系统_第9张图片

可以代码:

/*
* 刚开始仓库数为0
* 现在要生产10次,消费10次,那么理想中最终仓库数应该为0
* 使用多线程来并发模仿一个生产,一个消费
*/
class Store {
    private int num = 0;
    public void producer() {
        int times = 10;
        while(times-- != 0) {
            try {
                    this.num = this.num + 1;
                System.out.println("生产后,产品数为:" + this.num);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void comsumer() {
        int times = 10;
        while(times-- != 0) {
            try {
                this.num = this.num - 1;
                System.out.println("消费后,产品数为:" + this.num);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public int getNum() {
        return num;
    }
}
/*
* 消费者
*/
class Comsumer implements Runnable {

    private Store store;

    Comsumer(Store store) {

        this.store = store;
    }

    @Override
    public void run() {
        store.comsumer();
    }
}
/*
* 生产者
*/
class Producer implements Runnable {

    private Store store;

    Producer(Store store) {
        this.store = store;
    }

    @Override
    public void run() {
        store.producer();
    }
}
public class Main {

    public static void main(String[] args) {
        Store store = new Store();
        Producer producer = new Producer(store);
        Comsumer comsumer = new Comsumer(store);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(comsumer);
        t1.start();
        t2.start();
        // join 等待线程执行完才执行下面
        try {
            t1.join();
            t2.join();
            System.out.println("最终仓库剩余数:" + store.getNum());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果不唯一:我的量设置小了,可以设置大点。

最终仓库剩余数:3
最终仓库剩余数:5
最终仓库剩余数:0
最终仓库剩余数:-1

上面是我运行几次的结果,错的也有对的也有,这就是线程不同步的表现。

以上的根源问题就是:彼此间没有通信。比如现在仓库为空,生产者没有去通知消费者我目前正在生产,你先别消费,等我生产完再来消费。

线程同步:这个应该是我们听得最多的,进程也有同步那么线程更是有同步,因为进程的线程共享进程资源,像上面的代码案例就是一个线程不同步的表现(异步)。

线程同步的方法:(待详讲)

  • 互斥量
  • 读写锁
  • 自旋锁
  • 条件变量

临界资源:指的是一些虽作为共享资源却又无法同时被多个线程共同访问的共享资源。当有进程在使用临界资源时,其他进程必须依据操作系统的同步机制等待占用进程释放该共享资源才可重新竞争使 用共享资源。像上面的仓库就是一个临界资源。

进程同步:对竞争资源在多进程间进行使用有序的协调,使得并发执行的多个进程之间可以有效使用资源和相互合作。

为了对临界资源进行约束,提出了进程/线程间同步的四个原则

  • 空闲让进:资源无占用,允许使用。
  • 忙则等待:资源有占用,请求进程等待。
  • 有限等待:保证有限等待时间能够使用资源。
  • 让权等待:等待时,进程需要让出CPU。

进程同步的方法:(待详讲)

  • 消息队列
  • 共享存储
  • 信号量

4.5 Linux进程

进程类型:

  • 前台进程:就是具有终端shell,可以与用户交互的进程。
  • 后台进程:没有占用shell,不可以与用户交互的进程。如果要以后台运行,在末尾添加:"&"
  • 守护进程:很多守护进程在系统启动的时候就启动了,一直运行到系统关闭。Linux中,一般以“d”结尾的是守护进程,典型的守护进程有:crond,httpd,sshd,mysqld。

进程ID:唯一标识,ID是一个非负数,最大值由操作系统限定。ID为0的进程为idle进程,是系统创建的第一个进程。ID为1的进程为init进程,是0号进程的子进程,完成系统初始化。init进程是所有用户进程的祖先进程。

linux进程状态:

软件开发搞定操作系统_第10张图片

命令:

  • 进程中有父子进程,它们的关系通过pstree命令查看。
  • ps命令常用于显示当前进程的状态。配合aux参数或者ef参数和grep命令检索特定进程。
ps
ps -aux // 打印进程的详细信息 
ps -u root // 查看用户root的进程
ps -aux | grep 'xxx' // 查看特定进程
ps -ef --forest // 打印进程的父子关系
ps -aux --sort=-pcpu // 按照cpu的频率排序
ps -aux --sort=-pmen // 按照内存排序
  • kill命令发送指定信号给进程。kill -l 可以查看操作系统支持的信号。kill -9 进程ID:可以无条件终止进程。
  • top命令查看进程的状态。

5. 作业管理

5.1 进程调度

进程调度指的是计算机通过决策决定哪个就绪进程可以获得CPU使用权。主要有两个步骤:

  1. 保留旧进程的运行信息,请出旧进程。
  2. 选择新进程,准备运行环境并分配CPU。

为了实现这两个步骤就要了解三种机制:

  1. 就绪队列的排队机制:将就绪进程按照一定的方式排成队列,以便调度程序可以最快找到就绪队列。
  2. 选择队列进程的委派机制:调度程序以一定策略选择就绪进程,将CPU资源分配给它。
  3. 新老进程的上下文切换机制:保留旧进程的上下文信息,将新进程的上下文调度到CPU中准备好环境让新的进程可以运行起来。

要是进程调度时,旧进程还没执行完,此时也有两种进程调度方法:

  1. 非抢占式的调度:处理器一旦分配给某个进程,就让该进程一直执行下去。调度程序不以任何原因抢占正在被使用的处理器,直到进程完成工作或因为IO阻塞才会让出处理器。

  2. 抢占式的调度:允许调度程序以一定的策略暂停当前正在运行的进程,然后保留旧进程的上下文信息,分配处理器给新的进程

软件开发搞定操作系统_第11张图片

进程调度的算法:

  1. 先来先服务调度算法:就绪队列中按照谁先来的方式先调度到CPU中
  2. 短进程优先调度算法:调度程序优先选择就绪队列中估计运行时间最短的进程。但是对于较长运行时间的作业的调度非常不公平,有可能永远都没能运行。
  3. 高优先权优先调度算法:进程附带优先权,调度进程优先选择优先权高的进程。这使得紧急的任务可以优先被处理。
  4. 时间片轮转调度算法:按照先来先服务的原则排列就绪队列,每次从队列头部取出待执行的进程,分配一个时间片执行。该算法相对公平,但不能保证及时响应用户。最古老的算法。

除了时间片轮转调度算法属于抢占式的调度,其他三个属于非抢占式的调度。

看道题目:

软件开发搞定操作系统_第12张图片

解:

软件开发搞定操作系统_第13张图片
软件开发搞定操作系统_第14张图片
软件开发搞定操作系统_第15张图片
其实可以发现,如果没有多个进程在等待时,使用什么算法都是一样的。

5.2 死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

产生死锁的根源就是:

  1. 竞争的资源不够。如下,进程1占用传真机,进程2占用打印机,此时进程1想占用打印机,进程2想占用传真机,但是目前打印机和传真机正在被占用,必须等待释放,所以造成死锁。

软件开发搞定操作系统_第16张图片

  1. 进程的调度顺序不当:

软件开发搞定操作系统_第17张图片

只要满足以下四个必要条件就一定发生死锁:

  1. 互斥条件:进程对资源的使用是排他性的使用,即某资源只能由一个进程使用,其他进程需要使用只能等待。
  2. 请求保持条件:进程至少保持一个资源,又提出新的资源请求,新资源被占用,请求被阻塞,被阻塞的进程不释放自己保持的资源。
  3. 不可剥夺条件:进程获得的资源在未完成使用前不能被剥夺,获得的资源只能由进程自身释放。
  4. 环路等待条件:发生死锁时,必然存在进程-资源环形链,比如A进程正在等待B进程占用的资源,B进程正在等待C进程占用的资源,C进程正在等待A进程占用的资源。

如何预防死锁?只需要把上面四个条件中**除了互斥条件(这个破坏不了,因为有些资源根本就不能同时访问,比如打印机)**任意一个破坏即可:

  1. 破坏请求保持条件:系统规定进程运行之前,一次性申请所有需要的资源,进程在运行期间不会提出资源请求,从而破坏请求保持条件。
  2. 破坏不可剥夺条件:当一个进程请求新的资源得不到满足时,必须释放占有的资源,进程运行时占有的资源可以被释放,意味着可以被剥夺。
  3. 破坏环路等待条件:将系统资源进行统一排队,赋予不同编号;要求进程申请资源时,必须按照资源序号递增的顺序提出。线性申请不再形成环路,从而破坏了环路等待条件。比如:假设有1,2两个进程,1进程占用A资源需要使用B资源,2进程占用B资源需要使用A资源,这样会导致死锁,如果把资源按照A=>B的排序,并按顺序申请,那么对于B它必须先申请A资源,再申请B资源。

如何避免死锁?提出了银行家算法:以银行借贷系统分配策略为基础的算法,客户申请的贷款是有限的,每次申请需声明最大资金量,银行家在能够满足贷款时,都应该给用户贷款,客户在使用贷款后,能够及时归还贷款。

一般有三个数据结构:已分配资源表,可分配资源表,所需资源表。

来看例子,假设三张表的数据如下:

软件开发搞定操作系统_第18张图片

根据两张表,求个还需分配资源表:

软件开发搞定操作系统_第19张图片

下面就来操作:

软件开发搞定操作系统_第20张图片
软件开发搞定操作系统_第21张图片
软件开发搞定操作系统_第22张图片
软件开发搞定操作系统_第23张图片
软件开发搞定操作系统_第24张图片
软件开发搞定操作系统_第25张图片
可能有时候遇到资源分配不了的情况,一般会等待或者强行杀掉进程,极端的话就是蓝屏。

6. 存储管理

6.1 内存分配与回收

  1. 单一连续分配:属于最简单的一种,只能在单用户、单进程的操作系统中使用。

软件开发搞定操作系统_第26张图片

  1. 固定分区分配:固定分区分配是支持多道程序的最简单存储分配方式,内存空间被划分为若干固定大小的区域,每个分区只提供给一个程序使用,互不干扰。

软件开发搞定操作系统_第27张图片

  1. 动态分区分配:根据进程实际需要,动态分配内存空间。会涉及到相关数据结构、分配算法。

动态分区分配相关数据结构:

  1. 动态分区空闲表数据结构:

软件开发搞定操作系统_第28张图片

  1. 动态分区空闲链数据结构:常用这种。

软件开发搞定操作系统_第29张图片

动态分区的分配算法:

  1. 首次适应算法(FF算法):分配内存时从开始顺序查找适合内存区,若没有合适的空闲区,则该次分配失败。但是这就有个缺点,因为每次从头部开始,要是此时较头部的刚好有一个很大容量的空闲区可以分配,就会分配给它,然后再对这个很大容量的空闲区划分(因为一部分被占用了),也就是大材小用,一个可以分配给大容量的内存被一次一次划分,导致下次遇到一个大容量的可能分配不了。
  2. 最佳适应算法(BF算法):最佳适应算法要求空闲区链表按照容量大小排序,遍历空闲区链表找到最佳合适空闲区。这样可以避免大材小用的情况。比如:

软件开发搞定操作系统_第30张图片

  1. 快速适应算法(QF算法):要求有多个空闲区链表,每个空闲区链表存储一种容量的空闲区。

软件开发搞定操作系统_第31张图片

6.2 内存回收

一共有以下4种情况:

软件开发搞定操作系统_第32张图片

对于第一种和第二种其实都是一样的:

软件开发搞定操作系统_第33张图片

对于第三种:

软件开发搞定操作系统_第34张图片

对于第四种:

软件开发搞定操作系统_第35张图片

6.3 内外碎片

页内碎片/内碎片/内部碎片:内部碎片是已经被分配出去的内存空间(能明确指出属于哪个进程)大于请求所需的内存空间,不能被利用的内存空间就是内部碎片。

页外碎片/外碎片/外部碎片:外部碎片是指还没有分配出去的内存空间(不属于任何进程),但是由于大小而无法分配给申请内存空间的新进程的内存空闲块。

看图:

软件开发搞定操作系统_第36张图片

单一连续分配算法可能产生内碎片。

固定分区分配算法可能产生内碎片和外碎片。

动态分区分配算法可能产生外碎片。

6.4 存储的管理

上面是分配内存和回收内存,现在我们需要知道操作系统是如何管理进程的空间,比如内存满了又来一个新程序要怎么办等问题。有三种方法:页式存储管理,段式存储管理,段页式存储管理。

6.4.1 页式存储管理

首先需要知道页面这个单位还有块。

  • :对逻辑地址分页。对象是进程,页由页号和页内偏移地址组成,页号是一个标识,页内偏移是虚拟地址的划分,指向程序中的某一页。
  • :对物理地址分块。对象是内存,块由块号和块内偏移地址组成,块号是一个标识,块内偏移是实际地址的划分,指向内存空间中某一个物理块。

页和块的大小相等,只是为了区别计算机内存和进程而提出块和页。

页式存储管理:将进程逻辑空间等分成若干大小的页面(等分划分),并编号;相应的把物理内存空间分成与页面相同大小的物理块,并编号;以页面为单位把进程空间装进物理内存中分散的物理块。

在这里就有页表这个概念,页表记录进程逻辑空间与内存物理空间的映射。如图:

软件开发搞定操作系统_第37张图片

在现代计算机中,可以支持非常大的逻辑地址空间,所以页表就变得非常大,要占用非常大的内存空间,就提出了多级页表,由顶级页表(首页)与其他页表(二级页表、三级页表等)的映射,而且是按需拿取,比如现在需要某一页,但该页没有在内存中,所以通过顶级页表的映射关系把该页加载到内存中,其他不用的就不加载。所以相对来说节省空间,当然这是一种时间换空间的算法,所以比较耗时。

页式存储管理如果有一段连续的逻辑分布在多个页面中,将大大降低执行效率,所以提出了段式存储管理。

6.4.2 段式存储管理

段式存储管理:将进程逻辑空间划分成若干段(非等分)并编号,每段都定义了一组逻辑信息,例如主函数MAIN、子程序段X、子函数Y等。段的长度由逻辑信息组的长度(连续逻辑)决定。

段由段号和段内偏移地址组成,也有段表,存储段号、物理地址的起始地址(基址)和段长的表,如下:

软件开发搞定操作系统_第38张图片

段式存储和页式存储的比较:

  • 相同:段式存储和页式存储都离散地管理了进程的逻辑空间。
  • 不同:
    • 页是相对于物理空间去划分(这里跟前面说的不一样,前面是页和字块的比较,页和字块密切相关(页大小等于块大小),这里是页和段的比较,段比页更加接近逻辑空间划分),段是相对于逻辑空间去划分。
    • 分页是为了合理利用空间,分段是满足用户要求。
    • 页大小由硬件固定,段长度可动态变化。
    • 表的结构不同。

6.4.3 段页式存储管理

分页可以有效提高内存利用率(虽然说存在内碎片),分段可以更好满足用户需求,两者结合,形成段页式存储管理。

段页式存储管理:先将逻辑空间按段式管理分成若干段,再把段内空间按页式管理等分成若干页

段页式的地址结构:段号、段内页号、页内地址(通过块号找到内存的物理地址)。

如下图,基址是每一段的起始地址,表可能还有其他内容,这里忽略。

软件开发搞定操作系统_第39张图片

6.5 虚拟内存

有些进程实际需要的内存很大,超过物理内存的容量,而且多道程序设计(一个内存运行多个进程),使得每个进程可用物理内存更加稀缺,但是不可能无限增加物理内存,物理内存总有不够的时候,所以提出虚拟内存。

虚拟内存的实现是基于局部性原理的。局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中

所以程序运行时,无需全部装入内存,装载部分即可,如果访问页不在内存,则发出缺页中断,发起页面置换算法。从用户层面看,程序拥有很大的空间,即是虚拟内存。虚拟内存实际是对物理内存的补充,速度接近于内存,成本接近于辅存

虚拟内存:把程序使用的内存划分,将部分暂时不使用的内存放置在辅存。

软件开发搞定操作系统_第40张图片

上面提到置换算法,在虚拟内存中有三种置换算法:(这些置换算法也在组成原理的高速缓存中也有,但缺了OPT,当时说的是字块,现在说的是页面,其实都是一样的)

  • 先进先出算法(FIFO):顾名思义,优先移除最先进入内存的页面,后来的页面添加到队尾。
  • 最不经常使用算法(LFU):优先淘汰(移除)掉最不经常使用的页面。所以需要额外的空间来记录字块的访问次数。
  • 最近最少使用算法(LRU):优先淘汰一段时间内没有使用的页面,有多种实现方法,一般使用双向链表。每次使用的字块都会被拉到链表前(保证链表头是最近使用的),太久没使用的最终会被放到最后,然后当链表满了要添加数据时就会把链表尾的淘汰掉。
  • 最佳置换算法(OPT,理想的置换算法):发生缺页时,有些页面在内存中,其中有一页将很快被访问(也包含紧接着的下一条指令的那页),而其他页面则可能要到10、100或者1000条指令后才会被访问,每个页面都可 以用在该页面首次被访问前所要执行的指令数进行标记。该算法实现不了,因为计算机不知道当缺页发生时,操作系统无法知道各个页面下一次是在什么时候被访问。

看题:

软件开发搞定操作系统_第41张图片

解:

软件开发搞定操作系统_第42张图片
软件开发搞定操作系统_第43张图片
软件开发搞定操作系统_第44张图片
大型游戏中,有时候走近一个场景发现场景的东西才慢慢加载出来,这就是触发了置换策略;黑暗之魂中打BOSS前的雨门,当我们走进雨门就触发置换策略,把BOSS场景的东西加载出来。

在组成原理中说到高速缓存和主存的置换:

软件开发搞定操作系统_第45张图片

现在,说到主存和辅存的置换:

软件开发搞定操作系统_第46张图片

替换策略发生在Cache-主存层次、主存-辅存层次,Cache-主存层次的替换策略主要是为了解决速度问题,主存-辅存层次主要是为了解决容量问题。

6.6 Linux存储管理

6.6.1 Buddy内存分配算法

Buddy内存分配算法:在Linux中为了努力让内存分配与相邻内存合并能快速进行(解决外碎片),算法基于计算机处理二进制的优势具有极高的效率,以向上取整为2的幂大小作为内存分配原则,比如一个进程占70k,那么就会分配一个128k的内存给它。Buddy翻译过来是“伙伴”,具体指的是内存的“伙伴”,一片连续内存的“伙伴”是相邻的另一片大小一样的连续内存,看图理解:

软件开发搞定操作系统_第47张图片

Buddy内存的分配,会创建一系列空闲块链表,每一种都是2的幂次方。

软件开发搞定操作系统_第48张图片

来看下Buddy内存分配的例子:

  1. 假设存储空间有1M大小:也就是只有1MB的那块空闲区域链表有一个节点。

软件开发搞定操作系统_第49张图片

  1. 现在分配100k内存:

软件开发搞定操作系统_第50张图片

  1. 当运行完,回收时:

软件开发搞定操作系统_第51张图片

Buddy内存分配算法把外碎片变成内碎片。

6.6.2 Linux交换空间

交换空间(Swap)是磁盘的一个分区,Linux物理内存满时,会把一些内存交换至Swap空间,Swap空间是初始化系统时配置的。可以在Linux中查看,使用top命令:
软件开发搞定操作系统_第52张图片

交换空间的作用:

  • 冷启动内存依赖:比如对于一些大型的应用程序,在启动的时候需要使用大量内存,但是这些内存只是在启动的时候用一下后续在运行时很少用或不会用,所以系统就可以将这些不怎么用的内存放到交换空间去,从而释放更多的物理内存,提供给系统使用。
  • 系统睡眠依赖:当Linux要睡眠的时候,把内存中的所有数据保存到交换空间中,当下次唤醒Linux时再重新加载到内存中,加快系统的启动速度。
  • 大进程空间依赖:有些内存确实需要使用到很多的内存空间,但是物理内存不够使用,所以将进程需要使用的数据暂时保存到交换空间去,使得让大进程可以运行。

交换空间跟我们前面说到虚拟空间类似,下面是它们的比较:

软件开发搞定操作系统_第53张图片

7. 文件管理

7.1 文件逻辑结构

软件开发搞定操作系统_第54张图片

无结构文件:也称为流式文件,文件内容长度以字节为单位,比如exe文件、dll文件、so文件等。

有结构文件:文件内容由定长记录和可变长记录组成,定长记录存储文件格式、文件描述等结构化数据项,可变长记录存储文件具体内容。

软件开发搞定操作系统_第55张图片

有以下几种形式实现有结构文件:

  1. 顺序文件:顺序文件是指按顺序存放在存储介质中的文件,磁带的存储特性使得磁带文件只能存储顺序文件,顺序文件是所有逻辑文件当中存储效率最高的。但是对于顺序文件,如果要进行增删改,比如在文件中间添加数据,那么文件中间后面的数据都得往后移,学过数据结构与算法的都知道,这个做法是很费时的,所以**对于顺序结构的增删改的效率是非常差的。**所以提出索引文件。
  2. 索引文件:可变长文件不适合使用顺序文件格式存储,索引文件是为了解决可变长文件存储而发明的一种文件格式,索引文件需要配合索引表完成存储的操作。

软件开发搞定操作系统_第56张图片

  1. 索引顺序文件:顺序文件和索引文件结合的产物,各记录本身在介质上也是顺序排列的,它包含了直接处理和修改记录的能力。

7.2 辅存的存储空间分配

7.2.1 辅存分配方式

辅存分配方式指的是如何在辅存(磁盘)上存储数据。

软件开发搞定操作系统_第57张图片

1.连续分配:顺序读取文件内容非常容易,速度很快,对存储要求高,要求满足容量的连续存储空间。如图:

软件开发搞定操作系统_第58张图片

2.链接分配:链接分配可以将文件存储在离散的盘块中,需要额外的存储空间存储文件的盘块链接顺序。

  • 隐式链接如图:

软件开发搞定操作系统_第59张图片

  • 显式链接如图:使用一张表来存储,一个链接和该链接的下一个链接。但是显示链接分配不支持高效的直接存储(FAT记录项多,所以需要去找出空闲区域),检索时FAT表占用较大的存储空间(需要将整个FAT加载到内存)。

软件开发搞定操作系统_第60张图片

3.索引分配:把文件的所有盘块集中存储(索引),读取某个文件时,将文件索引读取进内存即可。每个文件拥有一个索引块,记录所有盘块信息。索引分配方式支持直接访问盘块,文件较大时,索引分配方式具有明显优势。所以现在常用这个来分配辅存。

软件开发搞定操作系统_第61张图片

7.2.2 存储空间管理

存储空间管理就是磁盘的空间管理,比如记录磁盘哪里是空闲的、哪里是已经存放东西的。

软件开发搞定操作系统_第62张图片

1.空闲表:空闲盘区的分配与内存分配类似,比如首次适应算法、循环适应算法等,回收过程也与内存回收类似。如图:

软件开发搞定操作系统_第63张图片

2.空闲链表:空闲链表把所有空闲盘区组成一个空闲链表,每个链表节点存储空闲盘块和空闲的数目。分配和回收同上。

3.位示图:位示图维护成本很低,位示图可以非常容易找到空闲盘块,位示图使用0/1比特位,占用空间很小。主要使用这个,如图:

软件开发搞定操作系统_第64张图片

7.3 目录管理

软件开发搞定操作系统_第65张图片

目录树:任何文件或目录都只有唯一路径。

7.4 Linux文件

软件开发搞定操作系统_第66张图片
软件开发搞定操作系统_第67张图片
需要知道:在Linux中,一切皆为文件(进程也是文件,进程以数字命名,存放在/proc目录下)。

绝对路径:从根目录开始的路径,比如:/home/aa/b 这个就是绝对路径。

相对路径:相对于当前的文件路径,比如:…/aa(…/ 返回上一级目录),/aa/bb(当前目录中的aa文件夹中的bb文件)

关于文件操作的一些指令:

  • touch 文件名(绝对路径+文件名 也可以):创建一个文件
  • vim 文件名(绝对路径+文件名 也可以):vim命令可以创建并修改文件,当输入后回车就会跳到另一个黑框,然后按下insert键或者i键就可以在该框输入,如果写好了想保存退出,按下ESC键,然后输入 :wq。如果想查看行数,按下ESC键,然后输入 :set nu。
  • cat 文件名(绝对路径+文件名 也可以): 查看文件内容。
  • rm 文件名(绝对路径+文件名 也可以):删除文件。
  • mkdir 文件目录名(绝对路径+文件名 也可以):创建文件目录。
  • rm -r 文件目录名(绝对路径+文件名 也可以):递归删除文件目录中的文件包括文件目录。

文件类型:

软件开发搞定操作系统_第68张图片

使用指令:ll,来查看文件类型:第一个字母指的是文件类型,后面的到菜鸟教程看。

软件开发搞定操作系统_第69张图片

文件系统概述:当格式化U盘时就会出现以下几种格式化模式。

  1. FAT(File Allocation Table),FAT16、FAT32等,早期微软Dos/Windows使用的文件系统,它使用一张表保存盘块的信息。
  2. NTFS (New Technology File System),WindowsNT环境使用的文件系统,NTFS对FAT进行了改进,取代了旧的文件系统。win10、win7都可以使用。
  3. EXT(Extended file system):扩展文件系统,Linux上使用的文件系统,EXT2/3/4 数字表示第几代。Linux也支持FAT、NTFS等。但是EXT不能给Windows识别。

EXT文件系统,长下面这样:

软件开发搞定操作系统_第70张图片

每一个Block Group长下面这样:

软件开发搞定操作系统_第71张图片

  • Inode Table:存放文件Inode的地方每一个文件(目录)都有一个Inode,是每一个文件(目录)的索引节点。

  • Inode:存放的内容包含:索引节点编号、文件类型、文件权限、文件物理地址、文件长度、文件连接计数、文件存取时间、文件状态、访问计数等。文件名不是存放在Inode节点上的,而是存放在目录的Inode节点,为了列出目录文件的时候无需加载文件的Inode

  • Inode bitmap:Inode的位示图,记录已分配的Inode和未分配的Inode。

在这里插入图片描述

  • Data block:存放文件内容的地方,(中文被翻译成)每个block都有唯一的编号,文件的block记录在文件的Inode上。

  • Block bitmap:功能与Inode bitmap类似,记录Data block的使用情况。

  • Superblock:记录整个文件系统相关信息的地方,Block和Inode的使用情况、时间信息、控制信息等。

一些相关指令:(了解就行)

  • df -T:查看系统挂载磁盘的信息。

软件开发搞定操作系统_第72张图片

  • dumpe2fs (接上上面df -T查出来的Filesystem名称),比如查询那个ext4类型/dev/sda1的信息:dumpe2fs /dev/sda1:(如果权限不足,升级权限,使用sudo dumpe2fs /dev/sda1)输出很多信息

软件开发搞定操作系统_第73张图片

把它导下来:dumpe2fs /dev/sda1 > 文件名.log,然后使用vim查看

在这里插入图片描述

这里说一个vim中的查询,输入 /,然后加上你要搜索的字符,比如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PKGQkfCY-1587525418135)(概述/78.png)]

  • stat 文件名(包括后缀,也可以加绝对路径):Inode是一个文件唯一的编号。

软件开发搞定操作系统_第74张图片

  • mv oldFileName newFileName:重命名(如果有后缀,后缀也要写上)

8. 设备管理

8.1 广义的IO设备

广义的IO设备:指的是对CPU而言,凡是对CPU进行数据输入的都是输入设备,对CPU而言,凡是CPU进行数据输出的都是输出设备(比如CPU处理完的数据放到内存)。可以有以下几种分类:按使用特性分类、按设备的共享属性分类、按信息交换的单位分类、按传输速率分类。如图:

软件开发搞定操作系统_第75张图片
软件开发搞定操作系统_第76张图片
软件开发搞定操作系统_第77张图片
软件开发搞定操作系统_第78张图片

8.2 IO设备的缓冲区

组成原理中提到CPU与IO设备的速率不匹配,所以采用了存储层次(主存-辅存层次、主存-高速缓存层次)。而IO设备的缓冲区也是为了解决这个问题,减少CPU处理IO请求的频率,提高CPU与IO设备之间的并行性。

软件开发搞定操作系统_第79张图片

然而该缓冲区只属于特定的IO进程(专用缓冲区),所以当这样的IO进程比较多时,对内存的消耗也很大。因此操作系统划出可供多个进程使用的公共缓冲区,称之为缓冲池

软件开发搞定操作系统_第80张图片

当需要用到IO缓冲区时,就从缓存池取出,当使用完就把缓冲区归还到缓冲池。达到了多个进程共同使用缓冲区的要求,也减少了内存的消耗。

8.3 SPOOLing技术

SPOOLing技术:是关于慢速字符设备如何与计算机主机交换信息的一种技术(还是跟上面一样,解决CPU与IO设备速度不匹配的问题),利用高速共享设备将低速的独享设备模拟为高速的共享设备,逻辑上,系统为每一个用户都分配了一台独立的高速独享设备,也称为虚拟设备技术。

软件开发搞定操作系统_第81张图片

作业:在输入、输出之间增加了排队转储环节(输入井、输出井),SPOOLing负责输入(出)井与低速设备之间的调度,逻辑上,进程直接与高速设备交互,减少了进程的等待时间。

你可能感兴趣的:(操作系统)