阅读 9140

😀前端入门操作系统知识,这一篇就够啦!

此文针对非科班同学来补充程序猿必备的基础知识。

好了,开撸操作系统!

弱弱的问一问: 要操作系统干嘛?

这里先不讲操作系统的概念了,因为文字太生硬了,我们只需要看一个简单的例子:

  • 在我们的JS代码里,只需要输入 console.log(1+1); 就可以在浏览器面板中看到2,这其中发生了什么事情呢?(简单扫一眼)
  • 首先键盘输入代码1+1到显示器输出2, 需要CPU控制键盘(输入设备) ,将获取的1+1指令放入内存
  • 然后CPU的控制器从内存中取出指令,并分析出指令是让计算机做一个1+1的加法运算
  • 此时CPU的控制将控制CPU的运算器做1+1的加法运算,并得出结果2
  • 最后CPU控制器控制运算器将结果返给内存,内存也在CPU控制器的控制下,将结果2返回给屏幕(输出设备)

好了,这里问题是,如果没有操作系统,一个简单的1+1运算,你的js代码还需要考虑这些硬件的协调工作,比如你的代码要协调CPU资源什么时候读取你的代码,什么时候把进程切换到别的进程。。。这些脏活累活都是操作系统帮你屏蔽了,要不这代码可咋写啊。。。

弱弱的问一问: 前端学这个干嘛?

很早以前看朴零大神的《深入浅出NodeJS》的时候,讲到进程间通信,有一句大概说,windows平台进程间通信用的是管道,linux平台用的是domain socket,我一看就傻眼了,啥是进程间通信?啥是管道?啥是domain socket?😭 看不懂啊.... 这些都是跟操作系统进程的知识相关)。

啥也了不说了,兄弟,学习的小车已经粗发了!

1、操作系统的四个特征

有以下四个特征:

  • 并发
  • 共享
  • 虚拟
  • 异步

接下来,我们分别来搞定每一个特征。

1.1 并发是什么?和并行有啥区别?

举个例子,假如你在语音跟同学玩英雄联盟:

  • 你一边用鼠标移动打游戏,同时语音嘴里说"队友挂机,真坑!", 这叫并行(边移动鼠标边语音BB)
  • 你一边用鼠标移动打游戏,然后离开鼠标,去砸键盘, 这叫并发(先离开鼠标然后砸键盘)

并发只是把时间分成若干段,使多个任务交替的执行。 并行的关键是你有同时处理多个任务的能力。

  • 所以我认为它们最关键的点就是:是否是『同时』

那么对于操作系统而言,操作系统的并发性指计算机系统中同时存在多个运行着的程序

  • 比如说以前的计算机是单核CPU,那么如何在操作系统上同时运行QQ、浏览器,记事本、ppt等多个程序呢,这就需要操作系统具有并发性

  • CPU时间片(操作系统分配给每个正在运行的进程微观上的一段CPU时间)轮着给进程执行的时间,因为执行速度很快,看起来就像浏览器能同时执行任务一样。

  • 有人会说,现在都多核CPU了,还需要并发吗,答案肯定是需要的,比如你有8核CPU,但是桌面要执行的任务很可能超过8个。

1.2 共享是什么?共享和并发有什么关系?

举一个例子: 你同时用QQ和微信发"年终述职.ppt"文件给领导,这时候QQ和微信都在读取这个ppt文件

  • 两个进程正在并发执行(并发性)
  • 需要共享地访问硬盘资源(共享性) 如果没有并发,也就是只有一个进程在运行,那就没有共享了。如果没有共享,QQ和微信就不能同时发文件,无法同时访问硬盘资源,也就无法并发。

其中共享分为两种情况:

  • 上面的例子,QQ和微信都要访问同一个文件,属于同时共享
  • 对于互斥共享,比如打印机,只能同一时刻被一个进程控制,如打印机,虽然他可以提供多个进程使用,但是试想,同时打印多个东西,会造成打印结果的混乱,因此规定,某些资源在进行使用的时候,必须要先让某进程先使用,等使用完之后,再同一其他进程进行访问。
  • 我们把一段时间内只允许一个进程访问的资源称为独占资源,或临界资源

1.3 虚拟是啥?

先举例,再说定义。

