Menu Close

用户态与内核态:操作系统如何保护你的计算机?

当你打开浏览器、播放音乐,或者运行一个 C++ 程序时,CPU 会执行这些应用程序的指令。与此同时,它也会执行操作系统的指令。

用户态与内核态:操作系统如何保护你的计算机?

但是,这些指令并不都拥有相同的执行权限。

浏览器可以进行计算,也可以更新自己的数据,但它不能随意改写内核的内存,更不能直接接管整台计算机。

计算机是怎样实施这些限制的?

答案要从**用户态(User Mode)和内核态(Kernel Mode)**说起。它们代表不同的执行权限,帮助计算机将普通应用程序与负责管理系统的代码区分开来。

本篇教程将介绍这两种模式的含义、存在的原因,以及应用程序如何通过受控的方式跨越这道边界,请求操作系统提供服务。

1. “模式”是什么意思?

模式(Mode)是处理器的一种执行状态,用来决定当前运行的代码可以执行哪些操作。这里所说的特权(Privilege),就是执行某种受保护操作所需要的权限。

用户态为普通应用程序提供受限制的执行权限。内核态则提供操作系统核心管理系统时所需要的权限。

它们并不是两个独立的处理器。同一个处理器可以先在用户态执行应用程序指令,随后切换到内核态执行内核指令。

在拥有多个 CPU 核心的计算机上,不同核心可以同时执行用户代码和内核代码。并不存在一个统一开关,让整台计算机只能处于用户态或内核态。

真正需要关注的是:此刻在这里执行的代码,具有什么权限?

2. 为什么需要不同的权限?

假设所有应用程序都能修改任意内存位置、改变处理器的保护设置,或者不受限制地向设备发送命令,会发生什么?

一个很小的编程错误就可能破坏操作系统。恶意程序也可能干扰其他程序,甚至关闭原本用来约束它的安全保护。

例如,文本编辑器可能因为程序错误,计算出了一个不正确的内存地址。如果这个地址指向受保护的系统内存,计算机就需要阻止这次访问。

这种保护不能依赖应用程序自觉遵守规则。

操作系统负责建立限制,处理器硬件协助执行这些限制。两者配合,在普通程序执行与系统管理之间建立起一道边界。

3. 程序在用户态可以做什么?

普通应用程序代码通常在用户态运行。

它们可以进行算术运算、比较数值、执行循环、调用自己的函数,以及读写被允许访问的内存。

**进程(Process)是程序运行起来后的一个实例,包括操作系统为它管理的资源和执行状态。**同一个程序可以拥有多个运行实例,也就是多个进程

每个进程通常都有自己的虚拟地址空间(Virtual Address Space),也就是它用来访问代码和数据的一组内存地址。

应用程序不能随意访问为内核保留的内存,也不能随意访问其他进程的内存。操作系统可以允许进程共享指定的内存区域,但这种共享受到控制。

这种隔离有助于限制程序故障的影响范围。一个浏览器进程崩溃,通常并不意味着整个操作系统也必须跟着崩溃。微软的用户态与内核态文档介绍了这种隔离机制。

用户态仍然允许应用程序直接使用 CPU 完成大量工作。内核不需要逐一批准每次加法、比较或普通函数调用。

4. 内核在内核态可以做什么?

内核(Kernel)是操作系统的核心,负责管理资源,并执行程序使用这些资源时必须遵守的规则。

为了完成这些工作,内核需要访问某些受保护的处理器操作。

**特权指令(Privileged Instruction)是必须在适当的处理器权限级别下才能执行的指令。**例如,有些指令用于修改受保护的内存管理设置,或者控制某些中断行为。

具体有哪些特权指令,以及它们受到什么限制,取决于处理器架构。

内核态为内核代码提供管理系统所需要的权限,但更大的权限也意味着错误可能造成更严重的后果。

**设备驱动程序(Device Driver)是知道如何与某个设备或某类设备通信的软件。**如果驱动程序在内核态运行,其中的错误就可能破坏关键系统数据。

一些驱动程序也可以在用户态运行,具体取决于操作系统和驱动程序的设计。微软文档介绍了驱动程序运行模式的差异及相关故障风险。

5. 硬件怎样执行这道保护边界?

处理器会记录当前的权限模式,将它作为自身执行状态的一部分。

当代码尝试执行受保护的操作时,硬件会检查当前权限是否允许这样做。

内存保护还依赖操作系统配置的数据结构。在使用虚拟内存的系统中,页表(Page Table)记录虚拟地址与物理内存之间的映射关系,同时包含访问权限信息。

**内存管理单元(Memory Management Unit,简称 MMU)**是负责地址转换并检查相关内存访问权限的硬件。

如果应用程序尝试进行被禁止的内存访问,硬件就会产生一个异常(Exception)。这里的异常,是指由当前执行的指令引发、需要专门处理的事件。

操作系统随后会作出响应。对于无效的应用程序内存访问,最终处理结果可能是终止该进程

有些异常可以恢复,因此出现异常并不一定意味着程序必须崩溃。

关键在于:程序不能仅仅把某个受保护的地址交给加载或存储指令,就绕过内存保护。

6. 应用程序怎样请求内核服务?

应用程序仍然需要访问文件、建立网络连接,以及使用操作系统管理的其他资源。

它们通过**系统调用(System Call)**请求这些服务。

系统调用是应用程序向内核请求服务的受控接口。在 Linux 中,应用程序通常通过**库函数(Library Function)**使用这个接口。库函数是可以重复使用的代码,其中一些库函数负责准备和提交系统调用请求。Linux 系统调用手册解释了两者之间的关系。

