为什么第一条 BIOS 指令位于 0xFFFFFFF0(RAM 的“顶部”)?

Fer*_*ini 60 bios memory boot kernel

我知道 BIOS 从 0xFFFFFFF0 加载它的第一条指令,但为什么是这个特定地址?我有很多问题,希望您至少可以帮助我解决其中的一些问题。

我的问题:

  • 为什么第一个 BIOS 指令位于 4 GB RAM 的“顶部”?
  • 如果我的电脑只有 1 GB 的 RAM 会发生什么?
  • 具有超过 4 GB RAM(例如,8 GB、16 GB 等)的系统呢?
  • 为什么用某个值(在本例中为位于 0xFFFFFFF0 处的值)初始化堆栈?

我今天下午已经读过,但我仍然不明白。

Law*_*ceC 62

0xFFFFFFF0是 x86 兼容的 CPU 在开机时开始执行指令的地方。这是 CPU 的一个固定的、不可改变的(没有额外的硬件)方面,不同类型的 CPU 表现不同。

为什么第一条 BIOS 指令位于 4 GB RAM 的“顶部”?

它位于 4 GB地址空间的“顶部” - 开机时 BIOS 或UEFI ROM 设置为响应这些地址的读取。

我关于为什么会这样的理论:

编程中的几乎所有内容都可以通过连续地址更好地工作。CPU 设计者不知道系统构建者想用 CPU 做什么,因此 CPU 要求空间中间的地址用于各种目的是一个坏主意。最好在地址空间的顶部或底部保持“不碍事”。当然,请记住这个决定是在 8086 是新的时候做出的,它没有MMU

在 8086 中,中断向量存在于内存位置 0 及以上。中断向量需要位于已知地址,并且为了灵活性而希望位于 RAM 中 - 但 CPU 设计人员不可能知道系统中将有多少 RAM。因此,从 0 开始对那些人来说是有意义的(因为 1978 年发明 8086 时没有系统会有 4 GB 的 RAM - 因此期望 RAM 为 0xFFFFFFF0 不是一个好主意),然后 ROM 必须是在上边界。

当然,至少从 80286 开始,中断向量可以移动到除 0 以外的其他起始位置,但是现代 64 位 x86 CPU 仍然以 8086 模式启动,所以为了兼容性,一切仍然以旧方式工作(荒谬听起来在 2015 年仍然需要​​您的 x86 CPU 才能运行 DOS)。

因此,由于中断向量从 0 开始并向上工作,因此 ROM 必须从顶部开始向下工作。

如果我的计算机只有 1 GB 的 RAM 会发生什么?

一个 32 位 CPU 有 4,294,967,296 个地址,编号为 0 (0x00000000) 到 4294967295 (0xFFFFFFFF)。ROM 可以存在于某些地址中,而 RAM 可以存在于其他地址中。使用 CPU 的 MMU,这甚至可以即时切换。RAM 不必位于所有地址。

只有 1 GB 的 RAM,某些地址在读取或写入时不会有任何响应。这会导致在访问此类地址或系统锁定时读取无效数据。

具有超过 4 GB RAM(例如:8 GB、16 GB 等)的系统呢?

保持一些简单:例如,64 位 CPU 有更多地址(这是使它们成为 64 位的原因之一 - 例如 0x0000000000000000 到 0xFFFFFFFFFFFFFFFF),因此额外的 RAM“适合”。假设 CPU 处于长模式。在那之前,RAM 就在那里,只是不可寻址。

为什么用某个值(在本例中为位于 0xFFFFFFF0 处的值)初始化堆栈?

