CPU内核/特权模式是什么,操作系统如何保护它?

3
在内核模式下,执行代码可以完全无限制地访问底层硬件。它可以执行任何CPU指令并引用任何内存地址。例如,哪些指令和哪些内存地址(最好是x86)?至于内存,是否意味着内核保留了高1GB地址(32位)?此外,是谁决定的?是CPU还是操作系统?如果是操作系统,CPU如何知道哪一部分内存被认为是保留的(也许是页面表中的某个位)?在内核模式下如何访问内存,仍然是通过虚拟地址吗?
关于进入内核模式,是通过类似于int的某些指令完成的,对吗?我理解所有系统调用都受到了严格的保护,但如果我用汇编语言编写一个包含int指令的程序会怎么样呢?那该如何保护呢?我的意思是,当这个程序被编译后,最终生成可执行文件时,int指令必须位于“.text”部分,因为编译器不会对其进行任何处理,对吗?那么操作系统在运行时会如何处理呢?或者说,“.text”部分中究竟是什么?它是CPU所识别的指令集,还是一些中间格式,需要由操作系统进行“翻译”,以便操作系统有机会在CPU运行之前“拒绝”某些指令(如“int”或其他“受限制”的指令)?

更新: 对于任何感兴趣的人,这里有一个来自Quora的很好的解释(至少对我来说如此):https://www.quora.com/What-is-CPU-kernel-mode-and-how-is-it-guarded-by-the-OS/answer/Mostafa-Abd-El-Aziz?snids=937993065&nsrc=1&filter=all

2个回答

2
我们有内核模式是为了保护进程互相之间和自身。每个进程看到它的地址空间被划分为用户区域和内核区域(除了一些例外情况)。用户区域完全属于该进程。所有进程共享同一个系统区域。
将对系统区域的访问限制在内核模式下,可以保护它免受在用户模式下运行的恶意进程的攻击。
所有进程都需要与内核通信。完成这个过程的方法是中断或异常。两者的处理方式相同,不同之处在于它们如何被触发。
内核通过调度表(使用各种名称)定义了进入自身的路径。异常和中断具有数字,这些数字是索引,包含用于处理特定事件或服务的函数的地址。
例如:
如果您想进行I/O操作,则您的fprintf语句最终会转换为一个用户模式的包装函数(系统服务),以一定的方式设置堆栈和寄存器。该系统服务会触发一个异常。调度表将其发送到正确的内核模式处理程序,以处理对磁盘的写入(通常是写入)。
内核模式处理程序需要仔细验证发送给它的所有参数。寄存器值是否正确?所有对内存的引用是否有效且在用户区域内?任何验证漏洞都可能导致系统被破坏。

内核模式函数会将要写入的数据发送到正确的位置,以便存储到磁盘。

关于进入内核模式的问题:

进入内核模式是通过某些指令,比如int吗?

是的。还有一些异常情况,比如除以零、页面错误等。在我看来,Intel INT指令是一个异常生成器,而不是通常描述的软件中断。让你进入内核模式的三件事情是:

  1. 中断(异步事件)
  2. 陷阱(INT引起陷阱)
  3. 故障(可以纠正的异常,并允许重新选举导致它们的指令)

我理解所有系统调用都受到了严格保护,但如果我编写包含int指令的汇编代码程序会怎样呢?

您的应用程序会通过分派表进行路由。在那里被调用的函数应该会捕获无效的调用。

当程序被编译并生成可执行文件时,int指令必须在“.text”部分中,因为编译器不会对其进行任何处理。那么操作系统在运行时会如何处理呢?或者“ .text”部分实际上包含了什么?它是CPU能够识别的指令,还是一些中间格式,需要由操作系统“翻译”后才能被CPU识别?这取决于1)强制您转到调度表中的处理程序和2)处理程序进行适当验证。

0

这些都在手册里有详细说明。

比如说视频内存/寄存器,或者其他正在计算机上运行的程序,它们都有一些用于指令和数据的内存。你的程序不应该直接触碰到这些内存,只有内核有权限可以这样做,无论好坏与否,但是你的应用程序只能触碰到自己的内存。

这是如何实现的呢?这取决于处理器系列和其他因素,但通常有一个MMU(内存管理单元),它允许你的程序使用虚拟地址。这个虚拟地址通过MMU进行处理,MMU会根据你使用的地址在表格中查找相关信息,比如下一条指令的地址、内存中变量的地址等等。MMU除了将虚拟地址转换为物理地址之外,还会在表格中包含权限和缓存信息。所以,如果你访问了MMU表格中没有授权给你的地址,就会发生错误,具体情况取决于硬件(处理器)和操作系统,可能会给你返回垃圾数据或0xFF,并让你继续执行,也可能会完全杀死你的应用程序,或者介于两者之间。在x86系统中,I/O空间也有类似的权限控制,可以授权或禁止访问I/O地址。

