🎬 开场与引入

我们学习操作系统时,最早了解到的内容之一是:操作系统充当硬件和用户程序之间的中间层。这意味着,用户程序无法在没有操作系统干预的情况下访问硬件资源。

仔细想一想,操作系统和用户程序都是软件,毕竟他们都是使用编程语言构建的。但如果真是如此,为什么用户程序不能压倒操作系统,并接管计算机的控制权呢?

今天我们将探讨硬件与软件之间的巧妙协作,如何共同促进现代操作系统的稳定性和可靠性。

💻 特权指令与运行模式

这正是少数几个仅靠软件本身无法解决问题,因而需要硬件支持的典型情况之一。如你所知,每台计算机的处理器都有一组特定的操作指令,称为该处理器的指令集。如果我们希望一个软件比另一个拥有更高的系统控制权限,可以定义一个特殊指令的子集,并限制只有部分进程可以使用。

许多架构定义了所谓的特权指令。为了实现这种限制,硬件必须支持不同的运行模式。常见的实现方法是使用一个特殊的单比特寄存器,该寄存器只能保存1或0。这个寄存器被称为模式位

  • 当其值设为1时,处理器被认为处于特权模式,意味着它可以执行指令集中的任何指令,包括特权指令。
  • 当其值设为0时,处理器运行在受限模式,意味着它不能执行特权指令。

众所周知,CPU内部使用组合逻辑电路来解码接收到的指令。模式位对普通指令几乎没有任何影响,因为这些指令在任何操作模式下都可以执行。然而对于特权指令能否执行,取决于模式位的值是否允许当前进程执行。通过这种方式,特权指令只能在处理器处于特权模式时才能被执行。

🚦 中断机制

并且你可能会想到这样一个问题:这个模式位的值是如何被翻转的?由谁来翻转?这可能是整个机制中最关键的部分。因为如果任何用户进程都可以随意切换到特权模式,那么这个功能就毫无意义了。

可能有多种方法来处理这个问题,但通常切换机制是通过中断来实现的。我们先暂时将模式位放在一边,先来解释一下什么是中断。

当处理器运行程序时,地址寄存器PC指向下一个要执行的指令的内存地址。

中断就像是发送给CPU的一个信号,通知他某个事件已经发生。例如当用户按下键盘上的按键或移动鼠标时,系统可以触发一个中断来响应事件。

当CPU接收到这个信号后,会暂停当前进程并跳转到一个特定的内存位置。

这个内存位置中包含了CPU用于处理中断的可执行代码。该处理流程可以分为四个步骤:

  1. 首先CPU需要执行代码,来保存被中断进程的运行状态。
  2. 然后执行用于处理中断的代码,例如调用一个打印字符串的子程序。
  3. 中断处理完成后,CPU执行代码以恢复被中断进程的状态。
  4. 最后CPU执行一条特殊的指令,称为中断返回,从而跳回被中断的进程并恢复执行。

这就是中断的基本工作流程,他暂时终止当前进程,并将控制权转移到另一个处理程序。

🛡️ 内核模式与用户模式

现在回到模式位。中断不仅可以使CPU的程序计数器跳转,还可以自动将模式位的值翻转为1。这意味着位于 该内存地址的代码      (这里的内存地址是指CPU会暂停当前进程并跳转到一个特定的内存位置)将获得CPU的控制权,并且是在特权模式下运行,也就是说用于处理中断的代码是占用CPU且是特权模式下运行的,关键是这个代码是什么。

有一点我必须讲清楚,任何可执行实体都可以被加载到这个内存区域,比如网页浏览器、文本编辑器、任何程序。因此我们必须极其谨慎,一旦某个实体获得了特权模式,他几乎可以在计算机内做任何事情,这包括防止其他任何进程获得特权模式。 

现代的做法是确保操作系统被加载在此处,从而能够使用特权指令来完全控制系统。支持操作模式的处理器正是为此目的而设计的。也就是说先把操作系统加载到这个内存地址中,操作系统就是中断中的处理中断的代码,所以在设计的时候:

  • 特权模式:专为操作系统的内核设计,因此也被称为内核模式
  • 受限模式:专用于用户程序,以防止他们压倒操作系统,因此通常被称为用户模式

