操作系统如何检测内存访问违规

Dod*_*ddy 14 linux operating-systems segmentation-fault

操作系统(最好是 Linux)如何知道您访问了不允许访问的内存位置?

这个问题的灵感来自那些该死的指针!我的看法是:计算机中的一切都是在速度、安全性、完整性等方面做出妥协。

我很清楚 Linux 中的内存映射,但对我来说,内核检查您尝试访问的位置是否位于每次访问的有效范围内听起来有点荒谬。听起来这会浪费很多时间,这些时间可以用来做一些更有效率的事情(但如果没有检查,可能会更不安全!)。或者它可能会记住所有最近的访问并在每个硬件计时器滴答时检查它们?(但这听起来不安全,而且还是很慢。)

我很惊讶这个问题似乎在任何地方都没有答案。这是我一直想知道的事情。这让我觉得有一部分硬件将代表操作系统以一种很好、方便的抽象级别来完成这项工作。但是,它可能需要在每次上下文切换时加载下一个进程的内存映射,这听起来又很慢。

所以是的,无论如何,我要讲一点:操作系统如何检测内存违规?

谢谢

Gil*_*il' 13

(以下答案假设是“现代”台式机、服务器或高端嵌入式平台(例如智能手机,以及越来越多的小型系统)。对于 x86 系统,现代意味着 386 及更高版本。以下答案还假设一个“现代”操作系统,例如几乎所有 Unix 或 95 年以来的 Windows。)

这不会发生在操作系统中,而是发生在处理器中,特别是在MMU内存管理单元)中。MMU 支持虚拟寻址,由此构成指针的位不直接指示位在内存中的物理位置。

在典型的 MMU 中,当指针被取消引用时,MMU 将位分解为两组:高位构成页码,低位构成页内的地址。大多数台式机和服务器机器使用 4kB 页面。MMU 在称为TLB的表(这就是您所说的“进程内存映射”)中查找虚拟页号。TLB 表示该虚拟页面对应的物理页面的编号。然后 MMU 从内存中的物理页获取数据。

如果 TLB 不包含此特定虚拟页号的条目,则 MMU 通知处理器发生了无效访问;这通常称为异常。

请注意,到目前为止我还没有提到操作系统。那是因为所有这些操作都独立于操作系统。操作系统发挥作用是因为它以两种方式配置事物:

  • 操作系统负责切换任务。当它这样做时,正如您所怀疑的那样,它会保存当前的 TLB 并用保存的 TLB 替换它以用于下一个计划任务。这样,每个进程都有一个 TLB,因此0x123456进程 X 中的地址可能与进程 Y 中的同一地址指向 RAM 中的实际位置不同,或者可能只是无效。如果一个进程试图在其地址空间之外取消引用一个指针,它不会到达另一个进程的空间,而是无处可去

  • 操作系统决定在引发异常时会发生什么。它可以终止执行无效内存访问的进程(分段错误、一般保护错误……)。这也是实现交换的方式:异常处理程序可能决定从交换空间中获取一些数据,相应地更新 TLB 并再次执行访问。

请注意,MMU 提供安全性,因为该进程无法更改其自己的 TLB。只有操作系统内核可以更改 TLB。TLB 更改权限的工作方式超出了本答案的范围。


Ric*_*lka 6

1) 段错误由内存管理单元检测。当您请求内存时,操作系统会要求内存管理单元从硬件中获取一些内存。必须有一些东西可以跟踪操作系统为您提供的所有大内存块。操作系统将其交给 MMU。因为它知道它给你的所有内存,它也可以告诉你何时尝试访问一个你没有从分配中获得的内存位置,操作系统专门为此有一个事件,你不拥有的内存。最终操作系统会杀死您的应用程序,触发段错误或其他操作系统上的等效程序。

并非所有操作系统都有这种保护。MacOS 最高 9 没有任何这些,即使 MMU 确实支持这一点。Win 3.1 也没有。Win95 有一些保护,因为它在没有保护和添加一些保护之间转换。

2)操作系统不知道除此之外的任何细节。如果您有一个访问从未分配过的内存的杂散指针,它就会知道。如果你有一个进入你的应用程序的另一部分,它当然不知道。它可以让你破坏这个。这就是您得到损坏堆栈的地方,来自您的应用程序的杂散指针覆盖了您应用程序的其他部分。

所以,是的,你可以搞砸自己的数据。如果你有一个覆盖你自己的应用程序的杂散指针,你希望你击中了你的堆栈,因为当你尝试从堆栈中返回时,这可能会导致另一个违规,但如果你击中自己的数据,你永远不会知道。

您可以尝试比“无保护”更严格,有一个名为 Electric Fence ( http://perens.com/FreeSoftware/ElectricFence/ )的工具会诱使您的 MMU 多工作一点,并使其检测到更多故障。