什么是段以及如何在 8086 模式下对它们进行寻址?

What are Segments and how can they be addressed in 8086 mode?

自从开始接触8086汇编语言编程以来,我就一直在琢磨这些Segments和Segment registers。我面临的问题是我无法直观地了解脑海中的细分市场,因此我不清楚这些概念。


问题一:

据我了解,在启用了 20 条地址线的 16 位实模式下,我们可以将物理内存分成 16 段,每段 64KiB。第一段从 0x00000 开始。下一段的起始地址是什么。是否会通过添加 0x10000 (65536 = 64KiB)?

问题二:

在这里问这个问题有点奇怪,但 SO 仍然是我唯一的选择。假设如果给我一个偏移地址 0x6000,我怎样才能找到它所属的段以便对其进行寻址。

谢谢

在这个回答中,我只是对实模式进行解释。在保护模式下,分段有点复杂,因为您可能永远不会编写分段保护模式程序,所以我不打算解释这个。


片段其实很简单。 8086 CPU 有四个 段寄存器 分别命名为 csdsesss。当您访问内存时,CPU 会像这样计算物理地址:

physical_address = segment * 16 + effective_address

其中effective_address是内存操作数所指示的地址,segment是这次内存访问的段寄存器的内容。默认情况下,CPU取代码时使用csss用于压入和弹出栈以及以bp为基址寄存器的内存操作数,es 用于某些特殊指令,ds 用于其他任何地方。可以使用 段前缀覆盖段寄存器。

这在实践中意味着什么? 8086 有 16 位寄存器,因此使用寄存器存储地址允许我们寻址多达 65536 字节的 RAM。使用段寄存器背后的想法是我们可以在 中存储额外的地址位,允许程序员寻址多于 220 = 1048576 字节 = 1 MiB RAM。该 RAM 被分成 65536 个重叠段,每个段 65536 字节,其中每个段是一个可以加载到段寄存器中的值。

这些段中的每一个都从一个地址开始,该地址是 16 的倍数,如您在上面的地址计算逻辑中所见。您可以将整个 1 MiB 物理地址 space 与 16 个非重叠段(如您在问题中所解释的)值 0x00000x1000、...、0xf000 平铺但您也可以使用任何您喜欢的段选择器。

...we could divide the physical memory into 16 segments with 64KiB each.

是的,但更准确的说法是“16 非重叠 段”,因为也有可能将内存分成 65536 重叠 段。

启用A20线后,我们有超过1MB的空间可以玩。 (1048576+65536-16) 当设置相关段寄存器为0xFFFF时,我们可以访问0x0FFFF0到0x10FFEF之间的内存。

两种细分的主要特点是:

  1. 非重叠片段
    • 包含 65536 字节。
    • 内存中相隔65536字节。
    • 这是我们经常方便地查看内存的方式。它使我们能够说我们已经把
      • A段(0xA0000-0xAFFFF)中的图形window
      • B段(0xB0000-0xBFFFF)中的文字视频window
      • F段中的BIOS(0xF0000-0xFFFFF)
  2. 重叠片段

    • 包含 65536 字节。
    • 在内存中相隔16个字节。

      有时您会看到人们将 16 字节的内存块称为段,但显然这是错误的。然而,对于这样的内存量有一个广泛使用的名称:“paragraph”。

    • 这是CPU(在实地址模式下)查看内存的方式。
      处理器使用以下步骤计算线性地址:
      • 首先计算指令操作数的偏移地址。结果被截断以适合 16 位(64KB 环绕)。
      • 接下来添加SegmentRegister * 16
        的乘积 如果 A20 行处于非活动状态,则结果将被截断以适合 20 位(1MB 回绕)。
        如果 A20 行处于活动状态,则结果将按原样使用,因此不会发生 1MB 回绕。

Suppose if I am given with an Offset address of 0x6000, How can I find the segment to which it belongs in order to address it.

问题又出在措辞上了!

如果 "an Offset address of 0x6000" 你的意思是像我们通常在实地址模式编程中使用的偏移量那么这个问题无法回答,因为 在每个段中都有这样的偏移量 0x6000存在

如果另一方面写法"an Offset address of 0x6000"实际上指的是线性地址0x6000那么段寄存器有很多解决方案:

segment:offset
--------------
   0000:6000
   0001:5FF0
   0002:5FE0
   0003:5FD0
   ...
   05FD:0030
   05FE:0020
   05FF:0010
   0600:0000

如您所见,有 0x0601 个可能的段寄存器设置可以到达线性地址 0x6000。
以上适用于 A20 线路确实启用时。如果 A20 处于非活动状态,则线性地址 0x6000(就像从 0 到 1MB-1 的任何其他线性地址一样)可以精确地以 0x1000 (4096) 种方式到达:

segment:offset
--------------
   F601:FFF0
   F602:FFE0
   F603:FFD0
   ...
   FFFD:6030
   FFFE:6020
   FFFF:6010
   0000:6000
   0001:5FF0
   0002:5FE0
   0003:5FD0
   ...
   05FD:0030
   05FE:0020
   05FF:0010
   0600:0000

一般来说,段是使用内部索引系统的内存间隔。

如果您将内存视为一长串字节 mem[0x100000],您可以指定一个连续的切片 seg=mem[a:a+b],其中 len(seg)=b 其中

  • seg[0] 存储在 mem[a]
  • seg[1] 存储在 mem[a+1]
  • ...
  • seg[b-1] 存储在 mem[a+b-1].

使用段的优点是段内的地址(段索引)可以更短,例如在 8086 的情况下,可寻址内存上升到物理地址(内存索引)220-1,(在后继者上你甚至可以更进一步,使用 16 位地址分段)。 此外,将程序放在内存中的任何位置都相当简单,因为您只需要分配一个或几个空闲段,并且大多数地址都在专用段内运行而无需调整它们。

在 8086 上所有段都是 216 字节长,因此段内地址适合 16 位以内,这使得它们易于处理。对于起始地址,您可以 select 物理内存中可被 16 整除且等于或小于 0xFFFF0 的任何地址。这意味着任何物理地址都位于多个段中。这些段由 16 位数描述,即起始地址除以 16。

所以段 0xBADA 对应于从 0xBADA0 开始的段。