操作系统
操作系统
操作系统(Operating System,OS)是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境,它是计算机系统中最基本的系统软件。
操作系统特征
-
并发
指两个或多个事件在同一时间间隔内发生。这些享件宏观上是同时发生的,但微观上是交替发生的。
-
共享
互斥共享:系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源。
例子:使用QQ和微信视频。同一时间段摄像头只能分配给其中一个。
同时共享:系统中的某些资源,允许一个时间段内由多个进程“同时”对它们进行访问(这个同时是宏观上的)。
例子:例子:使用QQ发送文件A,同时使用微信发送文件B。宏观上看两边同时都在读取并发送数据。微观上,两个进程是交替着访问硬盘的。
-
虚拟
概念:虚拟是指把一个物理上的实体变为若干个逻辑上的对应物。物理实体(前者)是实际存在的,而逻辑上对应物((后者)是用户感受到的。
虚拟技术:
空分复用技术 如:虚拟存储器技术):如:我们在电脑上打开多个软件,超过了电脑的内存(16GB),但这些软件仍可以在电脑上同时运行。
时分复用技术 (如:虚拟处理器):如一个单核CPU电脑可以打开多个程序
-
异步
概念:在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一管到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。
例如:点外卖,点完外卖之后去做其他事情,等外卖到了再去取外卖。在A事件的空闲期间,我们可以做B事件。当空闲期间结束后,我们再去执行A事件的后续操作。
操作系统的运行机制和体系结构
运行机制
两种指令:
- 特权指令:如:如内存清零指令
- 非特权指令:如:普通的运算指令
两种处理器状态:
- 核心态(管态):特权指令、非特权指令都可以执行
- 用户态(目态):此时CPU只能执行非特权指令
两种程序
- 内核程序:操作系统的内核程序是系统的管理者,既可以执行特权指令,也可以执行非特权指令,运行在核心态。
- 应用程序:为了保证系统能安全运行,普通应用程序只能执行非特权指令,运行在用户态
操作系统内核
- 时钟管理:操作系统的时钟管理是依靠
硬件定时器
的(具体硬件怎么实现我也不太清楚,好像是靠硬件周期性的产生一个脉冲信号实现的)。时钟管理相当重要,比如我们获取时间信息
,进程切换
等等都是要依靠时钟管理。 - 中断处理
- 原语:可以简单理解为用来实现某个特定功能,在执行过程中
不可被中断
的指令集合。原语有一个非常重要的特性,就是原子性(其运行一气呵成,不可中断
)。 - 对系统资源进行管理的功能:进程管理、存储器管理、设备管理等。
中断
-
在程序运行过程中,系统出现了一个必须由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操作、文件管理等),都必须
通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。
系统调用的分类:
需要注意的是,库函数
和系统调用
容易混淆。
- 库是可重用的模块
处于用户态
- 进程通过系统调用从用户态进入
内核态
, 库函数中有很大部分是对系统调用的封装
举个例子:比如windows
和linux
中,创建进程的系统调用方法是不一样的。 但在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使用完了,相应在内存上的数据也被删除了,那么
空闲的区域
,后面该怎么分配(也就是说随着进程退出,会有很多空闲的内存区域出现)
我们讲一种较为简单的处理方法叫空闲分区表
法来解决这个问题。如下图,右侧的表格就是一个空闲分区表。
当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配呢,例如下图,分别有20MB
,10MB
,4MB
三个空闲分区块,现在进程5
需要4MB
空闲分区,改怎么分配呢?
我们需要按照一定的动态分区分配算法,比如有首次适应算法
,指的是每次都从低地址开始查找,找到第一个能满足大小的空闲分区。还有比如最佳适应算法
,指的是从空闲分区表中找到最小的适合分配的分区块来满足需求。
连续分配缺点很明显
,大多数情况,需要分配的进程大小,不能跟空闲分区剩下的大小完全一样,这样就产生很多很难利用的内存碎片
。
这里我们介绍一种更好的空闲分区的分配方法,基本分页存储
。如下图
将内存空间分为一个个大小相等
的分区(比如:每个分区4KB
).每个分区就是一个“页框”
。页框号从0
开始。
将用户进程的地址空间分为与页框大小相等的一个个区域,称为“页”
。每个页也是从0
开始。
死锁
什么是僵尸进程
僵尸进程是已完成且处于终止状态,但在进程表中却仍然存在的进程。僵尸进程通常发生在父子关系的进程中,由于父进程仍需要读取其子进程的退出状态所造成的。
死锁产生的原因
死锁产生的原因大致有两个:资源竞争和程序执行顺序不当
死锁产生的必要条件
资源死锁可能出现的情况主要有
- 互斥条件:每个资源都被分配给了一个进程或者资源是可用的
- 保持和等待条件:已经获取资源的进程被认为能够获取新的资源
- 不可抢占条件:分配给一个进程的资源不能强制的从其他进程抢占资源,它只能由占有它的进程显示释放
- 循环等待:死锁发生时,系统中一定有两个或者两个以上的进程组成一个循环,循环中的每个进程都在等待下一个进程释放的资源。
死锁类型
- 两阶段加锁
- 通信死锁
- 活锁
- 饥饿锁
死锁的恢复方式
- 通过抢占进行恢复
- 通过回滚进行恢复
- 杀死进程恢复
破坏死锁
- 破坏互斥条件
- 破坏保持等待的条件
- 破坏不可抢占条件
- 破坏循环等待条件
文件管理
文件是什么?
文件就是一组有意义的信息/数据
集合。
文件的属性
文件名、标识符、类型、位置、大小、保护信息。
文件内部数据如何组织在一起
如下图,文件主要分为有结构文件
和无结构文件
。
文件之间如何组织起来
通过
树状结构
组织的。
文件的逻辑结构
逻辑结构是指,在用户看来,文件内部的数据是如何组织起来的,而“物理结构”
是在操作系统看来,文件是如何保存在外存,比如硬盘
中的。
1.顺序文件
什么是顺序文件
指的是文件中的记录一个接一个地在逻辑上是顺序排列
,记录可以是定长
或变长
,各个记录在物理上可以顺序存储
或链式存储
2.索引文件
3. 索引顺序文件
索引顺序文件是索引文件
和顺序文件
思想的结合。索引顺序文件中,同样会为文件建立一张索引表,但不同的是,并不是每个记录对应一个索引表项
,而是一组记录对应一个索引表项。
如上图,学生记录按照学生姓名的开头字母进行分组。每个分组就是一个顺序文件,分组内的记录不需要按关键字排序
文件目录
一个文件对应一个FCB,一个FCB就是一个目录项,多个FCB组成文件目录
文件目录的结构通常是树状的
- 需要注意的是,树状目录
不容易实现文件共享
,所以在树形目录结构的基础上,增加了一些指向同一节点的有向边(可以简单理解为引用关系,就跟js里的对象一样) - 也就是说需要为
每个共享节点
设置一个共享计数器
,用于记录此时有多少个地方在共享该结点。只有共享计数器减为0
,才删除该节点。
文件共享
文件共享分为两种
- 基于索引结点的共享方式(硬链接)
- 基于符号链的共享方式(软链接)
- 软连接可以理解为
windows
里的快捷方式
。 - 硬链接可以理解为js里的
引用计数
,只有引用为0
的时候,才会真正删除这个文件。
- 软连接可以理解为
文件保护
操作系统需要保护文件的安全,一般有如下3种方式:
- 口令保护。是指为文件设置一个
“口令”
(比如123),用户请求访问该文件时必须提供对应的口令。口令一般放在文件对应的FCB或者索引结点
上。 - 加密保护。使用某个
"密码"
对文件进行加密,在访问文件时需要提供正确的“密码”
才能对文件进行正确的解密。 - 访问控制。在每个文件的FCB或者索引节点种增加一个
访问控制列表
,该表中记录了各个用户可以对该文件执行哪些操作。
I/O设备
什么是I/O设备
I/O就是
输入输出
(Input/Output)的意思,计算机的外部设备,属于计算机中的硬件部件。
I/O设备分类--按使用特性
-
人机交互类设备,这类设备传输数据的速度慢。例:鼠标、键盘、打印机。
-
存储设备,这类设备传输数据的速度较快。例:移动硬盘、光盘等。
-
网络通信设备,这类设备的传输速度介于人机交互设备和存储设备之间
I/O控制器
CPU无法直接控制
I/O设备的机械部件
,因此I/O设备还要有一个电子部件作为CPU
和I/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下达的任务。