本系列笔记整理自 https://www.bilibili.com/video/BV1YE411D7nH

个人认为讲解比较清晰,容易理解。

一、中断和异常

1. 中断的作用

在前一节中我们提到“中断”会使CPU由用户态变为内核态,让操作系统重新夺回对CPU的控制权。如果没有“中断”机制,那么一旦应用程序在CPU上运行,CPU就会一直运行这个应用程序,那么也就无法实现“并发”。

2. 中断的分类

中断分为内中断和外中断,分类的依据主要是当前执行指令的来源。

2.1 内中断

内中断与当前执行的指令有关,中断信号来源于CPU内部

例如:试图在用户态下执行特权指令,或是执行除法指令时发现除数为0。这些都是非法指令,会引发一个中断信号。

有些时候,应用程序也会主动地请求操作系统内核的服务,这时就会执行一条特殊的指令——陷入指令,该指令会引发一个内部中断信号。例如:“系统调用”就是通过陷入指令完成的。

【补充】“陷入指令”实在内核处于用户态下执行的,因此它不是一条特权指令。

当CPU检测到中断信号时,会切换到内核态,接着处理中断信号的内核程序。

2.2 外中断

外中断与当前执行的指令无关,中断信号来源于CPU外部

这里我们通过一个时钟中断来说明。时钟中断指的是由时钟部件发出的中断信号。时钟部件每隔一个时间片(如50ms)会给CPU发送一个时钟中断信号。

假设此时有两个应用程序要执行。当第一个应用程序执行了50ms后,CPU会收到来自时钟部件的中断信号。CPU会停止执行当前应用程序,转而处理时钟中断的内核程序。处理后,CPU会知道第一个程序已经执行了50ms,为了“公平”,它接着会处理第二个应用程序,直至再次收到时钟中断信号又会切换去执行第一个应用程序。

再举个例子,比如说打印机完成输入输出任务后,也会向CPU发出中断信号,这也是一个外中断。接着CPU会处理I/O中断的内核程序。

我们可以认为每一条指令执行结束后,CPU都会例行检查是否有外中断信号

2.3 区分几个概念词

将上面的内容整理之后如下图所示:

OS

其中有几个词语我们还比较陌生,在这里做一下阐释:

  • 陷阱、陷入:这是由陷入指令引发,是应用程序故意引发的;
  • 故障:这是由错误条件引发,但可能被内核程序修复。内核程序修复故障后会把CPU使用权还给应用程序,让它继续执行下去;
  • 终止:这是由致命错误引起,内核程序无法修复该错误。因此一般不再将CPU使用权还给引发终止的应用程序,而是直接终止该应用程序。

3. 中断机制的基本原理

中断机制的基本实现原理可以分为两步。第一步是检查中断信号,CPU在执行指令时会检查是否有异常发生,这就是内中断。而在每个指令周期末尾,CPU都会检查是否有外中断信号需要处理。

检测到中断信号后,对于不同的中断信号,需要用不同的中断处理程序来处理。当CPU检测到中断信号后,会根据中断信号的类型去查询“中断向量表”,以此来找到相应的中断处理程序在内存中的存放位置。

这部分具体在计算机组成原理中说明,这里不做过多要求。

OS

二、系统调用

1. 什么是系统调用

我们之前说过,操作系统作为用户和计算机硬件之间的接口,需要向上提供一些简单易用的服务。主要包括命令接口和程序接口。其中,程序接口由一组系统调用组成。

OS

因此,系统调用可以认为是操作系统提供给应用程序使用的接口,是一种可供应用程序调用的特殊函数,应用程序可以通过系统调用来请求获得操作系统内核的服务

2. 系统调用和库函数的区别

我们一般编程常用的都是高级编程语言,其中内置了许多库函数帮助程序员简化程序的编写。其中有一些库函数涉及到了操作系统内核才能提供的服务,这些库函数中便会封装一些系统调用,从而使得上层程序也能请求内核的服务。

