操作系统(最好是 Linux)如何知道您访问了不允许访问的内存位置?
这个问题就是受那些该死的指针启发而来的!我的看法是:计算机中的一切都是速度、安全性、完整性等之间的妥协。
我很清楚 Linux 中的内存映射,但每次访问时内核都会检查您尝试访问的位置是否位于有效范围内,这听起来有点荒谬。这听起来会浪费很多时间,这些时间可以用来做一些更有成效的事情(但如果不检查,可能不太安全!)。或者也许它会记住所有最近的访问并在每个硬件计时器滴答声上检查它们?(但这听起来不安全,而且速度又慢。)
我很惊讶这个问题似乎没有得到任何解答。我一直想知道这个问题。这让我想到有一部分硬件可以代表操作系统完成这项工作,以一种方便的抽象级别。但是,它可能仍然需要在每次上下文切换时加载下一个进程的内存映射,这听起来又很慢。
是的,无论如何,我要继续说下去:操作系统如何检测内存违规?
谢谢
答案1
(以下答案假设使用“现代”桌面、服务器或高端嵌入式平台(例如智能手机,以及越来越多的小型系统)。对于 x86 系统,现代意味着 386 及以上。以下答案还假设使用“现代”操作系统,例如几乎任何 unix,或 95 年以来的 Windows。)
这不是发生在操作系统中,而是发生在处理器中,特别是在内存管理单元(内存管理单元). MMU 支持虚拟寻址,即组成指针的位并不直接指示位在内存中的物理位置。
在典型的 MMU 中,当指针被取消引用时,MMU 会将位分为两组:高位构成页页码,低位组成页内的地址。大多数台式机和服务器使用 4kB 页。MMU 在名为传输层协议(这就是所谓的“进程内存映射”)。TLB 指示与此虚拟页面对应的物理页面的编号。然后,MMU 从内存中的物理页面中获取数据。
如果 TLB 不包含此特定虚拟页号的条目,则 MMU 会通知处理器发生了无效访问;这通常称为异常。
请注意,到目前为止我还没有提到操作系统。这是因为所有这些操作都独立于操作系统。操作系统之所以发挥作用,是因为它以两种方式配置事物:
操作系统负责切换任务。当它这样做时,正如您所怀疑的那样,它会保存当前的 TLB,并将其替换为下一个计划任务的已保存 TLB。这样,每个进程都有一个 TLB,因此
0x123456
进程 X 中的地址可能不会指向与进程 Y 中相同地址相同的 RAM 实际位置,或者可能只是无效。如果一个进程试图取消引用其地址空间之外的指针,它不会进入另一个进程的空间,而是进入无处。操作系统决定在发生异常时会发生什么。它可以终止执行无效内存访问(分段错误、一般保护错误等)的进程。这也是实现交换的方式:异常处理程序可能会决定从交换空间中获取一些数据,相应地更新 TLB 并再次执行访问。
请注意,MMU 提供安全性,因为进程无法更改其自己的 TLB。只有 OS 内核可以更改 TLB。TLB 更改权限的工作原理超出了本回答的范围。
答案2
1) 段错误由内存管理单元检测。当您请求内存时,操作系统会要求内存管理单元从硬件中获取一些内存。必须有某种东西来跟踪操作系统给您的所有大内存块。操作系统会将其交给 MMU。由于它知道它给您的所有内存,因此当您尝试访问您未从分配中获得的内存位置时,它也可以告诉您,操作系统专门为此设置了一个事件,即您不拥有的内存。最终,操作系统会终止您的应用程序,在其他操作系统上触发段错误或等效事件。
并非所有操作系统都具有这种保护。MacOS 9 之前没有任何这种保护,尽管 MMU 确实支持这种保护。Win 3.1 也没有。Win95 有一些保护,因为它在无保护和添加保护之间过渡。
2) 除此之外,操作系统不知道任何其他细节。如果您有一个杂散指针访问您从未分配的内存,它知道。如果您有一个进入应用程序另一部分的指针,它当然不知道。它让您破坏这一点。这就是您获得损坏堆栈的地方,来自应用程序的杂散指针会覆盖应用程序的其他部分。
所以,是的,你可以搞砸自己的数据。如果你有一个覆盖你自己应用程序的流浪指针,你希望你击中了你的堆栈,因为当你试图从堆栈返回时,这可能会导致另一个违规行为,但如果你击中了你自己的数据,你永远不会知道。
你可以尝试比“没有保护”更严格,有一种称为“电围栏”的工具(http://perens.com/FreeSoftware/ElectricFence/) 将会欺骗您的 MMU 进行更多的工作,并使其检测到更多的故障。