假如一个叫范桶的货车司机在玩英雄联盟,平时因为酒驾太多,自己装了很多次别人的车,住院也花了不少钱,所以家里没钱,只能买个1G内存的二手电脑玩游戏。可英雄联盟至少需要2G内存,这就奇怪了,老司机虽然一到团战就卡死,但是还是能运行英雄联盟。为什么需要2G内存的游戏,1G电脑还能运行呢?

这就是虚拟存储器技术。实际上只有1G内存,在用户看来远远大于1G

还有,范桶的电脑还是单核的,但范桶居然能一边迅雷下着爱情动作片,一边听着网易云音乐,还在QQ上撩妹子,既然一个程序要被分配CPU才能正常执行,按道理来说同一时间只有1个程序在运行,为啥电脑能同时运行这么多程序呢?

这就是虚拟处理器技术。实际上只有一个CPU,在用户看来有3个CPU在同时服务。(因为CPU来回切换进程的速度特别块,感觉就像很多CPU在为我们服务)

虚拟这块的总结如下:

1.4 异步性是啥?

异步在JS里是很常见的,比如ajax请求,我们发出请求后并不是立马得到信息,也不会去等待ajax结果返回,而是继续执行下面的代码,等ajax结果回来,通知JS线程。这就跟CPU处理进程很类似。

比如,CPU正在执行一个进程,进程需要读取文件,读取文件可能要1个小时,那CPU不可能一直等一个小时,CPU会继续把时间片分给别的进程,等文件读取完成了(类似ajax返回结果了),CPU再继续执行之前被中断的进程。

所以异步性就是描述进程这种以不可预知的速度走走停停、何时开始何时暂停何时结束不可预知的性质。

2、操作系统运行机制和体系结构

预备知识: 什么是指令

比如说,如下图(简单扫一下即可):

a+b是一段程序代码,a+b在CPU看来并不能一步完成,可以翻译成如下:

// 意思是将内存的16号单元数据,放到A寄存器,
LOAD A, 16
// 意思是将内存的16号单元数据,放到B寄存器
LOAD B, 17
// 存器里的A,B数据相加,得到C
ADD C, A, B
复制代码

这里就可以看得出来,指令是CPU识别执行的最基本命令。

2.1 两种指令、两种处理器状态、两种程序

假如说一个用户可以随意把服务器上的所有文件删光,这是很危险的。所以有些指令普通用户是不能使用的,只能是权限较高的用户能使用。此时指令就分为了两种,如下图:

这就引出一个问题:CPU如何判断当前是否可以执行特权指令? 如下图:

CPU通常有两种工作模式即:内核态用户态,而在PSW(这个不用管,就知道有一个寄存器的标志位0表示用户态,1表示核心态)中有一个二进制位控制这两种模式。

对于应用程序而言,有的程序能执行特权指令,有的程序只能执行非特权指令。所以操作系统里的程序又分为两种:

2.2 操作系统内核简单介绍

从下图,我们先看看操作系统内核包含哪些

操作系统内核中跟硬件紧密相关的部分有:

  • 时钟管理。操作系统的时钟管理是依靠硬件定时器的(具体硬件怎么实现我也不太清楚,好像是靠硬件周期性的产生一个脉冲信号实现的)。时钟管理相当重要,比如我们获取时间信息进程切换等等都是要依靠时钟管理。
  • 中断处理(下一小节会详细介绍)。
  • 原语(后面会有案例提到)。现在可以简单理解为用来实现某个特定功能,在执行过程中不可被中断的指令集合。原语有一个非常重要的特性,就是原子性(其运行一气呵成,不可中断)。

2.3 中断

  • 在程序运行过程中,系统出现了一个必须由CPU立即处理的情况,此时,CPU暂时中止程序的执行转而处理这个新的情况的过程就叫做中断。 下面举一个例子:

