计算机操作系统——知识结构体系

参考:
https://www.yuque.com/milesgo/lxvcbh/rh61f4
https://www.bilibili.com/read/cv12148392?from=note

计算机的理解

答:计算机从总体上分为三个模块。
一个是纯硬件,包含CPU、内存、磁盘、输入输出设备(键盘、鼠标和显示器)等。
一个是纯软件,是我们常接触的应用程序,包含QQ、word、浏览器、视频播放器等。
一个是操作系统,负责让我们的应用程序可以合理有序并高效的用到计算机硬件。

计算机组成原理侧重纯硬件,主要讲解各个硬件设备的物理结构和工作原理。
操作系统主要围绕上层应用程序如何合理调度使用这些硬件资源,以充分发挥计算机的最大性能。
因此,计算机组成原理是学习操作系统的必备知识。如果想深入了解操作系统各个模块的实现,汇编语言也是必备知识。

操作系统

操作系统是计算机硬件和应用软件之间的一层软件,有两个功能。
对上为应用软件提供接口对下管理计算机硬件资源,主要包括CPU管理、内存管理、IO设备管理(终端管理、磁盘管理、文件管理)

注:计算机的工作原理是,取指执行(指令和数据都要放到内存中)

一、操作系统的启动

第一步:计算机开机后,执行固化在内存中的BIOS代码,计算机开始取指执行。
第二步:将操作系统从磁盘读入到内存(读入操作系统的引导扇区boot)
第三步:进行初始化工作(setup模块,system模块),之后上层软件便可以通过操作系统,来使用计算机硬件了。

二、操作系统的接口

1.什么是操作系统的接口?(重点是程序接口:系统调用)

答:

接口可以分为用户接口(命令接口和程序接口)和图形用户界面
在这里插入图片描述

1)用户接口(命令接口+程序接口)

答:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述操作系统的接口就是一些重要的函数,称为系统调用,它是操作系统提供给上层应用的接口。
上层应用软件的命令编译后变成可执行程序,这些程序中包含一些重要函数(对操作系统接口的调用),从而实现上层应用软件和操作系统的联系。

	常见的系统调用:fork和execl可以实现进程管理,调用了CPU;  scanf可以实现从键盘读入信息,调用了键盘输入;printf可以调用显示器。

2)图形用户界面

答:
在这里插入图片描述

2.如何实现操作系统的接口?

1)上层应用软件能直接访问操作系统(内核)吗?

答:
虽然应用软件和操作系统都放在内存中,但对于应用软件上的一些操作,不能从内存上直接跳转到操作系统所在位置,需要通过系统调用。否则这样会造成上层软件对操作系统使用权限过大,操作系统中的数据不安全,如密码泄露等。

	(上层应用不能直接访问内核(操作系统)的内存)

2)上层软件是如何通过操作系统使用到计算机硬件资源呢?

答:
首先,将内存分为内核段和用户段,使用硬件实现用户程序和内核程序的隔离
在这里插入图片描述

其次,上层应用进入内核是通过中断int 0x80,从而让上层软件通过操作系统使用到计算机硬件资源。
在这里插入图片描述

3)用户程序使用“系统调用”的细节?


1.用户程序包含一段中断指令
2.通过中断指令int 0x80进入操作系统
3.操作系统获取要调用的程序编号()
4.操作系统根据编号找到系统调用函数的实现体,并执行相应代码

(系统调用编号是用户程序给的,表示用户想要调用的函数,如write,printf,fork,这些重要函数(系统调用)实现体在内核中)

用户程序使用系统调用的过程:
在这里插入图片描述

在这里插入图片描述

三、操作系统管理计算机硬件

答:
操作系统的主要任务是管理计算机硬件。
多进程管理(CPU与内存),操作系统在管理CPU时,引出多进程概念。
文件管理(IO、磁盘、文件)
在这里插入图片描述
在这里插入图片描述

1.操作系统如何管理CPU

答:

1)进程是什么?多进程又是什么?为什么要多进程?

答:
在理解操作系统如何管理CPU之前,我们需要明白两个问题?一是首先明白CPU如何工作的?二是我们如何充分利用CPU

CPU的工作原理,简单说就是四个字,“取指执行”,就是让程序执行起来。

那如何充分利用CPU呢?就是启动多个程序,交替执行,不要让CPU停下来(一直让CPU保持计算和指令控制等)。

CPU为什么会停下来?
CPU的计算和CPU访问IO设备,包括内存,两者时间差距巨大,容易在CPU访问IO时,造成CPU不工作,效率低下。因此需要解决这个问题。
我们可以在CPU做一件事时,当它因为访问IO造成CPU不计算时,我们可以让CPU去处理另一件事