现在故障是可取的,例如您可能听说过虚拟内存。交换或交换空间或其他什么。假设所有正在运行的应用程序都需要比系统拥有的更多的RAM,那么最新的应用程序会获得它所需的一些RAM。选择了一些其他应用程序的RAM,该RAM被写入交换文件或硬盘或其他媒体,然后旧应用程序的MMU表被标记为将为您故障,以便您获得对该物理RAM的虚拟地址。当其他应用程序来访并想要访问曾经属于它的RAM时,它会出现故障,操作系统会被调用,并记住它已将该应用程序的内存交换到磁盘上,因此它现在从其他人那里找到一些RAM,将其保存到磁盘上,并从磁盘上拉出当前应用程序的内存,修补其MMU表并让其继续运行,这种情况不断发生。他们还使用这种方法来虚拟化外围设备或允许多个应用程序认为它们正在使用外围设备,但实际上它们都在共享,没有人真正触摸外围设备,只是触摸每个访问都会导致故障,从而导致操作系统模拟外围设备。然后您可以做无数的事情。

MMU还允许程序都认为它们在同一个地址空间中,因此假设所有程序都编译为具有0x8000的入口点,并且RAM从0x00000到需要或可以寻址的范围是线性的。每个程序都拥有相同的地址空间,这是否可能?实际上,它们中没有一个拥有该地址空间,他们认为的从地址零到N的线性地址空间实际上被MMU切成各种大小的块,每个块可以完全不同地放置在RAM中。也许0x8000到0x10000现在位于物理地址0xD0008000,而0x10000现在位于物理地址0x22000000,这是完全正常和常见的。每次启动程序时,操作系统都知道可用的物理地址空间,因为它正在管理它,程序说它想要N字节,因此操作系统将那些RAM拼凑在一起,与旧日MMU之前的情况不同,你不必移动其他东西来使空出一个N字节的空间,你只需要找到一些小于N字节的子块,以适合MMU表格,以便最终获得N总字节,然后填充该空间并让应用程序开始运行。因此,由同一编译器为同一操作系统构建的每个程序的.text将放置在相同的位置,.data理想情况下也是如此,或者至少必须符合该操作系统的规则。执行模式或正在运行的应用程序的虚拟ID或某些处理器特定机制确定您是否为受保护的或未受保护的程序。在某些甚至是大多数系统上,即使内核代码也必须通过MMU,但内核ID或模式或其他标记为具有访问所有内容的权限。对于外围设备访问而言,不是必需的,但可能需要虚拟地址等于物理地址,但也可能不需要,您可能仍然需要执行某种内核API调用以获取指向某些物理地址的虚拟地址/指针(类似于应用程序的mmap)。

谁来决定呢?操作系统根据CPU和其他硬件的规则来决定并实现所需的功能。通常,如何从应用程序切换到内核或从应用程序中进行“系统调用”是CPU和操作系统特定的组合。当您阅读手册时,它会以某种形式详细说明这一点。例如,ARM更容易学习这个过程,然后再去尝试更复杂的东西...在程序员模型章节中,模式已经为您详细说明,svc指令(有一个别名,我现在想不起来了)告诉您它将处理器切换到该模式,该模式会触发该模式下的处理程序,即基本上是内核/操作系统,它实现调用并返回切换模式(假设它是一个返回调用而不是应用程序由于错误退出或被杀死)。同样,数据异常或预取异常在文档中有明确的记录,说明它进入的模式等等。用户模式限制已定义,并且您开始看到用户模式代码真正被困在一个安全的沙盒中,无法逃脱。
因此,这不是一个在这个物理地址之上有很大范围受保护而在下面是公平竞争的情况,所有你的应用程序地址空间都由操作系统管理,并由CPU和相关硬件强制执行,所有它都是虚拟地址,这些地址可能很少映射到物理地址。内核可能会涉及物理地址,但其执行模式或 ID 拥有比您更多的权限。你的访客徽章可能会限制你进入等候室或咖啡厅,但其他人的徽章则会授予他们更深入公司/建筑物内部的访问权限,所有这些都由拥有神一般的能力的人管理,他们根据要求和信任编程系统,决定你的徽章、他的徽章或她的徽章将打开哪些门。

有没有现代架构在不允许读取的情况下“返回垃圾或0xFF”?(我可以想象对于微控制器上未定义的地址可能会出现这种情况,但即使对于这样的系统,早期失败似乎是首选行为。) - user2467198
在某些情况下,如果链接断开(可能是规范),PCIe会执行此操作。我只是留下了这个漏洞,因为可能有一个存在,或者肯定会有一个中止处理程序可以执行此操作并让您继续运行。为什么呢?谁知道呢,但可能会发生。如果硬件允许您在错误读取时继续运行,通常会得到陈旧的数据或总线上的任何内容,为什么要添加额外的逻辑或努力来控制当您做错事时得到什么呢? - old_timer
读取过期数据在某些情况下可能会引入安全漏洞(例如,如果在旧数据可用和新的读取执行之间更改了读取权限)。给出“总线错误”似乎通常比返回固定值更好,但我同意,在某些情况下,可能会有一些系统从更一般的角度来看做出不太理想的事情。 - user2467198

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接