第一个应用程序在用户态执行了一段时间后
接着操作系统切换到核心态,处理中断信号

  • 操作系统发现中断的信号是第一个程序的时间片(每个程序不能一直执行,CPU会给每个程序一定的执行时间,这段时间就是时间片)用完了,应该换第二个应用程序执行了
  • 切换到第2个进程后,操作系统会将CPU使用权交换给第二个应用程序,接着第二个应用程序就在用户态下开始执行。
  • 进程2需要调用打印机资源,这时会执行一个系统调用(后面会讲系统调用,这里简单理解为需要操作系统进入核心态处理的函数),让操作系统进入核心态,去调用打印机资源
  • 打印机开始工作,此时进程2因为要等待打印机启动,操作系统就不等待了(等到打印机准备好了,再回来执行程序2),直接切换到第三个应用程序执行
  • 等到打印机准备好了,此时打印机通过I/O控制器会给操作系统发出一个中断信号,操作系统又进入到核心态,发现这个中断是因为程序2等待打印机资源,现在打印机准备好了,就切换到程序2,切换到用户态,把CPU给程序2继续执行。

好了,现在可以给出一个结论,就是用户态、核心态之间的切换是怎么实现的?

  • "用户态 ---> 核心态"是通过中断实现的。并且中断时唯一途径
  • "核心态 ---> 用户态"的切换时通过执行一个特权指令,将程序状态的标志位设为用户态。

2.4 中断的分类

举一个例子,什么是内中断和外中断:

接着说之前的范桶同学,小时候不爱学习,每次学习着学习着突然异想天开,回过神来已经过好好长一段时间,这是内部中断。想着想着老师走过来,给了范捅一嘴巴,这是外部中断

官方解释如下:

  • 内中断常见的情况如程序非法操作(比如你要拿的的数据的内存地址不是内存地址,是系统无法识别的地址),地址越界(比如系统给你的程序分配了一些内存,但是你访问的时候超出了你应该访问的内存范围)、浮点溢出(比如系统只能表示1.1到5.1的范围,你输入一个100, 超出了计算机能处理的范围),或者异常陷入trap(是指应用程序请求系统调用造成的,什么是系统调用,后面小节会举例讲)。
  • 外中断常见的情况如I/O中断(由I/O控制器产生,用于发送信号通知操作完成等信号,比如进程需要请求打印机资源,打印机有一个启动准备的过程,准备好了就会给CPU一个I/O中断,告诉它已经准备好了)、时钟中断(由处理器内部的计时器产生,允许操作系统以一定规程执行函数,操作系统每过大约15ms会进行一次线程调度,就是利用时钟中断来实现的)。

2.5 系统调用

为什么需要系统调用?

  • 比如你的程序需要读取文件信息,可读取文件属于读取硬盘里的数据,这个操作应该时CPU在内核态去完成的,我们的应用程序怎么让CPU去帮助我们切换到内核态完成这个工作呢,这里就需要系统调用了

  • 这里就引出系统调用的概念和作用。

  • 应用程序通过系统调用请求操作系统的服务。系统中的各种共享资源都由操作系统统一管理,因此在用户程序中,凡是与资源有关的操作(如存储分配、I/O操作、文件管理等),都必须通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。

以下内容简单看一下即可,系统调用的分类:

需要注意的是,库函数系统调用容易混淆。

  • 库是可重用的模块 处于用户态
  • 进程通过系统调用从用户态进入内核态, 库函数中有很大部分是对系统调用的封装

举个例子:比如windowslinux中,创建进程的系统调用方法是不一样的。 但在node中的只需要调用相同函数方法就可以创建一个进程。例如

// 引入创建子进程的模块
const childProcess = require('child_process')
// 获取cpu的数量
const cpuNum = require('os').cpus().length

// 创建与cpu数量一样的子进程
for (let i = 0; i < cpuNum; ++i) {
  childProcess.fork('./worker.js')
}
复制代码

2.6 进程的定义、组成、组织方式、状态与转换

2.6.1 为什么要引入进程的概念呢?
  • 早期的计算机只支持单道程序(是指所有进程一个一个排队执行,A进程执行时,CPU、内存、I/O设备全是A进程控制的,等A进程执行完了,才换B进程,然后对应的资源比如CPU、内存这些才能换B用)。
  • 现代计算机是多道程序执行,就是同时看起来有多个程序在一起执行,那每个程序执行都需要系统分配给它资源来执行,比如CPU内存
  • 拿内存来说,操作系统要知道给A程序分配的内存有哪些,给B程序分配的内存有哪些,这些都要有小本本记录下来,这个小本本就是进程的一部分,进程的一大职责就是记录目前程序运行的状态
  • 系统为每个运行的程序配置一个数据结构,称为进程控制块(PCB),用来描述进程的各种信息(比如代码段放在哪)。