在这里插入图片描述
通过上面两个问题的回答,引出两个重要概念:
进程:就是正在运行的程序。
并发:CPU在一段时间内,交替执行多个程序。即在一段时间内来看,CPU是同时执行了多个程序。
在这里插入图片描述
总结:
进程是什么?——正在运行的程序
多进程是什么?——一段时间内,CPU同时运行多个程序(本质是交替执行)
为什么要多进程?——提供CPU的利用率

Q:CPU进行多进程工作时,操作系统需要做什么?

答:
操作系统管理CPU,本质上就是管理多进程。
当CPU执行多个程序时,操作系统只需要把这些进程记录好、要按照合理的次序推进(分配资源、进行调度),从而提高CPU工作效率。

查看多进程
打开任务管理器即可查看每个进程,其CPU的使用情况也可以查看。任务管理器本身也是一个进程。
linux中的shell、Windows中的桌面是第一个进程。然后利用它打开其他进程。
在这里插入图片描述

2)多进程如何被推进?

答:

PCB+状态+队列:用PCB记录各个进程信息,并把PCB放到队列中,利用状态来推进各个进程。

在这里插入图片描述

在这里插入图片描述

3)多进程如何交替执行?(大概过程)

答:

队列操作+调度+切换
在这里插入图片描述
操作系统完成进程调度与切换总结:
schedule()找到下一个进程(调度);
CPU当前信息保存到PCBcur,PCBnew的信息恢复到CPU。(切换)

调度:调度有很多算法,FIFO是最基本的一个,即先来先服务。

切换
调度找到下一个占用CPU的进程后,就要进行切换。这个过程需要精细控制(寄存器操作),所以需要用汇编代码来实现。
● 将当前CPU的各种信息(寄存器等)保存到pCur中
● 将pNew中的寄存器等信息恢复到CPU中
在这里插入图片描述

Q1:多个进程同时存在会相互影响,例如会发生内存地址的冲突,该如何解决?

答:
操作系统和用户进程需要隔离,用户进程之间的内存也需要隔离。一个进程原则上不能随意访问另一个进程的内存。
(多进程的地址空间分离:多进程需要完成对内存的管理,内存管理的主要内容)
在这里插入图片描述
解决办法:内存映射
○ 两个进程的100内存地址,是虚拟内存地址,会映射到不同的物理内存
○ 下图中展示了两个进程的100地址分别映射到了物理地址780和1260
在这里插入图片描述

Q2:多进程为什么需要合作?如何合作?

答:
因为不同进程需要共享计算机的资源,因此需要合作。
在这里插入图片描述
多进程对共享数据的操作会引发问题
● 例如counter如果不加以保护,其最终结果很可能不符合预期
● 生产者进程的counter++和消费者进程的counter–分别对应3条指令,如下图中间所示
● 下图右侧展示了一种可能的引发错误的指令执行序列,预期结果是5,实际结果是4
在这里插入图片描述
解决办法:锁机制
■ 所以多进程合作,需要注意共享数据的正确性,核心在于进程同步
● 例如在一个进程写counter时,需要阻断其他进程访问counter
● 下图展示了一种锁机制,用于保护共享数据counter
在这里插入图片描述
在这里插入图片描述

注:后面根据多进程工作涉及到的“PCB、切换、调度、进程合作、内存映射”,每个部分进行详细介绍。

4)多进程如何交替执行?(具体过程)

重点1:如何实现进程之间的切换?(具体)

答:
先讲线程切换(不带映射表),再将进程之间的切换(带映射表)

Q1:为什么讲进程切换,要讲线程呢?

答:
进程=资源+指令执行序列
资源包括内存映射表等。是否可以资源不变,而只切换指令执行序列呢?实现了多段程序交替执行,而且不需要切换资源这种消耗时间的操作。实质就是映射表不变而PC指针变,由此引入线程的概念。

1个进程=1个资源+多个指令执行序列(即多个线程)

只切换指令执行序列,而不切换资源。 保留了并发的优点,避免了进程切换代价,这就是线程之间的切换。

(线程切换也是进程切换的一部分,学习线程的切换也就是在学习进程切换的一个部分)
在这里插入图片描述

Q2:线程的这种设计是否实用?

答:
多进程带来的内存地址冲突问题,但在有些情况下,实际上多段代码执行序列是需要合作与共享资源的,使用线程技术就很方便。
( 以网页浏览器举例,为了用户体验,需要多段程序交替执行,但是这些程序又需要共享资源。)
在这里插入图片描述

