我正在尝试了解平面内存模型和分段内存模型。
平面内存模型和分段内存模型是由 CPU 架构还是操作系统或两者决定的?
或者它们不是由 CPU 架构和操作系统决定的,而是任何具有任何 CPU 架构和已安装操作系统的计算机上的程序员可用的选项?
如果是后者,哪些类型的编程需要考虑平面内存模型和分段内存模型?汇编语言、C 语言和/或其他高级编程语言?
以下是维基百科中一些令我困惑的引述。
从http://en.wikipedia.org/wiki/Address_space#Memory_models:
早期的 x86 计算机使用分段内存模型地址,该地址基于两个数字的组合:内存段和该段内的偏移量。一些段被隐式地视为代码段,专用于指令、堆栈段或普通数据段。尽管用途不同,但这些段没有反映这一点的不同内存保护。
现在,许多程序员更喜欢使用平坦内存模型,其中所有段(段寄存器)通常都设置为零,并且只有偏移量是可变的。
从http://en.wikipedia.org/wiki/Flat_memory_model#Comparison:
平面内存模型
- 不适用于通用计算或多任务操作系统,除非使用额外的内存管理硬件/软件进行增强;但现代 CISC 处理器几乎总是如此,它们在平面内存模型上实现了高级内存管理和保护技术。例如,Linux 使用平面内存模型比较 X86_memory_segmentation#Practices。
分段内存模型
- 在最初的 Intel 8086、8088、80186、80286 中实现,并由 80386 和所有后续 x86 机器支持,直到现在的 Pentium 和 Core 2 处理器。此内存模型一直保留在 x86 机器中,这些机器现在提供多模式操作,并且很少以兼容分段模式运行。
答案1
使用的内存模型由两件事决定:
- 正在使用的 CPU 以及它支持的模式
- 操作系统及其用途
从程序员的角度来看,除非您正在处理内核代码,否则您将获得操作系统提供的内容。在大多数现代操作系统中,操作系统都采用分页内存模型。
对于在用户空间的现代操作系统上执行的代码,内存模型就流程而言是平面的。这是因为操作系统为进程提供了一个虚拟内存空间,隐藏了所有分页、交换和可能发生在内存上的其他事情。这个抽象层非常有用,因为它不会强迫程序员为了整个系统的利益而巧妙地使用内存。
从历史上看,情况并非总是如此。
Windows 3.1x、MacOS 最高版本 v9 和 Novell NetWare 都缺少一些我们在 Linux/Windows/OSX 中习以为常的内存保护。具体来说,NetWare 为所有正在运行的代码提供了一个很大的内存空间,因此,一个试图访问未实际分配的内存的错误实际上可以获取该内存,并且通常会使整个系统崩溃。出于这个原因,为 NetWare 编程的程序员必须非常小心地进行内存管理,而 Linux/windows/OSX 程序员则不必为此操心。
尽管 NetWare 使用了分页内存模型,但它并没有为每个进程提供虚拟内存空间,而是为其提供了实际的内存空间。
使用的内存模型并不是对所有编程都灵活,但最接近硬件。内核编程和嵌入式设备编程是此类事情非常重要的领域。
答案2
“内存模型”既是一个低级的概念,又是一个(相对)高级的概念。
在过去,使用分段内存模型的处理器与使用“分页”或“映射”内存模型的处理器之间存在着一场“战争”。Burroughs B5000 系列机器和 Plessey 250 使用具有“功能”(或“描述符”)的分段内存模型。也就是说,物理存储不是作为概念上的固定大小页面进行管理,而是作为可变长度的段进行管理,每个段对应于某个逻辑实体(例如,过程或对象)。为了在段之间寻址,使用了“功能寄存器”,该寄存器只能以受保护的方式加载,其中包含段的物理地址、段的长度以及执行程序被授予引用该段的授权(例如,读/写/执行)。
当“分页”系统仍在努力启动时,这些系统已经顺利运行。
最初的 PC 是基于 8086 处理器的,该处理器旨在支持低端分段架构。我记得当时有四个段寄存器,可以选择将它们添加到 16 位通用寄存器值中以生成 20 位地址。理论上,这些寄存器将由软件管理,类似于 Burroughs 和 Plessey 系统管理它们的方式,但硬件支持稍多一些。但在开发出利用此功能的优质软件之前,DOS 被嵌入到 8086 中,因此该功能从未真正得到有效利用。
至于哪个更好,嗯,这已经不重要了,因为分段模型不存在于任何“真实”(非实验)环境中。但在过去,分段模型通常表现更好,并且允许操作系统更强大。主要的负面因素是它对编译器施加了限制,并且在一定程度上对程序员施加了限制,这与当时和现在许多程序员的“狂野西部”态度不一致。
“分页”模型
“分页”模型假设地址空间被划分为一定大小的“页面”(尽管在某些情况下支持几种不同的页面大小)。通常,页面大小介于 256 字节和 64k 字节之间(始终为 2 的幂)。还假设硬件包含某种地址转换支持,以便“逻辑”地址(程序“地址空间”中的地址)可以映射到“物理”地址(RAM 中的地址)。
实现分页模型主要有两个原因:
- 允许多个不同的线程/进程具有单独的地址空间,每个地址空间从“零”(或其他标准地址)开始,而不需要操作系统将线程/进程的整个可能的地址空间预先分配为一个连续的块。
- 通过允许逻辑地址空间中的各个页面“分页”到磁盘来启用“虚拟内存”,以便在程序尝试引用它们时稍后重新加载。
页面转换硬件还有一些可使用的次要功能,如设置读/写/执行权限、允许某些页面在进程/线程之间“共享”等。
“扁平”模型
忽略哈佛模型(它可以说是第一个计算机内存模型),平面冯·诺依曼存储模型是第一个得到普遍使用的模型。这基本上就是——一个“平面”地址空间,由不区分的字(直到游戏后期才变成字节)组成,从地址零开始,一直向上延伸到可用内存的“顶部”。内存“底部”或“顶部”的某些内存部分将保留给某种“加载器”,其余部分可供单个执行程序使用。每次运行一个程序,程序可以使用除那一小块保留的 RAM 区域之外的所有内存。
慢慢地,事情发生了变化,小的保留区域变大,可以容纳各种操作系统,但一次只能容纳一个程序。随着时间的推移,人们发明了各种技巧,例如允许某些专用程序在内存的“角落”运行,允许假脱机程序等与用户程序共存。
但“多道程序设计”的推动正在产生压力。一些系统添加了粗糙的地址映射硬件,以使多个程序各自拥有自己的地址空间。其他系统要求程序“自我重定位”,以便它们可以在内存中的任何位置运行,而不是“链接”到特定地址。
早期的 Unix(以及其他系统)通过“交换”程序来处理多道程序工作:一个程序(加载在地址零)将运行,直到它“阻塞”以进行 I/O,然后它将被“交换”(完整地,包括所有代码和数据)到磁盘并且另一个不同的程序被“交换”进来。
但在大多数情况下,这些技术慢慢地演变(或许是退化)为各种形式的页面映射存储模型。
答案3
我是一名程序员,但不是一名实际上必须处理此级别内存模型的嵌入式程序员,因此请谨慎对待我所说的话。
我的理解是,内存模型由处理器架构决定。它们与使用汇编语言、低级 C 或任何接近硬件的语言的程序员最相关。对于许多高级语言(如 Java 或 C#),存在抽象来隐藏这些细节,因此您通常不必考虑硬件内存模型。
有时会出现奇怪的情况。据我所知,32 位 x86 处理器(至少在过去十年中)可以使用段访问超过 4GB 的内存(PAE 模式),许多 32 位操作系统都支持它,但是单个进程可以使用的最大内存量限制为 4GB,因为大多数程序都期望平坦内存模型(并且我怀疑许多现代操作系统会允许它们期望其他任何东西)。
澄清引述:
早期的 x86 处理器的内存模型很像一组编号的抽屉,抽屉内有编号较小的隔间。要访问某个内存位,您必须指定抽屉编号和隔间编号。所有抽屉都相同,有些程序员会决定使用一个抽屉来存放代码,另一个抽屉来存放数据等,但这些决定并不是由处理器架构决定的。平面内存模型就像只有一个抽屉,因此您只需使用隔间编号即可。
如果没有额外的功能(例如防止一个程序使用另一个程序的内存的功能),平面内存模型就不适合现代操作系统,但几十年来所有现代桌面/服务器处理器都具备这些功能。
所有 x86 处理器都具有像 8086、80286 等一样的内存访问模式,以实现向后兼容,但它们已经过时了。几乎没有人再使用它们,它们总是切换到更现代的模式。
希望这可以帮助。
答案4
内存分段是将计算机的主内存划分为段或部分。当编译程序的目标文件链接在一起形成程序映像或将映像加载到内存中时,也会使用段或部分。在使用分段的计算机系统中,对内存位置的引用包括标识段的值和该段内的偏移量。可以为不同的程序模块或不同类别的内存使用(例如代码段和数据段)创建不同的段。某些段甚至可以在程序之间共享