操作系统

操作系统

操作系统(Operating System,OS)是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境,它是计算机系统中最基本的系统软件。

操作系统特征

  1. 并发

    指两个或多个事件在同一时间间隔内发生。这些享件宏观上是同时发生的,但微观上是交替发生的。

  2. 共享

    互斥共享:系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源。

    例子:使用QQ和微信视频。同一时间段摄像头只能分配给其中一个。

    同时共享:系统中的某些资源,允许一个时间段内由多个进程“同时”对它们进行访问(这个同时是宏观上的)。

    例子:例子:使用QQ发送文件A,同时使用微信发送文件B。宏观上看两边同时都在读取并发送数据。微观上,两个进程是交替着访问硬盘的。

  3. 虚拟

    概念:虚拟是指把一个物理上的实体变为若干个逻辑上的对应物。物理实体(前者)是实际存在的,而逻辑上对应物((后者)是用户感受到的。

    虚拟技术

    空分复用技术 如:虚拟存储器技术):如:我们在电脑上打开多个软件,超过了电脑的内存(16GB),但这些软件仍可以在电脑上同时运行。

    时分复用技术 (如:虚拟处理器):如一个单核CPU电脑可以打开多个程序

  4. 异步

    概念:在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一管到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。

    例如:点外卖,点完外卖之后去做其他事情,等外卖到了再去取外卖。在A事件的空闲期间,我们可以做B事件。当空闲期间结束后,我们再去执行A事件的后续操作。

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

运行机制

两种指令:

  1. 特权指令:如:如内存清零指令
  2. 非特权指令:如:普通的运算指令

两种处理器状态:

  1. 核心态(管态):特权指令、非特权指令都可以执行
  2. 用户态(目态):此时CPU只能执行非特权指令

两种程序

  1. 内核程序:操作系统的内核程序是系统的管理者,既可以执行特权指令,也可以执行非特权指令,运行在核心态。
  2. 应用程序:为了保证系统能安全运行,普通应用程序只能执行非特权指令,运行在用户态

操作系统内核

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

中断

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

  • 操作系统发现中断的信号是第一个程序的时间片(每个程序不能一直执行,CPU会给每个程序一定的执行时间,这段时间就是时间片)用完了,应该换第二个应用程序执行了

  • 切换到第2个进程后,操作系统会将CPU使用权交换给第二个应用程序,接着第二个应用程序就在用户态下开始执行。

  • 进程2需要调用打印机资源,这时会执行一个系统调用(后面会讲系统调用,这里简单理解为需要操作系统进入核心态处理的函数),让操作系统进入核心态,去调用打印机资源

  • 打印机开始工作,此时进程2因为要等待打印机启动,操作系统就不等待了(等到打印机准备好了,再回来执行程序2),直接切换到第三个应用程序执行

  • 等到打印机准备好了,此时打印机通过I/O控制器会给操作系统发出一个中断信号,操作系统又进入到核心态,发现这个中断是因为程序2等待打印机资源,现在打印机准备好了,就切换到程序2,切换到用户态,把CPU给程序2继续执行。

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

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

中断的分类

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

假如你对象上课的时候突然异想天开,回过神来已经过好好长一段时间,这是内部中断。想着想着老师走过来,给了你对象一嘴巴,这是外部中断

官方解释如下:

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

系统调用

为什么需要系统调用?

  • 比如你的程序需要读取文件信息,可读取文件属于读取硬盘里的数据,这个操作应该是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')
}

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

为什么要引入进程的概念呢?

  • 早期的计算机只支持单道程序(是指所有进程一个一个排队执行,A进程执行时,CPU、内存、I/O设备全是A进程控制的,等A进程执行完了,才换B进程,然后对应的资源比如CPU、内存这些才能换B用)。

  • 现代计算机是多道程序执行,就是同时看起来有多个程序在一起执行,那每个程序执行都需要系统分配给它资源来执行,比如CPU内存

  • 拿内存来说,操作系统要知道给A程序分配的内存有哪些,给B程序分配的内存有哪些,这些都要有小本本记录下来,这个小本本就是进程的一部分,进程的一大职责就是记录目前程序运行的状态

  • 系统为每个运行的程序配置一个数据结构,称为进程控制块(PCB),用来描述进程的各种信息(比如代码段放在哪)。