Q3:用户级线程如何工作?

答:

以网页浏览器举例,实现浏览器的核心骨干伪代码,这里面涉及两个线程的切换

GetData用于从服务器获取数据
Show用于展示数据
在这里插入图片描述
当用“多段指令序列+1个栈”会有什么问题?
答:

由于两个线程共用了一个栈,线程切换后地址返回错误。这里的栈,指的是保存返回地址的栈 。
在这里插入图片描述
解决方案:每段指令序列都配备1个栈,引出TCB(Thread Control Block)
在这里插入图片描述
** 为什么说上面的内容是用户级线程?有什么缺点?

答:
○ 因为yeild是用户程序,完全没有进入内核,操作系统完全感知不到这种线程的存在,但是
yeild实现了线程切换**。
○ 缺点举例:在浏览器获取数据的函数GetData中,等待网络IO阻塞(这是内核级别的阻塞),因为操作系统完全不知道还有用户级线程的存在,所以不会线程切换到展示数据的函数Show,而是直接在内核中进行了进程的调度与切换(切换到了浏览器进程之外的别的进程),
如下图所示
在这里插入图片描述
由于用户级线程的缺点,从而引出核心级线程的概念:
○ 核心级线程的ThreadCreate和用户级线程的ThreadCreate是两个不同的函数
○ 核心级线程的ThreadCreate是系统调用,会进入内核
○ 内核级线程不再能由用户主动yeild,而是由操作系统自动调度

Q4:为什么要有核心级线程?

答:
多核系统必须要实现内核级线程,因为只有进入到内核中,才可以将线程分配到不同的核心(运算部件)上。
如果没有内核级线程,只有用户级线程,那么操作系统内核就无法感知这些线程,也就无法把这些线程分配到多个核上,多核就失去了意义在这里插入图片描述

Q5:核心级线程怎么工作?

答:
用户级线程两个栈,内核级线程两套栈

○ 从用户态进入内核态时(INT中断指令),需要在内核栈中先依次压入源SS、源SP、EFLAGS、源PC、源CS等内容
■ 源SS和源SP是指向用户栈的指针,也就是说内核栈中存放了指向用户栈的指针
■ 源PC和源CS是用户栈中的返回地址
■ 从内核态返回用户态的时候(IRET中断返回指令),会从内核栈弹出这些信息,根据这些信息就可以恢复到用户栈
在这里插入图片描述

内核线程switch_to的五段论:
○ 1. 从用户代码到内核代码,可能是主动的系统调用,也可能是时钟中断(时间片轮转)等情况,不过本质上都是中断
○ 2. 在系统调用进入阻塞或者时间片轮转等情况下,会调用schedule()
○ 3. schedule()会首先调用next(),进行调度,找到下一个占用CPU的内核级线程
○ 4. schedule()然后调用switch_to(),进行内核级线程的切换(第一级切换)
○ 5. 在切换后的线程的内核态下执行指令,直到iret指令,会回到用户态(第二级切换)
在这里插入图片描述
在这里插入图片描述

用户发出线程切换需求:

用户栈1——>内核栈1调用schedule()——>schedule()找到TCB1——>switch_to:TCB1切换到TCB2——>切换到内核栈2(第一次切换)——>用户栈2(第二次切换)

Q6:用户级线程和核心级线程之间的比较?

答:
代价指的是,进入内核,会消耗系统资源。

例如用户级线程完全不需要进入内核,也就不需要额外的内核数据结构,用户想起多少个用户级线程都行,就像浏览器想开多少个标签都行,如果用内核级线程来启动浏览器标签,那么启动多了之后就卡了。

用户灵活性,用户级线程可以由用户自己实现调度,自己决定什么时候切换,而核心级线程的调度与切换是在内核中提前写好的。

用户级线程和核心级线程搭配的效果最好。
在这里插入图片描述

重点2:CPU的调度策略?(具体)·

答:
CPU的调度,就是将CPU这个资源如何合理地分配给不同进程。

衡量指标:
在这里插入图片描述

三种基本的CPU调度算法:

1.短作业优先:保证周转时间短

答:
在这里插入图片描述
在这里插入图片描述

2.轮转调度:响应时间短

答:
在这里插入图片描述

3.优先级调度:兼顾响应时间和周转时间

答:
以轮转调度为核心,在此基础上增加优先级调度(SJF也是一种以时间为优先级的调度、前台任务优先级高于后台任务)

举例:schedule函数

重点3:进程同步?如何实现进程同步?(具体)

