13

我正在阅读有关内存寻址的信息。我阅读了有关段偏移的信息,然后阅读了有关描述符偏移的信息。我知道如何在实模式下计算确切的地址。这一切都很好,但我无法理解偏移量到底是什么?我到处读到:

在实模式下,寄存器只有 16 位,因此您最多只能寻址 64k。为了允许寻址更多内存,地址从 segment * 16 + offset.

在这里我可以理解第一行。我们有 16 位,所以我们最多可以寻址 2^16 = 64k。

但这第二行是什么?段代表什么?为什么要乘以 16?为什么我们添加偏移量。我只是无法理解这个偏移量是什么?任何人都可以解释我或给我这个链接吗?

4

6 回答 6

20

英特尔在构建 8086 时,有一个有效的案例,即在一台机器中拥有超过 64KB 的空间,但它不可能使用 32 位地址空间。那时,即使是一兆字节也是一大堆内存。(还记得那句臭名昭著的名言“640K 应该对任何人都足够了”吗?这实际上是对当时 1MB 非常大的事实的误译)“技嘉”这个词在 15 到 20 年内不会被普遍使用,并且在那之后的 5 到 10 年内它不会指代 RAM。

因此,他们没有实现一个如此巨大以至于“永远不会”被充分利用的地址空间,而是实现了 20 位地址。他们仍然使用 16 位字作为地址,因为毕竟这是一个 16 位处理器。上面的词是“segment”,下面的词是“offset”。不过,这两个部分有很大的重叠——“段”是从 开始的 64KB 内存块,(segment) * 16“偏移量”可以指向该块中的任何位置。为了计算实际地址,将地址的段部分乘以 16(或将其左移 4 位......同样的事情),然后加上偏移量。完成后,您有一个 20 位地址。

 19           4  0
  +--+--+--+--+
  |  segment  |
  +--+--+--+--+--+
     |   offset  |
     +--+--+--+--+

例如,如果段为 0x8000,偏移量为 0x0100,则实际地址为((0x8000 << 4) + 0x0100)== 0x80100

   8  0  0  0
      0  1  0  0
  ---------------
   8  0  1  0  0

不过,数学很少那么简洁——0x80100可以用数以千计的不同段表示:偏移组合(如果我的数学是正确的,则为 4096)。

于 2010-11-07T20:36:45.780 回答
13

在 x86 实模式内存下,物理地址为 20 位长,因此计算如下:

PhysicalAddress = Segment * 16 + Offset

另请参阅:实模式内存管理

于 2010-11-07T20:35:08.747 回答
1

我想在这里添加一个答案,只是因为我也在网上搜索试图理解这一点。其他答案遗漏了我从其中一个答案中提供的链接中获得的关键信息。然而,我几乎完全错过了它。阅读链接页面,我仍然不明白这是如何工作的。

我可能遇到的问题是我自己真正了解 Commodore 64(6502 处理器)如何布置内存。它使用类似的符号来寻址内存。它有 64k 的总内存,并使用 PAGE:OFFSET 的 8 位值来访问内存。每页长 256 字节(8 位数),偏移量指向该页中的一个值。页面在内存中是背靠背间隔的。所以第 2 页从第 1 页结束的地方开始。我正以同样的方式进入 386。事实并非如此。

实模式使用类似的样式,即使它是不同的措辞 SEGMENT:OFFSET。一个段的大小为 64k。然而,这些部分本身并没有像 Commodore 那样背靠背布置。它们彼此间隔 16 个字节。偏移量仍然相同,表示从页\段开始有多少字节。

我希望这个解释能帮助其他发现这个问题的人,它帮助我写了这个问题。

于 2012-12-17T18:56:49.753 回答
1

我可以看到问题和答案已经有几年了,但是有一个错误的说法是在实模式下只有 16 位寄存器存在。

在实模式下,寄存器不仅是 16 位的,因为也有 8 位的寄存器。这些 8 位寄存器中的每一个都是 16 位寄存器的一部分,它们分为 16 位寄存器的低位和高位部分。

