操作系统如何阻止程序访问内存



我目前的理解是,

  • 我可以用C 编写操作系统

  • 我可以用C 为那个操作系统写一个程序

  • 当我写操作系统时,我可以看到所有的内存

  • 当我写一个程序时,操作系统会对其他程序隐藏内存。

  • 每当一个程序在操作系统中运行时,它对程序来说就好像分配的内存是计算机拥有的所有内存

CPU/OS是如何实现这一点的?这纯粹是在软件级别上实现的吗?或者它也需要硬件实现吗?

它不是纯粹的软件级别。用几句话描述英特尔体系结构:

每个进程的地址空间是隔离的;每个进程都有相同的虚拟地址空间(让我们简化一下:0x00000000到0xffffffff),映射到不同的物理位置。

地址空间表示内存页的集合。页面仅在需要时进行物理映射。长时间未访问的页面(有特殊算法)会从物理内存中删除;如果它们包含动态修改的内容,则会存储在硬盘上的"交换"文件中。

每个页面都属于特定的进程(某些系统页面除外),都分配了虚拟地址和访问标志:读/写/执行。看起来是连续阵列的东西,可以分配在几个不连续的页面上,其中一些页面现在甚至可以交换到硬盘上。

程序(进程)只能看到自己的地址空间。有几种方法可以到达其他进程的空间,但常规程序很少这样做。

地址空间是不完全可访问的:如果程序将尝试访问未分配的地址,或写入受保护的页,将触发内存冲突。

通常,程序只能在自己的地址空间中分配、取消分配或更改页面的访问标志。内存有多种类型(用于加载可执行映像、堆栈和几种不同类型的可分配内存)。

对不起,我不记得书名了,很久以前就读过了。

操作系统如何阻止程序访问内存?

简单答案:在x86处理器上,它们通过激活保护模式(32位)或长模式64位)来实现这一点。ARM或其他处理器实现类似的概念。保护模式保护不同进程的内存空间,使每个进程都有自己的内存空间。这个概念被称为虚拟内存

在硬件中,这是通过MMU(用于存储器)或IOMMU(用于IO存储器)来实现的,其阻止对存储器空间的某些区域的访问。

CPU/OS是如何实现这一点的?这纯粹是在软件级别上实现的吗?或者它也需要硬件实现吗?

如上所述,这最好在硬件中实现,以提高效率。它不能(有效地)纯粹在软件级别上完成。

作为高级读者的思想实验:
尝试在真实模式中实现进程隔离(防止另一个进程访问该进程的内存)。

一个(合理的)答案:
我所知道的软件实现的唯一方法是虚拟机,它检查内存访问的所有边界(所有指令的边界),这基本上就是MMU所做的。

当前常见的解决方案是使用MMU,即内存管理单元。不需要只考虑英特尔或arm。

您可以查找术语虚拟内存和物理内存,尽管术语虚拟内存的使用存在问题。

物理内存是从0x000…0000到0xFFF…的处理器地址空间。。。FFF,但是地址有许多位。

虚拟内存不需要单独的处理器模式,但在一般实现中需要,这允许在内核(如果愿意的话,可以是操作系统)和应用程序之间进行隔离。在处理器和mmu之间的核心地址总线上,会显示id以及地址和数据。操作系统设置mmu表,mmu表定义虚拟内存块并描述物理地址。因此,特定应用程序的0x00000000的16K字节的虚拟地址块可能映射到物理内存中的0x12300000。对于相同的应用程序0x00004000可能映射到0x321000等等,这使得操作系统的内存分配更加容易,如果你想分配兆字节的内存,它不必找到线性/对齐的空闲内存块,而是可以用较小的未分配/空闲内存块构建它。这允许应用程序认为它可以访问处理器内存空间的很大一部分。