答:
进程同步,就是让多个进程走走停停,从而合理地让多进程有序推进。

解决进程同步靠的是信号量,就是根据信号量的值,进行等待和唤醒的过程。

在这里插入图片描述
在这里插入图片描述
○ sem=-x,x表示有多少个进程在等待
○ sem=+x,x表示有多少个资源
○ 生产者生产1个资源,如果此时sem<0,则需要唤醒1个进程,同时sem++
○ 消费者想要消费,先sem–,然后若sem<0,说明没有资源,需要睡眠等待

Q1:信号量解决进程同步存在的问题?

答:
虽然信号量可以解决进程同步的问题,但不同进程都可以对信号量进行修改,难免造成一个进程正在对信号量进行修改时,另一个进行也要修改信号量,造成竞争,从而信号量的值和实际需要的值不同。

解决方案:给信号量上锁
当一个进程修改信号量时,不允许其他进程修改。即要么不修改,要么修改过程不被影响,确保修改完成。
(上锁是一种硬件实现,不可以是软件实现,不然锁本身也需要保护,出现套娃现象。硬件原子指令法)

由信号量引出临界区概念:
临界区: 读写信号量的代码一定是临界区

在这里插入图片描述

Q2:临界区代码的保护原则?

答:
基本原则:互斥进入

附加原则
■ “有空让进”:临界区空闲的时候,应尽快选择出一个进程进入临界区(不能所有人卡着门口,结果一个人都进不去)
■ “有限等待”:进程发出请求进入临界区的等待时间必须是有限的,不能出现饥饿问题(不能总是让别人进,我也要尽快进去)

方法:
轮换法——>标记法——>非对称标记法——>Peterson算法——>面包店算法(两个进程以上的多个进程)

总结:用临界区保护信号量,用信号量实现进程的同步。

Q3:死锁是什么?必要条件?如何解决?

答:
信号量在被多个进程使用时,多个进程由于互相等待对方持有的资源而造成的谁都无法执行的情况叫死锁。

死锁问题的产⽣是由两个或者以上线程并⾏执⾏的时候,争夺资源⽽互相等待造成的。
死锁只有同时满⾜互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发⽣。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
1.死锁预防:两种方法
答:
在进程执行前,一次性申请所有需要的资源,不会占有资源再去申请其它资源。
缺点1:需要预知未来,编程困难
缺点2:许多资源分配后很长时间后才使用,资源利用率低

对资源类型进行排序,资源申请必须按序进行,不会出现环路等待
缺点:仍然造成资源浪费

2.死锁避免:银行家算法(重点)
答:
每次进行资源申请时,比如当前先执行P0,假装进行资源分配,然后使用银行家算法,判断会不会产生死锁。
在这里插入图片描述
在这里插入图片描述

3.死锁检测+恢复(符合实际)
答:
由于经常使用银行家算法造成时间效率低下,可以采用 定时检测或者是发现资源利用率低时检测,执行一遍银行家算法

	找到死锁的进程序列,从里面挑一个进程回滚。

4.死锁忽略
答:
个人PC机一般不处理死锁,直接重启即可,代价小。而对于银行系统,需要死锁处理。

2.操作系统如何管理内存

1)如何让内存用起来(重定位)

答:
将磁盘中的程序载入到内存中时,程序中的地址是相对地址,到了内存中需要重定位,才能让程序放在内存中正确的地方,以便让其正常运行。
在这里插入图片描述

编译时重定位:那么各个程序在内存中都有固定的位置,不会因为什么时候载入时,而产生变化。

载入时重定位:是当程序载入到内存的过程中,进行重定位,从而找到合理的内存区间。所以同一个程序,不同时刻载入内存时,其在内存中的位置是不同的。如果载入后,程序在内存中的位置不发生变化,会有一个问题就是,当该进程阻塞时,就会占用内存,造成浪费。

运行时重定位: 在运行每条指令时才完成重定位,每执行一条指令都要从逻辑地址算出物理地址,即地址翻译。

○ 不需要去修改程序中的地址,而是动态的去翻译这个地址为物理内存地址
○ 每个进程有各自的基地址,放在哪里?PCB
○ 对应的有相应的基地址寄存器,专门用来存放基地址,硬件加速;
○ 在进程切换的时候,会将相应进程的PCB中的基地址放到基地址寄存器中,那么之后这个进程执行的每条指令的寻址,都会加上这个基地址
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2)如何让内存更好的用起来?(分段)

答:
对于某个程序,是将它整个程序一起载入内存中吗?引入分段的概念
在这里插入图片描述