从 80386+ 开始实模式,我们变成 32 位寄存器,另外还有两个新的指令前缀,一个用于覆盖/反转默认操作数大小,一个用于覆盖/反转一条指令的默认地址大小一个代码段。

这些指令前缀可以组合使用,以将一条指令的操作数大小和地址大小一起反转。在实模式下,默认操作数大小和地址大小为 16 位。使用这两个指令前缀,我们可以使用 32 位操作数/寄存器示例来计算一个 32 位寄存器中的 32 位值,或者将 32 位值移入和移出内存位置。并且我们可以使用所有 32 位寄存器(可能与 base+index*scale+displacement 结合使用)作为地址寄存器,但有效地址的总和不必超过 64 kb 段大小的限制.

(在 OSDEV-Wiki 页面上,我们可以在“操作数大小和地址大小覆盖前缀”的表中找到“0x66 操作数前缀”和“0x67 地址前缀”对于实模式和虚拟 8086 模式。http
://wiki.osdev.org/X86-64_Instruction_Encoding 但这是完全错误的,因为在英特尔手册中我们可以找到这样的声明:“这些前缀可以在实地址模式下使用以及在保护模式和虚拟 8086 模式下”。)

从 Pentium MMX 开始,我们变成了 8 个 64 位 MMX 寄存器。
从 Pentium 3 开始,我们变成了 8 个 128 位 XMM 寄存器。
..

如果我没记错的话,那么 256 位 YMM-Register 和 512 位 ZMM-Register 以及 x64 的 64 位通用寄存器不能在实模式下使用。

短剑

于 2014-02-06T12:56:26.057 回答
1

最小的例子

和:

  • 偏移量 =msg
  • 段 =ds
mov $0, %ax
mov %ax, %ds
mov %ds:msg, %al
/* %al contains 1 */

mov $1, %ax
mov %ax, %ds
mov %ds:msg, %al
/* %al contains 2: 1 * 16 bytes forward. */

msg:
.byte 1
.fill 15
.byte 2

所以如果你想访问64k以上的内存:

mov $0xF000, %ax
mov %ax, %ds

请注意,如果您使用以下内容,这允许地址大于 20 位宽:

0x10 * 0xFFFF + 0xFFFF == 0x10FFEF

在只有 20 条地址线的早期处理器上,它被简单地截断,但后来 A20 线(第 21 条地址线)变得复杂:https ://en.wikipedia.org/wiki/A20_line

在带有运行它所需样板的GitHub 存储库上。

于 2015-11-07T09:07:25.293 回答
-1

16 位寄存器最多只能寻址 0xFFFF(65,536 字节,64KB)。当这还不够时,英特尔添加了段寄存器。

任何逻辑设计都会简单地将两个 16 位寄存器组合成一个 32 位地址空间,(例如0xFFFF : 0xFFFF = 0xFFFFFFFF),但是……英特尔不得不让我们感到很奇怪。

历史上,前端总线 (FSB) 只有 20 条地址线,因此只能传输 20 位地址。为了“纠正”这一点,英特尔设计了一种方案,其中段寄存器仅将您的地址扩展 4 位(理论上为 16 位 + 4 = 20)。

为此,段寄存器从其原始值左移 4 位,然后添加到通用寄存器中的地址(例如[es:ax] = ( es << 4 ) + ax注意:左移 4 位相当于乘以 16

就是这样。以下是一些说明性示例:

;; everything's hexadecimal

[ 0:1 ] = 1

[ F:1 ] = F1

[ F:0 ] = F0

[ F:FF] = 1EF ; [F becomes F0, + FF = 1EF]

[ F000 : FFFF ] = FFFFF (max 20-bit number)

[ FFFF : FFFF ] = 10FFEF (oh shit, 21-bit number!)

因此,您仍然可以寻址超过 20 位。发生什么了?地址“环绕”,就像模数运算(作为硬件的自然结果)。所以,0x10FFEF变成0xFFEF

你有它!英特尔雇佣了一些愚蠢的工程师,我们不得不忍受它。

于 2017-03-22T20:11:22.763 回答