现在我们来看看内核模式可以做,而用户模式不能做的几件事:

  • 最明显的例子是与I/O设备的交互:比如网络和显卡、硬盘驱动器以及显示器和键盘等外设。直接操控这些设备需要使用特权指令,这意味着CPU只有在内核模式下运行时才能执行此类操作。
  • 在内存方面:特权指令允许操控一个称为内存管理单元的特殊硬件组件。该单元负责限制CPU可访问的物理内存区域。掌控MMU意味着完全掌控内存,这就是为什么操作系统可以读写任意物理内存位置,包括用户程序的地址空间。
  • 重新定义中断的处理方式:正如我前面所解释的,中断会导致CPU跳转到一个特定的内存位置,该位置包含处理中断所需的历程代码。问题是内存的使用方式并非总是固定的,处理中断的代码也不一定总是在相同的内存位置加载。如前所述,如果中断导致CPU切换到内核模式并跳入用户进程的地址空间,那将非常危险。这正是特权指令的用武之地,他们允许操作系统定义CPU在触发中断时必须跳转的确切内存位置。并且由于内存管理单元只能通过特权指令进行操控,操作系统会利用这一点,确保用户进程无法访问存储中断处理程序的内存区域。这可以防止任何恶意进程覆盖中断处理程序,从而试图夺取系统控制权。在内核模式下,我们能够完全控制CPU的中断流程。

🔗 系统调用

到现在你可能已经注意到,虽然硬件提供了这些工具,但最终还是取决于软件——也就是操作系统是否能正确使用它们来维持对系统的控制。

以上只是用户模式程序无法执行的操作中的几个例子。用户程序仍被允许使用大量普通指令。在用户模式下,程序可以执行移动和复制数据等基本任务,执行算数、浮点和按位运算,进行条件判断和运行循环。

然而试想,如果程序无法完成读取文件、在屏幕上显示文本或通过网络发送消息等基本操作,用户程序仍需要以某种方式访问硬件,否则他们将毫无用处。那么他们是如何实现这一点的呢?

你可能听说过API。为了让用户程序能够访问硬件资源,操作系统以库的形式提供一组功能,这些功能称为系统调用,对用户程序至关重要。

例如如果一个用户程序需要处理文件,它就需要通过系统调用来打开文件,每次想要读取或写入文件时都需要更多的系统调用,当工作完成后,还需要另一个系统调用来关闭文件。

系统调用仍然是函数,这意味着它们必须具有内部实现,并最终被编译为机器代码。但这里的情况开始显得有点奇怪,因为这些函数是库的一部分,当我们编写用户程序并使用这些函数时,它们会成为我们源代码的一部分。这意味着在编译后,他们会成为我们用户程序的一部分。但这并不合理,因为我们刚才已经确定,用户程序运行在用户模式下,因此无法使用操作硬件所需的特权指令。

那么这里究竟发生了什么?如果我们仔细观察,会发现在这些系统调用函数内部的某个时刻,存在一条触发中断的指令。是的,中断不仅可以通过硬件触发,也可以通过软件触发。在这种情况下,int指令使程序触发一个中断,导致程序计数器跳转到操作系统地址空间中的一个历程。

补充:打开文件的函数,里面有一个中断指令,中断:首先保存程序原来的运行状态,然后执行用于处理中断的代码,这里的处理中断的代码,就是跳转到操作系统地址空间中的一个历程,这个历程就是专门用于打开文件的,这是已经设计好的,发生中断,CPU会切换到内核模式,因为发生中断,都是加载到操作系统中去的。这个函数就是所谓的系统调用

这里就是操作系统来帮你完成,然后完成的步骤是怎样的

请记住,当中断发生时,CPU会切换到内核模式,允许该历程执行所有必要的特权指令来操作硬件并完成系统调用。一旦所需的特权指令执行完毕,就必须将控制权返还给被中断的进程,并确保模式位被重新设置为0,以便程序按预期在用户模式下恢复执行。