有不同的设计实现,但为了在操作系统和应用程序之间进行保护,总线上使用的id区分了应用程序和操作系统。如果总线事务包含id和id无法访问的地址的组合(每个块都有访问/保护位,以某种形式指示id是否可以访问该虚拟地址),则mmu会生成一个故障,该故障是以处理器特定的方式对处理器的某种异常/中断,将处理器切换到受保护/内核模式,并命中中断/异常处理程序。

这不一定是坏事。例如,当运行虚拟机而不是应用程序时,可以有意地设计虚拟机软件,以使特定的虚拟地址是某个外围设备的仿真,例如以太网控制器,从而使VM可以访问网络。当应用程序到达该地址时,就会发生故障,但您并没有关闭应用程序并通知用户有问题,而是基于该地址,通过对应用程序无法区分实际外围设备的结果做出反应或将结果返回给应用程序来模拟外围设备。故障的另一个特征是外行(而不是程序员/软件/硬件工程师)版本的虚拟内存。

这就是你的应用程序可能认为它可以访问所有计算机内存的地方。应用程序可能已经用完了系统中的所有可用内存(RAM)。但在他们的虚拟地址空间中,他们中没有一个人真正做到了这一点,在某一点上,应用程序可能已经将物理0x11100000分配给了虚拟0x20000000,但系统需要分配内存,而且没有更多可用的内存。操作系统可以使用算法来确定该应用程序已经有一段时间没有使用其空间,或者更可能是随机抽奖,标记虚拟0x20000000,以便在访问时出错,并为当前内存分配请求提供物理0x11100000(可以是同一应用程序,也可以是不同的应用程序)。

当这个应用程序出现并访问0x20000000的内存块时,操作系统会发现错误,选择其他内存块,将其保存到磁盘,将其标记为错误,获取这个应用程序的内容0x20000000将其从磁盘中取出,放在ram中,释放错误,应用程序继续运行。这就是为什么当你的系统内存不足时,性能会跌落悬崖;交换;内存有时也称为虚拟内存。

如果有mmu,并且处理器设计用于操作系统,那么理想情况下,有一种快速切换mmu表的方法。对于一个单线程处理器来说,为了简化这一点,一次只能运行一件事,即使用户觉得有很多事情在发生,一次只运行一组指令,它们来自操作系统中的特定应用程序或处理程序。每个处理器id都需要一个mmu表——每个应用程序和内核本身(通常情况下,你不会关闭mmu——你只是让内核完全访问内存空间,或者mmu知道没有检查特定的id,这是mmu/系统的设计所特有的)。mmu表存在于内存中,但mmu不必经过自身就可以到达那里——这不是鸡和蛋的事情,操作系统根本不会将内存分配给任何人,mmu可以将id和虚拟地址的上半部分结合起来查找mmu表条目,也可以在单线程系统中有一个活动表,操作系统切换使用哪个表或哪个id可以访问块,或者这样想,一个单线程系统可能只有两个id。这里太模糊了,你需要看看特定的处理器/架构/实现,看看它是如何工作的,处理器模式是如何工作,从中生成了什么id,mmu是如何对这些做出反应的,等等

另一个让我们所有人的生活更轻松的功能是,这也允许应用程序A的程序在0x00000000,应用程序B的程序在(虚拟地址)0x00000000和应用程序C的程序在0x0000000,因为它们的物理地址都在不同的地方。但我们现在可以为该操作系统编译程序,使它们在相同的内存空间中运行。在mmu之前或没有mmu,那么1)您可能没有受到保护,但2)您肯定仍然可以拥有带有应用程序的操作系统。

您需要让操作系统移动内存或强制执行与位置无关的代码,以便在启动时,每个应用程序都在已知地址启动,但操作系统已经移动/交换了另一个应用程序,或者与位置无关,并且每个应用程序在不同的空间启动。为了支持内存分配,操作系统需要更加努力地跟踪,并尝试使用一种算法来避免碎片,有时在应用程序重新分配时必须复制数据。

最新更新