也就是说,普通的应用程序可以直接进行系统调用,也可以使用库函数。而有的库函数涉及系统调用,有的不涉及。

OS

例如:“取绝对值”就是一个不涉及系统调用的函数,“创建一个文件”就是一个涉及系统调用的函数。

3. 系统调用的必要性

这里我们举一个例子来说明为什么我们要使用系统调用。

假设我们不使用系统调用,而是允许所有应用程序各自请求操作系统内核的服务。那么,当我们在打印店打印材料时,同时使用WPS和WOED进行打印,那么就会出现打印机设备交替地收到两个进程发来的打印申请,使得打印内容混在一起。

解决的办法也很简单,由操作系统内核对共享资源进行统一的管理,并向上提供“系统调用”,用户进程想要使用打印机这种共享资源,只能通过系统调用向操作系统内核发出请求。内核会对各个请求进行协调处理。

应用程序通过系统调用请求操作系统的服务。而系统中的共享资源都由操作系统的内核统一掌管,因此凡是与共享资源有关的操作,都必须通过系统调用的方式向操作系统内核提出服务请求,这样可以保证系统的稳定性和安全性,防止用户进行非法操作。

OS

4. 系统调用的过程

我们之前讲了什么是系统调用,为什么要系统调用。现在我们简单地描述一下系统调用的具体过程。

在高级语言代码中的每一条代码都会被翻译成机器语言指令,再交给计算机进行处理。当遇到库函数(该库函数内部封装了系统调用的复杂细节)时,首先会处理相关指令,其中包含一些传参指令,这些传参指令可以告诉CPU需要哪一个系统调用。接着向CPU发送一个陷入指令,这会让CPU从用户态切换成内核态。CPU会停止应用程序,转而处理中断程序即系统调用的入口程序。

然后,操作系统会根据参数判断用户需要哪一个系统调用服务,并完成相关服务。最终返回用户态继续完成应用程序。

【补充】陷入指令=trap指令=访管指令

三、操作系统的大内核和微内核

1.1 大内核和微内核的区别

在之前我们总是不断地提到内核这个概念。我们一般认为操作系统包括内核功能和非内核功能。但事实上,内核的定义并不明确。在现在的计算机系统中,微内核有时也称为内核。

OS

在上图中,我们将拥有中间绿色、橙色和黄色三层功能的部分统称为操作系统。而除去非内核功能的部分我们认为是内核。但由于剩下的这些功能有些并不是非常接近硬件。因此又衍生出了两种不同的形式。

我们一般将包括了所有剩下功能的称为大内核,将只包括与硬件关系最紧密的,如时钟管理、中断处理、原语等,称为微内核。

【补充】原语是一种特殊的程序,具有原子性。也就是说,这段程序的运行必须一气呵成,不可被“中断”。

1.2 微内核操作系统的优点与缺点

微内核的结构是建立在模块化、层次化结构的基础上的,同时采用了客户/服务器模式和面向对象的程序设计技术,因而可以概括出以下优点:

  1. 提高了系统的可拓展性:微内核操作系统的许多功能是由相对独立的服务器软件来实现的,因此系统的灵活性大有改善,可以很方便地增加新的功能,修改原有功能,以及删除已过时的功能。
  2. 增强了系统的可靠性:微内核内部提供了规范而精简的应用程序接口(API),并且因为所有服务器都运行在用户态,当某个服务器发生错误时不会影响内核。
  3. 可移植性:微内核结构的操作系统中大部分与特定 CPU 和 I/O 设备硬件相关的代码均放在内核中,而操作系统其它绝大部分均与硬件平台无关。因此,把操作系统移植到另一个计算机硬件平台上所需作的修改是比较小的。
  4. 提供了对分布式系统的支持
  5. 融入了面向对象技术

当然,这样的结构缺点也十分明显。正是因为大部分功能都在内核之外,因此消息交互时需要在用户/内核模式以及上下文进行多次切换,这会导致操作系统的运行效率有所降低。