应用程序需要指明希望执行的操作,并提供相应的参数(Argument),也就是完成这项操作所需要的值。

随后,一条与处理器架构相关的指令,通过预先建立的内核入口机制转移执行流程。

应用程序并不是获得更高权限后,继续在内核态运行自己的任意代码。

执行流程会转向由特权系统软件配置好的内核入口。内核检查请求、核对传入的信息,并判断这项操作是否被允许。

请求处理完成后,执行流程可以返回用户态中的应用程序代码,同时带回处理结果或错误信息。

7. 示例:读取文件

以文本编辑器读取一个已有文件为例。

首先,应用程序请求打开文件。内核根据请求定位文件,并检查相关的访问权限。

Linux 中,打开操作成功后,应用程序会获得一个文件描述符(File Descriptor)。它是一个较小的整数,用来在该进程中标识这个已打开的文件。

随后,应用程序请求读取数据,并提供文件描述符、目标缓冲区(Buffer),以及希望读取的数据量。缓冲区是一块用于临时存放数据的内存区域。

内核检查请求并获取数据。如果所需数据已经在内存中,就可能不需要访问存储设备;否则,系统会安排必要的设备操作。

对于普通的读取操作,内核将取得的数据放入应用程序的缓冲区,并返回处理结果。

文本编辑器随后在用户态继续运行,处理读到的文字。

在这个简化示例中,打开文件和读取文件是两个独立的操作。用户看到的一次应用程序操作,背后可能涉及多次系统调用。

在整个过程中,文本编辑器请求的是具体服务,而不是获得对存储设备的无限制控制权。

8. 系统调用并不是进入内核的唯一方式

硬件事件也可以让内核代码开始运行。

中断(Interrupt)是一种能够使处理器将执行流程转向处理程序的事件,例如定时器信号或设备通知。处理程序(Handler)则是负责响应这个事件的代码。

例如,设备可以通过中断通知系统:某项操作已经完成。

异常同样可以将执行流程转向处理程序。例如,程序尝试执行被禁止的指令,或者进行了一次需要操作系统处理的内存访问。

这些事件的起点不同:

  • 系统调用:应用程序主动请求服务。
  • 硬件中断:硬件报告某个事件。
  • 异常:由当前执行的指令引发。

当这些事件使执行流程从应用程序转入内核时,处理器会进入相应的特权状态。

如果事件发生时处理器已经在执行内核代码,那么处理该事件就不一定需要改变权限模式。

9. 模式切换不一定是上下文切换

模式切换(Mode Switch)是执行权限级别发生变化,例如从用户态进入内核态。

上下文切换(Context Switch)则是通过保存一个线程的执行状态、恢复另一个线程的执行状态,改变当前运行的线程。线程(Thread)是进程中的一条执行路径。

这两种变化可以同时发生,但它们不是同一件事。

假设应用程序发起一个系统调用,而内核可以立即完成这项请求。内核代码可能代表同一个线程处理请求,然后直接返回。

这个过程中发生了模式切换,却没有切换到另一个线程。

如果请求需要等待存储设备,操作系统可能在等待期间运行另一个线程。这时就还会发生上下文切换。

因此,进入内核并不自动意味着另一个应用程序开始运行。

10. 管理员权限不等于内核态

管理员账户在操作系统的权限规则下拥有较大的操作权限。

Linux 和其他类 Unix 系统中,传统的管理员账户称为 root

但是,以 root 身份运行的程序,其应用程序指令通常仍然在用户态执行。在 Windows 中,以管理员身份启动的应用程序也存在同样的区别。

账户权限影响的是内核会允许哪些请求,而不是直接改变应用程序指令执行时的 CPU 权限模式。

例如,管理员可能被允许修改某项受保护的系统设置,但应用程序仍然需要通过操作系统接口请求这项修改。

用户身份与 CPU 权限模式,是两个不同的概念。

11. 这与 RISC-V 有什么关系?

“用户态”和“内核态”描述了操作系统中一种常见的权限区分,但不同处理器架构并不都只提供两个硬件权限级别。

RISC-V 的标准特权架构定义了三个主要级别:用户模式(User Mode)、监督者模式(Supervisor Mode)和机器模式(Machine Mode),分别简称 U 模式、S 模式和 M 模式。

在典型的、运行 Linux操作系统的 RISC-V 系统中,应用程序运行在 U 模式,内核运行在 S 模式。

M 模式拥有更高的权限,通常用于运行底层固件(Firmware)。这里的固件是负责平台初始化或提供基本机器服务的软件。

并非所有 RISC-V 处理器都实现这三种模式。M 模式是必需的,较简单的实现可以省略其他模式。RISC-V 特权架构规范定义了这些级别及其预期用途。

虽然具体实现不同,但基本原则相同:软件系统中的不同部分,获得不同程度的操作权限。

12. 总结

用户态与内核态在应用程序和操作系统核心之间建立了一道重要的权限边界。

应用程序以受限制的权限执行普通指令,内核则使用更高权限管理资源并执行保护规则。

系统调用让应用程序通过受控入口请求服务。中断和异常则提供了让内核代码运行的其他途径。

模式切换不一定涉及线程切换,而管理员权限也不会让应用程序代码自动变成内核代码。

这些保护机制不能消除所有程序错误和安全漏洞,但它们为限制故障影响和控制资源访问提供了基础。

理解这道边界之后,下一个问题就更加具体了:系统调用究竟怎样把请求从应用程序送进内核,又怎样把处理结果带回来?

Posted in 操作系统