2.6.2 进程的定义?

简要的说,进程就是具有独立功能的程序在数据集合上运行的过程。(强调动态性)

2.6.3 PCB有哪些组成

如下图,分别说明一下

  • 进程标识符PID相当于身份证。是在进程被创建时,操作系统会为该进程分配一个唯一的、不重复的ID,用于区分不同的进程
  • 用户标识符UID用来表示这个进程所属的用户是谁。
  • 进程当前状态和优先级下一小节会详细介绍
  • 程序段指针是指当前进程的程序在内存的什么地方
  • 数据段指针是指当前进程的数据在内存的什么地方
  • 键盘和鼠标是指进程被分配得到的I/O设备
  • 各种寄存器值是指比如把程序计数器的值,比如有些计算的结果算到一半,进程切换时需要把这些值保存下来。
2.6.4 进程的组织

在一个系统中,通常由数十、数百乃至数千个PCB。为了对他们加以有效的管理,应该用适当的方式把这些PCB组织起来。这里介绍一种组织方式,类似数据结构里的链表。

2.6.5 进程的状态

进程是程序的一次执行。在这个执行过程中,有时进程正在被CPU处理,有时又需要等待CPU服务,可见,进程的 状态是会有各种变化。为了方便对各个进程的管理,操作系统需要将进程合理地划分为几种状态。

进程的三种基本状态:

进程的另外两种状态:

2.6.6 进程状态的转换

进程的状态并不是一成不变的,在一定情况下会动态转换。

以上的这些进程状态的转换是如何实现的呢,这就要引出下一个角色了,叫`原语。

  • 原语是不可被中断的原子操作。我们举一个例子看看原语是怎么保证不可中断的。

原语采用关中断指令开中断指令实现。

  • 首先执行关中断指令
  • 然后外部来了中断信号,不予以处理
  • 等到开中断指令执行后,其他中断信号才有机会处理。

2.6 进程的通信

为什么需要进程间通信呢?

因为进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。

2.6.1 进程通信方法---共享存储

因为两个进程的存储空间不能相互访问,所以操作系统就提供的一个内存空间让彼此都能访问,这就是共享存储的原理。

其中,介绍一下基于存储区的共享。

  • 在内存中画出一块共享存储区,数据的形式、存放位置都是由进程控制,而不是操作系统。
2.6.2 进程通信方法---管道

  • 管道数据是以字符流(注意不是字节流)的形式写入管道,当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走。当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将被阻塞。
  • 如果没写满就不允许读。如果都没空就不允许写。
  • 数据一旦被读出,就从管道中被丢弃,这就意味着读进程最多只能有一个。
2.6.3 进程通信方法---消息传递

进程间的数据交换以格式化的消息为单位。进程通过操作系统提供的"发送消息/接收消息"两个原语进行数据交换。

其中消息是什么意思呢?就好像你发QQ消息,消息头的来源是你,消息体是你发的内容。如下图:

接下来我们介绍一种间接通信的方式(很像中介者模式或者发布订阅模式), 如下图:中介者是信箱,进程通过它来收发消息。

2.7 线程

为什么要引入线程呢?

  • 比如你在玩QQ的时候,QQ是一个进程,如果QQ的进程里没有多线程并发,那么QQ进程就只能同一时间做一件事情(比如QQ打字聊天)
  • 但是我们真实的场景是QQ聊天的同时,还可以发文件,还可以视频聊天,这说明如果QQ没有多线程并发能力,QQ能够的实用性就大大降低了。所以我们需要线程,也就是需要进程拥有能够并发多个事件的能力。

引入线程后带来的变化

3 进程的同步和互斥

同步。是指多个进程中发生的事件存在某种先后顺序。即某些进程的执行必须先于另一些进程。

比如说进程A需要从缓冲区读取进程B产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。概念如图1所示。

互斥。是指多个进程不允许同时使用同一资源。当某个进程使用某种资源的时候,其他进程必须等待。

比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。概念如图3所示。

3.1 信号量(了解概念即可)

信号量主要是来解决进程的同步互斥的,我们前端需要了解,如果涉及到同步和互斥的关系(我们编程大多数关于流程的逻辑问题,本质不就是同步和互斥吗?)

在操作系统中,常用P、V信号量来实现进程间的同步互斥,我们简单了解一下一种常用的信号量,记录型信号量来简单了解一下信号量本质是怎样的。(c语言来表示,会有备注)

/*记录型信号量的定义*/
typedef struct {
    int value; // 剩余资源
    Struct process *L // 等待队列
} semaphore
复制代码

意思是信号量的结构有两部分组成,一部分是剩余资源value,比如目前有两台打印机空闲,那么剩余资源就是2,谁正在使用打印机,剩余资源就减1。

Struct process *L意思是,比如2台打印机都有人在用,这时候你的要用打印机,此时会把这个打印机资源的请求放入阻塞队列,L就是阻塞队列的地址。

/*P 操作,也就是记录型信号量的请求资源操作*/
void wait (semaphore S) {
    S.value--;
    if (S.value < 0){
        block (S.L);
    }
}
复制代码

需要注意的是,如果剩余资源数不够,使用block原语使进程从运行态进入阻塞态,并把挂到信号量S的等待队列中。

/*V 操作,也就是记录型信号量的释放资源操作*/
void singal (semaphore S) {
    S.value++;
    if (S.value <= 0){
        wakeup (S.L);
    }
}
复制代码

释放资源后,若还有别的进程在等待这个资源,比如打印机资源,则使用wakeup原语唤醒等待队列中的一个进程,该进程从阻塞态变为继续态。

3.2 生产者消费者问题(了解概念即可)

为什么要讲这个呢,主要是node的流的机制,本质就是生产者消费者问题,可以简单的看看这个问题如何解决。

如上图,生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

这里我们需要两个同步信号量和一个互斥信号量

// 互斥信号量,实现对缓冲区的互斥访问
semaphore mutex = 1;
// 同步信号量,表示目前还可以生产几个产品
semaphore empty = n;
// 同步信号量,表示目前可以消耗几个产品
semaphore full = 0;
复制代码

生产者代码如下

producer () {
    while(1) {
        // 生产一个产品
        P(empty);
        // 对缓冲区加锁
        P(mutex);
        这里的代码是生产一个产品
        // 解锁
        V(mutex);
        // 产出一个产品
        V(full);
    }
}

复制代码

消费者代码如下

producer () {
    while(1) {
        // 消费一个产品
        P(full);
        // 对缓冲区加锁
        P(mutex);
        这里的代码是消费一个产品
        // 解锁
        V(mutex);
        // 消费一个产品
        V(empty);
    }
}

复制代码

4 内存的基础知识和概念

为什么需要内存

内存是计算机其它硬件设备与``CPU沟通`的桥梁、中转站。程序执行前需要先放到内存中才能被CPU处理。