我无法立即找到有关 x86 在开机时分配堆栈指针的任何信息,但是一旦该例程发现系统中有多少 RAM,它最终必须由初始化例程重新分配。(@Eric Towers 在下面的评论中报告说它在加电时设置为零。)

  • 此答案假设 CPU 在 16 位模式下可以使用 32 个地址位。但是在 16 位模式下它只能使用 20 个地址位。在 CPU 切换到 32 位模式之前,地址“0xFFFFFFF0”无法访问。上次我仔细查看 BIOS 代码时,入口点是“0xFFFF0”。 (19认同)
  • @kasperd 有一个黑客 - 内存管理器将高 12 位设置为 1,直到发生第一次跳远。所以是的,从逻辑上讲,您正在使用“0xFFFF0”,但实际上,它映射到“0xFFFFFFF0”。我希望这样做是为了与 8086 兼容 - 它和更现代的 CPU *似乎*使用`0xFFFF0`,但 32 位 CPU 实际上访问 `0xFFFFFFF0`(映射到 BIOS ROM)。 (9认同)
  • 最好将地址空间视为一个大空间,硬件可以在其中分配事物。当 CPU 读/写内存时,它实际上是通过总线进行通信,硬件可以确保 RAM 或 ROM 之类的东西在特定的地址范围内响应。因此,当 CPU 复位时,此类硬件必须确保 ROM 以 0xFFFFFFF0 响应。ROM 紧接着 RAM 出现并没有固有的义务。它也可以出现在硬件告诉它的任何地方,具体取决于此类硬件的功能。 (7认同)
  • @MichaelKjörling 你的计算是错误的。移位段和偏移量不是 OR 运算,而是相加。因此,逻辑 FFFF:FFF0 是物理 (1)0FFE0(如果启用 A20,则存在前导 1)。 (6认同)
  • 可能有 ROM、RAM 或任何东西都没有使用的“漏洞”或未分配的空间——通常访问这些会导致系统锁定。 (4认同)
  • @FernandoPaladini 进行“完整”阅读尝试 http://www.drdobbs.com/parallel/booting-an-intel-architecture-system-par/232300699?pgno=2 ,其中包含启动 CPU 驱动器时的小细节前 12 个地址位高(将 20 位实模式地址扩展为 32 位硬件地址)。 (4认同)
  • 这个答案有几个问题。旧处理器的“32 位”特性限制了虚拟或线性地址空间,但不限制 RAM。启用 PAE 的 32 位处理器可以处理高达 64 GB 的 RAM。x64 处理器最多可以处理 52 位 RAM 地址,这意味着 4 _PB 的 RAM。即使它处于传统模式(即运行 32 位操作系统)也是如此。 (2认同)
  • 32 位 Windows 客户端版本默认以 PAE 模式运行相当长一段时间(我认为是从 Vista 开始),因为它们需要它来实现 NX。但它们仍然不支持 0xFFFFFFFF 以上地址的 _RAM_,因为某些视频驱动程序无法处理它。 32 位服务器版本(以 Server 2008 结束)仍然最高可达 64 GB,尽管如果您使用 x64 CPU,硬件确实支持更多容量。 https://msdn.microsoft.com/en-us/library/aa366778.aspx (2认同)

psu*_*usi 28

它不位于 RAM 的顶部;它位于 ROM 中,其地址位于内存地址空间的顶部,以及扩展卡上的任何内存,如以太网控制器。它在那里,所以它不会与 RAM 冲突,至少在您安装 4 GB 之前。具有 4 GB 或更多 RAM 的系统可以通过两件事来解决冲突。便宜的主板会忽略与 ROM 所在位置冲突的 RAM 部分。体面的人重新映射该 RAM 以使其地址高于 4 GB 标记。

我不确定你在问什么关于堆栈。它当然没有被初始化为在 ROM 中。当 CPU 复位时,它最初处于“实模式”,它的行为就像原来的 8086 一样,使用 16 位分段寻址,只允许它访问 1 MB 的内存。BIOS 代码位于该 1 MB 的顶部。BIOS 选择 RAM 中的某个位置来设置堆栈并加载并执行第一个可引导驱动器的第一个扇区。一旦接管并设置自己的堆栈(每个任务/线程一个),由操作系统切换到 32 位或 64 位模式。


Lua*_*aan 18

首先,这真的与 RAM 无关。我们在这里讨论的是地址空间——即使您只有 16 MiB 的内存,您在 32 位 CPU 上仍然拥有完整的 32 位地址空间。

这已经回答了你的第一个问题,真的 - 在设计它的时候,现实世界的 PC 还没有接近完整的 4 GiB 内存;它们更多地在 1-16 MiB 的内存范围内。出于所有意图和目的,地址空间是免费的。

