使用地址(内存、I/O、内存映射 I/O 等)访问某些内容的指令有时会提供完整的(从处理器执行层的角度来看)地址,有时它们会提供偏移量。例如,您的接近或相对跳转程序计数器是基地址,指令提供了该基地址的偏移量,将两者相加,您就得到了地址(在该级别)。
采用 16 位系统,其中有 16 位寄存器和 64KByte 的最大地址空间限制。扩展该内存的一种非常简单的方法是分段。指令中的寄存器不是包含整个地址的寄存器,而是包含一个基址的偏移量,很像相对于 pc 的指令。除了在这种情况下,还有另一个寄存器用作基地址。您可以在许多希望轻松扩展其地址范围而无需对内核进行太多修改的架构中看到这一点。 (可以在内存控制器中完成,无需修改内核)在 x86 的情况下,有几个寄存器。一个用于扩大执行范围,分支。另一个扩展数据访问、加载和存储的范围。非 pc 相对分支的地址是使用左移 4 位的代码段计算的,然后添加到指令中指定的寄存器中。对于与 pc 无关的加载和存储,使用了数据段寄存器,左移 4 位添加指令中指定的寄存器。所以如果你想寻址0x123456789,你可以让段寄存器包含0x12340000,用于寻址的寄存器包含0x56789,或者段0x12345678和gpr包含0x9。 pc相对寻址当然是segment + pc + offset。
这导致采用各种内存模型。小,小,中,大,大。你可以想象最小的模型会有规则,或者假设在 x86 的情况下一切都在 64K 内存空间内,编译器和你的代码永远不必担心段寄存器,它们被假定保持不变。对于较大的模型或当使用远指针到达更远没什么大不了时,您设置数据段,然后设置数据偏移并执行加载或存储。对于代码,您可以想象它有点困难,因为一旦您更改代码段寄存器,它就会影响您获取指令的整体地址。您可能需要一个硬件解决方案来允许分支修改段和偏移量,或者您可以在代码中进行(如果硬件允许)。我暂时不会把你和那个混为一谈。
只要代码中有数组:
unsigned char abc[123];
基本上是一样的。基地址,数组在内存中开始的地址就像你的段,索引是你的偏移量。如果上面的 abc 位于地址 0x1004,则 abc[5] 位于地址 0x1004+5 = 0x1009。不像 x86 段:偏移地址那样移动,而是添加基数和偏移量的相同概念。您没有添加的某些分段架构,某些寄存器中的某些位是高位。在这些系统之一上获取地址 0x12345,0x1 必须在段中,0x2345 在 16 位 gpr 中。您可以将其视为移位并根据需要添加,但与 x86 段:偏移不同,您也可以将其视为移位和或。
平坦的内存空间有点像幻觉,尤其是在 x86 系统中。 x86 计算机,32 位甚至许多 64 位,将插入卡的平面内存空间限制为总共 1Gig,对于总共有 4 Gig 地址空间的 32 位系统很有意义,并且这就是为什么其中一些给你一个 3 gig 的限制,或者给你 4 gig 的错觉,但是为了插卡而砍掉了一些。 (您在主板上的许多项目以及实际的插卡都在这个空间中)。根据视频卡和分辨率等,您有时无法将整个帧缓冲区放入该外围空间的子集中,因此您必须对访问进行分段。 BIOS 可能已经为您提供了地址 0x80000000 作为 x86 地址空间中的基地址,然后在视频卡的其他一些寄存器中,您指定了视频卡地址空间内的地址。出于演示目的,假设您在 x86 地址 0x80000000 处获得了一个 16MByte 的窗口。 16Mbytes 是 0x01000000。如果您想访问视频内存中的地址 0x04321888,您可以想象必须将视频卡中的段寄存器设置为 0x04,然后在 x86 地址空间(也是 pci(e) 地址空间)中使用地址 0x80321888。
这里的底线是从这里取一些位,从那里取一些位,把它们放在一起,这就是目标的地址。在处理外设时,无论是显卡还是板载 I/O 控制器,或者 pci 或 pcie 控制器,您都必须学会从目标地址空间的角度进行思考。从您的程序的角度来看,处理器有一个地址空间。 mmu 可以并且确实将其打乱到物理地址空间中,然后您拥有您的 pcie 地址空间,然后通过 pcie 访问的外围设备拥有自己的地址空间。英特尔和基于英特尔的 pc 世界所做的是使处理器的物理地址空间和 pcie 地址空间相同。 mmu 中的虚拟与物理加扰仍然存在,进入外围设备地址空间的窗口仍然存在,您仍然需要从这里获取一点地址,从那里获取一点地址以获得任何目标的最终地址。
真实和受保护与访问有关。例如,在 C 中,您可以创建指针、更改指针并创建您想要的任何地址,这是否意味着您可以在另一个应用程序内存或内核内存中四处寻找?理想情况下,您不想让这种情况发生,因此当您为该应用程序执行指令时,您在一个虚拟机中的每个应用程序,如果您愿意,每个内存访问(无论是代码还是指令)都会经过过滤器。该过滤器检查该访问是否在程序允许的空间内,如果它超出该空间,则发生异常(认为中断)该异常允许内核(没有这些限制或具有不同的限制)决定允许该访问,或者虚拟化对某物的访问,或者向用户发出警告(一般保护错误)。以像 vmware 这样的实际虚拟机程序为例,允许虚拟化程序在处理器上实际运行指令,当该虚拟化程序访问它认为是显卡的地址时,发生保护故障,vmware 驱动程序/应用程序(想想内核级别)获取该地址并伪造视频卡响应并将控制权返回给应用程序。让指令在金属上执行允许更快的虚拟化,而不是模拟每个处理器指令。这是极端情况,即使您正在阅读本文的 Web 浏览器也已被虚拟化,因此它认为它具有基于某些基地址(如 0x000 或 0x8000)的内存空间,您将特定操作系统的每个程序编译为相同平坦的虚拟内存空间,操作系统负责将地址从虚拟地址更改为物理地址。您的网络浏览器访问其地址 0x8000 可能是物理地址 0x12345678,而您的 mp3 播放器程序访问地址 0x8000 可能是物理地址 0x2345678,但对于这两个应用程序,它们的指令都在计算 0x8000。
问什么是最好的总是一个相对的术语,一个人最好就是另一个人最差。你必须为自己定义最好的和最坏的。势头和公众舆论将 x86 推向了平坦的内存空间,或者至少从程序员的角度来看是平坦的内存空间的错觉,因此您在顺应潮流时会遇到更少的麻烦。
我建议购买一份 8086/8088 编程器和硬件参考手册,几块钱就能买到。
http://www.amazon.com/Manual-Programmers-Hardware-Reference-240487-001/dp/1555120814/ref=sr_1_1?ie=UTF8&qid=1340000636&sr=8-1&keywords=8088+programmers+reference
使用像 pcemu 这样的模拟器(我为此目的有一个克隆 http://github.com/dwelch67/pcemu_samples)并使用指令集,在虚拟化、保护等之前很老的学校。回到上面计算的段和偏移量时,转移段左四并添加偏移量(这在本手册中都有描述)。从那以后,每一代人都在尝试改进,同时尝试反向兼容。这当然帮助了利润,但把处理器变成了讨厌的野兽。你最好忘记 x86 的细节并学习一些更干净的系统,因为 x86 的发展方式,你将获得最小的收益,例如尝试用 asm 编写一些东西而不是编译的代码。由于来自不同系列的处理器以不同的速度执行相同的代码,因此新一代通常以更慢的速度执行来自先前的手动调整的代码。您不能手动调整某些东西以在所有平台上快速运行,而不是比编译器更快,因此只需将 x86 asm 代码留给编译器即可。在没有这些问题的健全平台上工作,并且可以根据自己的喜好进行调整或制作更好的编译器等。