我知道BIOS从0xFFFFFFF0加载其第一条指令,但是为什么要使用这个特定地址?我有很多问题,希望您至少可以帮助我。
我的问题:
- 为什么第一条BIOS指令位于4 GB RAM的“顶部”?
- 如果我的计算机只有1 GB RAM,将会发生什么?
- 拥有超过4 GB RAM(例如8 GB,16 GB等)的系统怎么办?
- 为什么用某个值(在这种情况下,值为0xFFFFFFF0)初始化堆栈?
我今天下午已经读到了那本书,但我仍然不明白。
我知道BIOS从0xFFFFFFF0加载其第一条指令,但是为什么要使用这个特定地址?我有很多问题,希望您至少可以帮助我。
我的问题:
我今天下午已经读到了那本书,但我仍然不明白。
Answers:
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在下面的评论中报告它在加电时设置为零。)
0xFFFFFFF0
在将CPU切换到32位模式之后,该地址才可访问。上次我仔细查看BIOS代码时,入口位于0xFFFF0
。
0xFFFF0
,但实际上,它映射到0xFFFFFFF0
。我希望这样做是为了与8086兼容-它和更现代的CPU 似乎都可以使用0xFFFF0
,但实际上是32位CPU可以访问0xFFFFFFF0
(映射到BIOS ROM)。
它不位于RAM的顶部。它位于ROM中,其地址位于内存地址空间的顶部,以及扩展卡(如以太网控制器)上的所有内存。它在那里,因此至少在安装4 GB之前不会与RAM冲突。具有4 GB或更多RAM的系统可以做两件事来解决冲突。廉价的主板只是忽略与ROM所在位置冲突的RAM部分。体面的内存重新映射该RAM,使其看起来具有高于4 GB标记的地址。
我不确定您在问什么堆栈。当然,它尚未初始化为ROM。当CPU复位时,它最初处于“实模式”,其行为与原始的8086一样,并使用16位分段寻址,从而使其只能访问1 MB内存。BIOS代码位于该1 MB的顶部。BIOS选择RAM中的某个位置来设置堆栈并加载并执行第一个可引导驱动器的第一个扇区。一旦操作系统接管并设置了自己的堆栈(每个任务/线程一个),操作系统就可以切换到32位或64位模式。
首先,这与RAM无关。我们在这里谈论地址空间 -即使您只有16 MiB的内存,您在32位CPU上仍然拥有完整的32位地址空间。
确实,这已经回答了您的第一个问题-在设计时,现实世界中的PC的内存不足4 GB。它们更多的是在1-16 MiB的内存范围内。出于所有目的和目的,地址空间是免费的。
现在,为什么精确为0xFFFFFFF0?CPU不知道有多少BIOS。有些BIOS可能只占用几千字节,而另一些BIOS则需要占用整兆的内存-我什至没有进入各种可选的RAM。必须将CPU硬连线到某个地址才能启动-无需配置CPU。但这只是地址空间的映射-地址直接映射到BIOS ROM芯片中(是的,这意味着如果您有那么多的内存,此时您将无法访问完整的4 GiB RAM-但是没什么特别的,许多设备在地址空间中需要它们自己的范围)。在32位CPU上,该地址为您提供了完整的16个字节来进行最基本的初始化-这足以设置您的段,并在需要时设置地址模式(请记住,真正的启动“程序”。在这一点上,您根本不使用RAM-只是映射的ROM。实际上,RAM甚至还没有准备好被使用-这是BIOS POST的工作之一!现在,您可能在想-16位实模式如何访问地址0xFFFFFFF0?当然,有段,所以您有20位地址空间,但这仍然不够好。好吧,这有个窍门-设置地址的高12位,直到您执行第一次长跳转,才可以访问高地址空间(同时拒绝访问任何低于0xFFF00000的内容-直到执行长跳转) 。
所有这些都是现代操作系统中程序员(更不用说用户)所不愿看到的。通常,您无法访问任何低级别的东西-有些东西已经无法挽救了(您不能随意地切换CPU模式),有些则只能由OS内核处理。
因此,更好的视图来自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初始化地址:)当然,这意味着BIOS初始化例程可以在重新映射之前安全使用堆栈。更有用的地方 在老式编程中,在分页成为事实上的默认设置之前,堆栈通常在RAM的末尾开始,并且当您开始覆盖应用程序内存时发生“堆栈溢出”。内存保护在很大程度上改变了这一点,但总的来说,它尽可能地保持了向后兼容性-请注意,即使是最现代的x86-64 CPU 仍然可以引导MS DOS 5 -或Windows如何仍可以运行许多不知道分页的DOS应用程序。
0xFFFFFFEC
将被映射到ROM )。这不仅意味着没有,push
而且也没有call
。这些必须等到RAM准备好为止。
除了提到的其他要点之外,了解地址是有帮助的。虽然较新的体系结构使事情复杂化,但是从历史上看,机器会在每个内存周期上通过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都可以很好地工作,并且避免了对地址空间进行完全解码的需要。
我的想法是,因为我们使用负逻辑,所以数字一(1)根本没有张力(O伏)。初始化时只需要在最后4位施加张力,因此程序计数器(或指令指针)在1111 1111处累加1111 1111 1111 1111 11110000。我们不必处理高28位,因为大多数(旧)cpu都是16位,而较低的半字节在过去可以由一个地址芯片来解决。现在,由于我们具有64位与32位以及32位与16位兼容的兼容性,因此改进了硬件功能,但是方法仍然存在。此外,生物程序并不总是被编程为64位或32位。我的观点还在于,由于记忆并不总是相同的,因此BIOS必须位于同一第一段。我们看待BIOS的方式并不是一直都是真实的地址。只是教给我...
复位后,与8088/8086兼容的CPU在0FFFF0执行指令,该指令比1 MB的限制低16个字节。通常,此位置(在PC实施中)的ROM是BIOS,因此在BIOS ROM的末尾,有一个跳转到BIOS rom的开头。
如此处所示:起始向量及其后的“日期”签名,IBM 5150 PC 8KB eprom dump bios日期:10/19/1981
00001FEE FF db 0xff
00001FEF FF db 0xff
00001FF0 EA5BE000F0 jmp word 0xf000:0xe05b
00001FF5 3130 xor [bx+si],si
00001FF7 2F das
00001FF8 3139 xor [bx+di],di
请注意,该地址的大小为8KB $ 2000 rom,它将起始地址(绝对远的JMP,到任何其他位置,在这种情况下,位于8KB rom自身内,尽管不是该rom中的最低地址)在$ FFFF处: $ 0分段或$ FFFF0线性。
至于兼容性:如果某些“未来”或当前的处理器“期望”它在地址前面有很多F,那没关系。为了在较旧的系统中兼容较新的cpus,其他地址线保持未连接状态,因此数据总线上的数据完全相同。只要最低有效位保持为FFFF0。
(在只有1mb内存且rom位于该内存末尾的系统中,别无其他,它会很高兴地“认为”它正在与更高的地址进行通信,但却获得了完全相同的数据,因为这些实现从未听说过地址线高于A19)
请注意,世界不仅仅是“个人电脑” ... ibm个人电脑是“事故”,这些处理器从未专门为“个人电脑”设计,除个人电脑(卫星,武器系统等)。通常不希望使用32位和64位保护模式。(例如,虚拟8086模式作为选择较新版本(386+)的原因会更加有趣)。因此,“向后兼容性”不仅仅是“会运行dos”。