4.1 cpu如何区分执行程序的数据在内存的什么地方

  • 是通过给内存的存储单元编址实现的。(存储单元一般是以字节为单位)
  • 如下图,内存的存储单元,就像一个酒店的房间,都有编号,比如程序一的数据都在1楼,1楼1号存储着程序里let a = 1这段代码。

4.2 内存管理-内存空间的分配与回收

  • 内存分配分为连续分配非连续分配,连续分配是指用户进程分配的必须是一个连续的内存空间

  • 这里我们只讲连续分配中的动态分区分配

  • 什么是动态分区分配呢,这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。(比如,某计算机内存大小64MB,系统区8MB,用户区56MB...,现在我们有几个进程要装入内存,如下图)

  • 随之而来的问题就是,如果此时进程1使用完了,相应在内存上的数据也被删除了,那么空闲的区域,后面该怎么分配(也就是说随着进程退出,会有很多空闲的内存区域出现)

我们讲一种较为简单的处理方法叫空闲分区表法来解决这个问题。如下图,右侧的表格就是一个空闲分区表。

当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配呢,例如下图,分别有20MB10MB4MB三个空闲分区块,现在进程5需要4MB空闲分区,改怎么分配呢?

我们需要按照一定的动态分区分配算法,比如有首次适应算法,指的是每次都从低地址开始查找,找到第一个能满足大小的空闲分区。还有比如最佳适应算法,指的是从空闲分区表中找到最小的适合分配的分区块来满足需求。

连续分配缺点很明显,大多数情况,需要分配的进程大小,不能跟空闲分区剩下的大小完全一样,这样就产生很多很难利用的内存碎片