进程的定义

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

比如启动QQ,这个程序运行过程的整体就是一个进程。

PCB有哪些组成

如下图

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

进程的状态

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

进程的三种基本状态:

进程的另外两种状态:

进程状态的转换

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

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

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

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

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

进程的通信

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

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

进程通信3种方法

共享存储

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

注:同一时刻只能有一个进程访问共享空间

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

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

管道

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

消息传递

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

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

直接通信:消息直接挂到接受进程的消息缓冲队列上

间接通信:消息要先发送到中间实体中,因此也称“信箱通信方式”。

线程

为什么要引入线程呢?

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

引入线程后带来的变化

进程的同步和互斥

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

例:进程B需要从缓冲区读取进程A产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。

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

例:进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。

信号量

信号量主要是来解决进程的同步互斥的。

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

/*记录型信号量的定义*/
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原语唤醒等待队列中的一个进程,该进程从阻塞态变为继续态。

生产者消费者问题

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

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

内存的基础知识和概念

为什么需要内存

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

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

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

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

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

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

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

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

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

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

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

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

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

死锁

什么是僵尸进程

僵尸进程是已完成且处于终止状态,但在进程表中却仍然存在的进程。僵尸进程通常发生在父子关系的进程中,由于父进程仍需要读取其子进程的退出状态所造成的。

死锁产生的原因

死锁产生的原因大致有两个:资源竞争和程序执行顺序不当

死锁产生的必要条件

资源死锁可能出现的情况主要有

  • 互斥条件:每个资源都被分配给了一个进程或者资源是可用的
  • 保持和等待条件:已经获取资源的进程被认为能够获取新的资源
  • 不可抢占条件:分配给一个进程的资源不能强制的从其他进程抢占资源,它只能由占有它的进程显示释放
  • 循环等待:死锁发生时,系统中一定有两个或者两个以上的进程组成一个循环,循环中的每个进程都在等待下一个进程释放的资源。

死锁类型

  1. 两阶段加锁
  2. 通信死锁
  3. 活锁
  4. 饥饿锁

死锁的恢复方式

  1. 通过抢占进行恢复
  2. 通过回滚进行恢复
  3. 杀死进程恢复

破坏死锁

  1. 破坏互斥条件
  2. 破坏保持等待的条件
  3. 破坏不可抢占条件
  4. 破坏循环等待条件

文件管理

文件是什么?

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

文件的属性

文件名、标识符、类型、位置、大小、保护信息。

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

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

文件之间如何组织起来

通过树状结构组织的。

文件的逻辑结构

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

1.顺序文件

什么是顺序文件

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

2.索引文件

3. 索引顺序文件

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

如上图,学生记录按照学生姓名的开头字母进行分组。每个分组就是一个顺序文件,分组内的记录不需要按关键字排序

文件目录

一个文件对应一个FCB,一个FCB就是一个目录项,多个FCB组成文件目录

文件目录的结构通常是树状的

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

文件共享

文件共享分为两种

  1. 基于索引结点的共享方式(硬链接)
  2. 基于符号链的共享方式(软链接)
    • 软连接可以理解为windows里的快捷方式
    • 硬链接可以理解为js里的引用计数,只有引用为0的时候,才会真正删除这个文件。

文件保护

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

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

I/O设备

什么是I/O设备

I/O就是输入输出(Input/Output)的意思,计算机的外部设备,属于计算机中的硬件部件。

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

  • 人机交互类设备,这类设备传输数据的速度慢。例:鼠标、键盘、打印机。

  • 存储设备,这类设备传输数据的速度较快。例:移动硬盘、光盘等。

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

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要读写的是哪个寄存器

I/O控制方式

  • 这里我们只讲一下目前比较先进的方式,通道控制方式。
  • 通道可以理解为一种“弱鸡版CPU”。通道可以识别并执行一系列通道指令。

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

posted @ 2021-08-23 22:30  贝贝子  阅读(340)  评论(1编辑  收藏  举报