现在,为什么是 0xFFFFFFF0 呢?CPU 不知道有多少 BIOS。一些 BIOS 可能只需要几千字节,而其他人可能需要整兆字节的内存——我什至没有进入各种可选的 RAM。CPU 必须硬连线到某个地址才能启动 - 没有人可以配置 CPU。但这只是地址空间的映射——地址直接映射到 BIOS ROM 芯片中(是的,这意味着如果你有那么多内存,此时你无法访问完整的 4 GiB 内存——但是这没什么特别的,许多设备都需要自己的地址空间范围)。在 32 位 CPU 上,该地址为您提供了完整的 16 个字节来进行非常基本的初始化——这足以设置您的段,如果需要,还可以设置地址模式(请记住,真正的启动“程序”。在这一点上,您根本不使用 RAM - 它只是映射 ROM。事实上,此时 RAM 甚至还没有准备好使用——这是 BIOS POST 的工作之一!现在,您可能在想 - 16 位实模式如何访问地址 0xFFFFFFF0?当然,有段,所以你有 20 位地址空间,但这仍然不够好。嗯,有一个技巧——地址的高 12 位被设置,直到你执行你的第一次长跳转,让你访问高地址空间(同时拒绝访问低于 0xFFF00000 的任何内容——直到你执行一个长跳转) .

所有这些都是现代操作系统上大部分对程序员(更不用说用户)隐藏的东西。您通常无法访问如此低级的任何内容 - 有些东西已经无法挽救(您无法随意切换 CPU 模式),有些则由操作系统内核专门处理。

所以更好的观点来自 MS DOS 上的老式编码。设备内存直接映射到地址空间的另一个典型例子是直接访问视频内存。例如,如果您想快速将文本写入显示器,您可以直接写入地址B800:0000(加上偏移量 - 在 80x25 文本模式下,这意味着(y * 80 + x) * 2如果我没记错的话 - 每个字符两个字节,一行一行)。如果要逐像素绘制,则使用图形模式和起始地址A000:0000(通常为 320x200,每像素 8 位)。做任何高性能的事情通常意味着深入研究设备手册,弄清楚如何直接访问它们。

这一直存在到今天 - 它只是隐藏起来。在 Windows 上,您可以在设备管理器中看到映射到设备的内存地址 - 只需打开网卡之类的属性,转到资源选项卡 - 所有内存范围项目都是从设备内存到主地址空间的映射。在 32 位上,您会看到大多数设备都映射到 2 GiB(后来是 3 GiB)标记之上——同样,为了尽量减少与用户可用内存的冲突,尽管这并不是虚拟内存的真正问题(应用程序无法接近真实的硬件地址空间——它们有自己的虚拟化内存块,例如可以映射到 RAM、ROM、设备或页面文件)。

至于堆栈,嗯,应该有助于理解默认情况下,堆栈从顶部增长。因此,如果您执行 a push,则新堆栈指针将位于0xFFFFFEC- 换句话说,您不会尝试写入 BIOS init 地址 :) 这当然意味着 BIOS init 例程可以在重新映射之前安全地使用堆栈更有用的地方。在老式编程中,在分页成为事实上的默认设置之前,堆栈通常从 RAM 的末尾开始,当您开始覆盖应用程序内存时,就会发生“堆栈溢出”。内存保护改变了很多,但总的来说,它尽可能地保持向后兼容性 - 请注意即使是最现代的 x86-64 CPU仍然可以启动 MS DOS 5 - 或者 Windows 如何仍然可以运行许多不知道分页的 DOS 应用程序。

  • 很好的答案,只是为了扩展并说现代处理器开始放弃像 [A20 line](https://en.wikipedia.org/wiki/A20_line) 掩码这样的黑客攻击,因此对旧边缘情况的支持正在消亡。 (3认同)
  • 最后一段:BIOS 不能“自由地”使用堆栈:它不能写入 ROM(`0xFFFFFFEC` 将被映射到该 ROM)。这意味着不仅没有`push`,而且也没有`call`。这些必须等到 RAM 准备好。 (2认同)

sup*_*cat 10

除了上面提到的其他点,它可能有助于了解一个地址什么。虽然较新的架构使事情复杂化,但从历史上看,一台机器会在每个内存周期输出 20 到 32 条线路上的所需地址(取决于架构,有一些特殊的技巧来注意它是否需要同时生成一对或四个字节);记忆系统的各个部分会检查这些电线的状态,并在它们看到某些高低值组合时激活自己。

如果一台有 32 条地址线的机器只需要使用 1MB 的 RAM 和 64KB 的 ROM [对于某些嵌入式控制器来说非常合理],它可能会激活顶部地址线为低电平的所有地址的 RAM 和它所在的所有地址的 ROM高的。然后将底部 20 个地址线连接到 RAM 以选择 1,048,576 个字节之一,底部 16 个地址线也将连接到 ROM,以选择 65,536 个字节之一。剩下的 11 条地址线将根本不连接任何东西。

在这样的机器上,访问地址 0x00100000-0x001FFFFF 相当于访问 RAM 地址 0x00000000-0x000FFFFF。与地址 0x000200000-0x0002FFFFF 或 0x7FF00000-0x7FFFFFFFF 类似。0x80000000 以上的地址都将读取 ROM,在整个空间中重复 64K 模式。

即使处理器有 4,294,967,296 字节的地址空间,也没有必要让硬件识别许多不同的地址。将复位向量放在地址空间顶部附近是一种无论系统有多少 RAM 和 ROM 都可以很好地工作的设计,并且避免了对地址空间进行完全解码的需要。