这里我们介绍一种更好的空闲分区的分配方法,基本分页存储。如下图

将内存空间分为一个个大小相等的分区(比如:每个分区4KB).每个分区就是一个“页框”。页框号从0开始。

将用户进程的地址空间分为与页框大小相等的一个个区域,称为“页”。每个页也是从0开始。

进程的页与内存的页框有着一一对应的关系。各个页不必连续存放,也不必按先后顺序来,可以放到不相邻的各个页框中。

5 文件管理

文件是什么?

文件就是一组有意义的信息/数据集合。

计算机中存放了各种各样的文件,一个文件有哪些属性呢?文件内部的数据应该怎样组织起来?文件之间又该怎么组织起来?

5.1 文件的属性

  • 文件名。即文件的名字,需要注意的是,同一目录下不允许有重名的文件。
  • 标识符。操作系统用于区分各个文件的一种内部的名称
  • 类型。文件的类型。
  • 位置。文件存放的路径,同时也是在硬盘里的位置(需要转换成物理硬盘上的地址)
  • 创建时间、上次修改时间、文件所有者就是字面意思。
  • 保护信息。比如对这个文件的执行权限,是否有删除文件权限,修改文件权限等等。

5.2 文件内部数据如何组织在一起

如下图,文件主要分为有结构文件无结构文件

5.3 文件之间如何组织起来

通过树状结构组织的。例如windows的文件间的组织关系如下:

接下来我们详细的了解一下文件的逻辑结构

5.4 文件的逻辑结构

逻辑结构是指,在用户看来,文件内部的数据是如何组织起来的,而“物理结构”是在操作系统看来,文件是如何保存在外存,比如硬盘中的。

比如,“线性表”就是一种逻辑结构,在用户看来,线性表就是一组有先后关系的元素序列,如:a,b,c,d,e....

  • “线性表”这种逻辑结构可以用不同的物理结构实现,比如:顺序表/链表顺序表的各个元素在逻辑上相邻,在物理上也相邻:而链表的各个元素在物理上可以是不相邻的。
  • 因此,顺序表可以实现“随机访问”,而“链表”无法实现随机访问。

接下来我了解一下有结构文件的三种逻辑结构

5.4.1 顺序文件

什么是顺序文件

指的是文件中的记录一个接一个地在逻辑上是顺序排列,记录可以是定长变长,各个记录在物理上可以顺序存储链式存储

  • 顺序文件按结构来划分,可以分为串结构顺序结构
  • 串结构是指记录之间的顺序与关键字无关,通常都是按照记录的时间决定记录的顺序。
  • 顺序结构就必须保证记录之间的先后顺序按关键字排列

这里需要注意的知识点是,顺序文件的存储方式和是否按关键字排列,会影响数据是否支持随机存取是否可以快速按关键字找到对应记录的功能。

5.4.2 索引文件

对于可变长记录文件,要找到第i个记录,必须先顺序查找前i-1个记录,但是很多场景中又必须使用可变长记录,如何解决这个问题呢?这就引出来马上讲的索引文件

  • 给这些变长的记录都用一张索引表来记录,一个索引表项包括了索引号长度指针

  • 其中,可以将关键字作为索引号的内容,如果关键字本身的排列是有序的,那么还可以按照关键字进行折半查找。

  • 但是建立索引表的问题也很明显,首先若要删除/增加一个记录,同时也要对索引表操作,其次,如果增加一条记录才1KB,但是索引表增加i一条记录可能有8KB,以至于索引表的体积大大多于记录。存储空间的利用率就比较低。

5.4.3 索引顺序文件

索引顺序文件是索引文件顺序文件思想的结合。索引顺序文件中,同样会为文件建立一张索引表,但不同的是,并不是每个记录对应一个索引表项,而是一组记录对应一个索引表项。

5.5 文件目录

首先,我们需要了解一下文件控制块是什么。我们假设目前在windows的D盘,如下图

可以看到,目录本身就是一种有结构的文件,记录了目录里的文件目录的信息,比如名称和类型。而这些一条条的记录就是一个个的“文件控制块”(FCB)