程序由若干部分(段)组成,每个段有各自的特点、用途,用户可独立考虑每个段怎么存放数据(分治),因此引入内存分段。

■ 例如代码段是只读的,data段是可写的,这两种数据放在一起显然不合适
■ 代码段/数据段不会动态增长
■ 栈段应该符合栈的操作逻辑,而且可以动态增长
■ 函数库段可能并不需要一开始就载入

因此,不是将整个程序,是将各段分别放入内存。

○ 每个段内的逻辑地址都是从0开始的
○ 引入段后的地址定位:<段号,段内偏移>
○ 栈是可以动态增长的,如果空间不够了,那么栈段可以单独的再重新分配,而不需要给整个程序的数据重新分配
○ 寻址要用段地址+段内偏移
PCB中需要存放所有段的地址
○ 不同的段会用到不同的段基址寄存器,例如数据段是DS,指令段是CS
○ 下图中,PCB中有一个进程段表,保存了每个段的段号、基址、长度、读写标志
在这里插入图片描述
○ 讲引导的时候,提到的GDT表,就是操作系统这个进程的进程段表,而其他进程的进程段表是LDT表
○ 进程切换的时候需要切换LDT表,进程寻址是到LDT表中查出段基址
在这里插入图片描述

整理与总结
○ 程序按照数据的不同用途分成多个段,每个段分别加载到内存的空闲区域,每个段的基址等信息存放在LDT表中
○ PCB保存了LDT,还是说PCB保存了LDT的指针?反正可以通过PCB找到该进程的LDT表,然后通过LDT表查出段基址
段基址+程序中的相对地址=物理内存地址
○ ldtr寄存器是做什么用的?存放段编号,是查找LDT表的索引
○ 之前讲进程切换的时候,提到的内存映射表,就是LDT表

3)内存是如何分割的?(分页)

答:
可变分区管理——>最佳适配

引入分页:解决内存分区导致的内存效率问题
在这里插入图片描述
通过内存紧缩来将内存碎片进行整合,速度太慢不适合。能不能将请求的内存大小进行碎片化,然后再分配到内存中各个碎片上

■ 把程序的段分成1页1页,物理内存在系统初始化的时候也已经被分成了1页1页
● 系统初始化中提到的mem_map中,就是以4k为单位分割内存,每4k就是1页
■ 中间空的页本身就是分配的单位,所以不算是内存碎片;在页的内部,每个段最多浪费4k(<4k)的空间
■ 针对每个段内存请求,系统1页1页的分配给这个段
■ 不需要内存紧缩

操作系统将内存分成段,再将段分成页。
页框是内存的划分,是物理页,页号是程序的划分,是逻辑页。

分页具体怎么分?=>多级页表+快表(见参考)

4)段页结合的实际内存管理(虚拟内存)

答:
○ 程序员希望用段,物理内存希望用页,所以尝试段、页结合
○ 分段:代码分段+内存分区+段表
○ 分页:内存分页+(多级)页表
○ 两者组合,让前者内存分区的输出作为后者分页的输入,中间就是虚拟内存。

对用户来说,是将程序分配到虚拟内存上;对物理内存来说,是将虚拟内存映射到物理内存上。
在这里插入图片描述
在这里插入图片描述

5)如何实现虚拟内存(内存换入和换出)

答:
虚拟内存是仓库,店面是物理内存(见参考)
○ 每个进程的虚拟内存加起来是很可能大于物理内存的,甚至单独一个进程的虚拟内存就比物理内存大,怎么办?
○ 用换入、换出实现“大内存”

缺页中断处理程序中,在从磁盘读入数据之前,首先要通过调用get_free_page()在物理内存中分配页
get_free_page()并不能总是获得新的页,内存是有限的
需要选择一页淘汰,置换算法?
■ FIFO
■ MIN
■ LRU,重点

3.操作系统如何管理文件(IO设备)

1)操作系统如何管理终端设备(键盘和显示器)

答:
CPU对外设发出操作指令,外设工作;外设向CPU发出中断,CPU读取外设数据到内存中。
在这里插入图片描述

1.操作系统管理显示器过程

在这里插入图片描述

2.操作系统管理键盘过程

在这里插入图片描述
总结:
在这里插入图片描述

2)操作系统如何管理磁盘(文件从磁盘中生成)

答:
在这里插入图片描述
具体过程:
在这里插入图片描述
从文件怎么得到盘块号,怎么从盘块号得到文件?
文件:建立字符流到盘块集合的映射关系,操作系统将这种映射关系进行封装

将一堆文件形成文件系统,即抽象整个磁盘。
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值