具体如何实现因架构而异。在此示例中,由中断返回指令处理,但在其他架构中,可能需要单独的指令来切换操作模式,涉及专用指令或显示覆盖模式位,然后通过另一条指令将控制权返还给被中断的进程。

另一种理解方式是,该函数的一部分位于以用户模式运行的用户程序地址空间中,另一部分则位于操作系统的地址空间中,并以内核模式运行。

总的来说,我们可以说系统调用为用户程序提供了必要的服务。我原本计划单独制作一个关于系统调用的视频,但我觉得我可以在本片中讲完其余内容,包括其优缺点。

系统调用的优点:

  1. 硬件抽象:它为开发人员提供的硬件抽象。许多人完全不知道直接操作硬件有多么困难和复杂。由于系统调用替我们处理了硬件交互,当我们需要访问硬件资源时,作为开发人员,我们只需调用它们即可。系统调用极为有用,它们让我们能够用代码思维,而不是晶体管思维。
  2. 安全性:这与硬件抽象密切相关。直接操作硬件容易出错,可能导致漏洞和系统故障。从Python或JavaScript迁移到底层编程语言的人常会问我会不会烧毁我的电脑,答案永远是否定的。因为操作系统为我们管理硬件,即使我们编写了一个非常糟糕的C程序,也无法以可能损坏电脑的方式直接操纵硬件。
  3. 可移植性:如果多个平台遵循某些规范,同一份源代码就能在不同系统中编译。

系统调用的缺点:

  1. 性能开销:这在某种程度上也与他们提供的硬件抽象相关。系统调用依赖的中断在性能上并不廉价,他们需要额外的步骤,例如上下文切换,而具有缓存等功能的现代架构会进一步增加性能成本。此外当程序调用系统调用时,它实际上会自行中断,将控制权交给操作系统。一旦操作系统获得控制权,就无法保证他会立即处理该系统调用,操作系统可能利用这个机会执行其他任务,例如运行CPU调度程序或管理其他系统资源。最终系统调用会被处理,但对于用户程序来说,无法保证处理会很快发生。从某种角度看,系统调用可以被看作陷阱,硬件抽象就是诱饵,引诱用户程序将控制权交还给操作系统。
  2. 平台依赖性:这与我之前提到的可移植性优点相矛盾。虽然许多平台力求兼容性,但某些奇怪的差异仍然存在。

⏱️ 抢占式调度

总结CPU操作模式机制很简单:用户程序可以通过中断使CPU切换到内核模式,但代价是中断自身并将CPU控制权交给操作系统。反过来,操作系统可以使用特权指令恢复被中断的进程,但强制其继续以用户模式运行。

由于系统调用的概念非常成功,一些架构通过提供专用指令使调用它们变得更加容易,无需手动设置中断。因此在许多架构中,你可以找到syscall指令和sysret特权指令,他们也使汇编代码更易读。

当操作系统依赖用户程序的协作来重新获得控制权并执行管理任务时,它被归类为非抢占式或协作式操作系统。这类通用操作系统已不再使用,因为它们存在一个重大问题。

设想一个包含无限循环且内部没有任何系统调用的程序,CPU将无限期的陷入执行该循环。如果一个进程有意或无意的避免使用系统调用,CPU将永远不会将控制权返回给操作系统,实际上是所有其他进程陷入饥饿。

再次说明,仅靠软件无法解决此问题,必须依赖硬件支持。解决方案是使用定时器。当定时器到期时,它会触发一个硬件中断。该定时器内置于CPU中,只能通过特权指令控制。

在将CPU分配给用户进程之前,操作系统会设置定时器。在进程运行时,定时器开始倒计时。用户进程仍可通过系统调用主动将控制权返还给操作系统。然而如果一个进程长时间未调用系统调用,定时器将确保最终触发一个中断,强制该进程停止,并将控制权交还给操作系统。

该机制使内核模式能够完全控制每个进程允许使用的CPU时间。此外当CPU支持操作模式时,硬件必须能够响应用户程序试图直接操作硬件的行为。如果处理器在用户模式下运行并遇到特权指令,硬件可配置为立即触发中断,将控制权传递给操作系统,以便它处理用户程序的可疑行为,很可能终止其执行。