文件目录的结构通常是树状的,例如linux里/是指根路径,/home是根路径下的二级目录

  • 需要注意的是,树状目录不容易实现文件共享,所以在树形目录结构的基础上,增加了一些指向同一节点的有向边(可以简单理解为引用关系,就跟js里的对象一样)
  • 也就是说需要为每个共享节点设置一个共享计数器,用于记录此时有多少个地方在共享该结点。只有共享计数器减为0,才删除该节点。

5.6 文件存储空间管理

首先,我们了解一下磁盘分为目录区文件区

接着,我们了解一下常见的两种文件存储空间的管理算法,如下图,假如硬盘上空闲的数据块是蓝色,非空闲的数据块是橙色。

对分配连续的存储空间,可以采用空闲表法(只讲这种较简单的方法)来分配回收磁盘块。对于分配,可以采用首次适应,最佳适应等算法来决定要为文件分配哪个区间。(空闲表表示如下)

  • 首次适应是指当要插入数据的时候,空闲表会依次检查空闲表中的表项,然后找到第一个满足条件的空闲区间。

  • 最佳适应算法是指当要插入数据的时候,空闲表会依次检查空闲表中的表项,然后找到满足条件而且空闲块最小的空闲区间

如何回收磁盘块呢,主要分为以下4中情况

  • 回收区的前后没有相邻空闲区
  • 回收区前后都是空闲区
  • 回收区前面是空前去
  • 回收区后面是空闲区

最重要的是要注意表项合并的问题。(比如说回收区前后都有空闲区就将其一起合并为一个空闲区)

5.7 文件共享

文件共享分为两种

  • 注意,多个用户共享同一个文件,意味着系统只有“一份”文件数据。并且只要某个用户修改了该文件的数据,其他用户也可以看到文件的变化

  • 软连接可以理解为windows里的快捷方式

  • 硬链接可以理解为js里的引用计数,只有引用为0的时候,才会真正删除这个文件。

5.8 文件保护

操作系统需要保护文件的安全,一般有如下3种方式:

  • 口令保护。是指为文件设置一个“口令”(比如123),用户请求访问该文件时必须提供对应的口令。口令一般放在文件对应的FCB或者索引结点上。
  • 加密保护。使用某个"密码"对文件进行加密,在访问文件时需要提供正确的“密码”才能对文件进行正确的解密。
  • 访问控制。在每个文件的FCB或者索引节点种增加一个访问控制列表,该表中记录了各个用户可以对该文件执行哪些操作。

6 I/O设备

什么是I/O设备

I/O就是输入输出(Input/Output)的意思,I/O设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部件。

6.1 I/O设备分类--按使用特性

  • 人机交互类设备,这类设备传输数据的速度慢

  • 存储设备,这类设备传输数据的速度较快

  • 网络通信设备,这类设备的传输速度介于人机交互设备和存储设备之间

6.2 I/O控制器

CPU无法直接控制I/O设备的机械部件,因此I/O设备还要有一个电子部件作为CPUI/O设备机械部件之间的“中介”,用于实现CPU对设备的控制。这个电子部件就是I/O控制器

  • 接收和识别CPU发出的指令是指,比如CPU发来读取文件的命令,I/O控制器中会有相应的控制寄存器来存放命令和参数
  • 向cpu报告设备的状态是指,I/O控制器会有相应的状态寄存器,用来记录I/O设备是否空闲或者忙碌
  • 数据交换是指I/O控制器会设置相应的数据寄存器。输出时,数据寄存器用于暂存CPU发来的数据,之后再由控制器传送给设备。
  • 地址识别是指,为了区分设备控制器中的各个寄存器中的各个寄存器,也需要给各个寄存器设置一个特性的“地址”。I/O控制器通过CPU提供的“地址”来判断CPU要读写的是哪个寄存器

6.3 I/O控制方式

  • 这里我们指讲一下目前比较先进的方式,通道控制方式。

  • 通道可以理解为一种“弱鸡版CPU”。通道可以识别并执行一系列通道指令。

通道最大的优点是极大的减少了CPU的干预频率I/O设备完成任务,通道会向CPU发出中断,不需要轮询来问I/O设备是否完成CPU下达的任务。

本文完结。

预告:后面会有数据结构入门知识(常用的数据结构以及在内存的存储形式,并对比其增删改查的时间复杂度)

注: 本文绝大多数资料来源于以下的学习视频资料

关注下面的标签,发现更多相似文章
评论