实模式内存寻址中的段和偏移量是多少?

时间:2010-11-07 20:20:24

标签: memory assembly operating-system x86 real-mode

我正在阅读有关内存寻址的内容。我读了关于段偏移然后关于描述符偏移的内容。我知道如何计算实模式下的确切地址。这一切都没问题,但我无法理解究竟是什么偏移?无处不在我读到:

  

在实模式下,寄存器只有16位,因此您只能寻址   高达64k。为了允许寻址更多的内存,地址是   从段* 16 +偏移计算。

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

但这第二行是什么?细分代表什么?为什么我们将它乘以16?为什么我们添加偏移。我只是无法理解这个偏移是什么?请问有人解释我或给我链接吗?

6 个答案:

答案 0 :(得分:16)

当英特尔正在构建8086时,机器中有超过64KB的有效案例,但它没有办法使用32位地址空间。那时候,甚至一兆字节都是一大堆内存。 (还记得臭名昭着的引语“640K应该对任何人来说都足够了吗?”这实际上是一个错误翻译的事实,当时,1MB是非常恐怖的巨大的。)“gigabyte”这个词并不相同再使用15到20年,并且在此之后的5到10年内不会再提到RAM了。

因此,他们没有实现如此巨大的地址空间以至于“永远不会”被充分利用,而是实现了20位地址。他们仍然使用16位字作为地址,因为毕竟这是一个16位处理器。上面的单词是“段”,下面的单词是“偏移”。然而,这两个部分相当重叠 - “段”是从(segment) * 16开始的64KB内存块,“偏移量”可以指向该块内的任何位置。为了计算实际地址,您将地址的段部分乘以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,如果我的数学是正确的)。

答案 1 :(得分:12)

在x86实模式内存下,物理地址长度为20位,并且是可卡的:

PhysicalAddress = Segment * 16 + Offset

同时检查:Real-Mode Memory Management

答案 2 :(得分:1)

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

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

真实模式使用类似的风格,即使它与SEGMENT:OFFSET不同。一个段的大小为64k。然而,这些细分市场并不像Commodore那样背靠背布局。它们彼此间隔16个字节。偏移量仍然相同,表示页面\段开始的字节数。

我希望这个解释可以帮助那些发现这个问题的人,它帮助我写了它。

答案 3 :(得分:1)

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

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

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

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

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

从Pentium MMX开始,我们成为8个64位MMX寄存器 从奔腾3开始,我们成为8个128位XMM寄存器 ..

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

德克

答案 4 :(得分:1)

最小例子

使用:

  • offset = msg
  • segment = 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 repo上运行它。

答案 5 :(得分:-1)

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

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

历史上,前端总线(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

你有它!英特尔聘请了一些愚蠢的工程师,我们必须忍受它。