中断是CPU的基本功能,因此更准确的说,内核模式授予了对CPU使用方式的完全控制。当硬件架构支持这些功能且操作系统利用它们时,我们称之为抢占式操作系统

⚠️ 驱动程序与内核级软件

考虑到这一点,我们现在应该明白,用户程序和操作系统都只是软件。由于用户程序被限制在用户模式下,他们无法直接与硬件交互,而必须将控制权交给操作系统,由操作系统代表他们操作硬件。因此操作系统始终处于中间。这一概念实际上是对真实情况的一种高度抽象。

许多人可能会想知道,除操作系统外的软件是否也能在内核模式下运行。答案是肯定的。

例如当新的显卡发布时,操作系统可能不知道如何控制它,因为它在显卡存在之前就已经开发出来了。操作系统开发者无法为每一种存在的硬件设备编写代码,因此这项任务通常由硬件制造商完成。解决方案称为驱动程序,是专为控制特定硬件而设计的软件。由于控制硬件需要特权指令,驱动程序必须在内核模式下运行。当用户程序需要硬件时,操作系统会使用相应的驱动程序。

然而这种技术带来了一个严重缺点:任何在内核模式下运行的代码都可以访问特权指令,这使得操作系统无法将其与其他进程甚至操作系统本身完全隔离。因此所有在内核模式下运行的代码共享一个地址空间。如果一个驱动程序错误地写入了错误的内存地址,可能会破坏操作系统的关键数据;如果驱动程序崩溃,将导致整个操作系统崩溃。没有操作系统,用户程序无法运行,整个系统会崩溃。这就是为什么有缺陷的驱动程序常常导致Windows蓝屏死机。

尽管存在风险,允许代码在内核模式下运行仍是必要的,因为驱动程序是不可或缺的。同样对于操作系统开发者来说,为市面上每一种硬件设备编写代码几乎是不可能的。

但第三方代码能够在内核模式下运行这一事实使其用途超出了单纯的设备驱动程序。这包括视频游戏的反作弊系统和恶意软件检测软件等。让我快速解释一下为什么会这样:

  • 反作弊:首先以内核模式运行的软件不依赖系统调用,因此它的运行速度比在用户模式下快得多。想象一款禁止使用手柄以防止对鼠标和键盘玩家造成不公平优势的游戏。玩家可能通过第三方程序将手柄映射为键盘输入来作弊。由于游戏运行在用户模式下,他无法判断输入是否真正来自键盘。然而以内核模式运行的反作弊软件可以直接监控硬件活动,检测输入是否实际来自手柄。 
  • 恶意软件检测:对于恶意软件检测,内核模式允许软件扫描网络适配器等设备,监控网络流量,并检查正在运行进程的地址空间已发现可疑行为,例如监视私人数据并将其发送到不可信的终端。

现在就很容易理解为什么这类软件必须在内核模式下运行了。如果他在用户模式下运行,就无法完成这些任务。 

不过并非所有情况都适合使用内核模式。以CrowdStrike为例,这是一款安装在全球数百万企业设备上的恶意软件检测软件。去年9月一次更新损坏了一个配置文件,导致文件内容全部变为零。文件损坏后,该软件崩溃,导致操作系统也随之崩溃。结果就是全球范围的大混乱,成千上万的设备出现蓝屏死机,企业遭受了数十亿美元的损失。

另一个例子是Vanguard,这是拳头游戏公司Riot Games为其热门游戏英雄联盟和VALORANT开发的反作弊软件。首次发布时,他曾导致数百名玩家的电脑出现蓝屏死机。但更令人担忧的是它与腾讯的关联,腾讯是一家拥有拳头游戏的中国公司。事实证明你不安装Vanguard就无法游玩拳头游戏。从反作弊的角度来看这是合理的。令人困扰的是,Vanguard即使在你不玩游戏时也以内核模式运行,这意味着它可能监控你在电脑上的所有操作。虽然目前没有证据表明他确实这样做了,但腾讯作为一家中国公司可能受到政府监管,这引发了隐私方面的担忧。考虑到Vanguard运行在全球数百万台电脑上,这种情况变得相当令人不安。

🏁 结尾

本期内容就到这里下次见

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