段寄存器偏移内存访问的基数。 16位模式下的地址计算如下:
address = ((twenty_bit_t)segment << 4) + offset
其中(虚构的)twenty_bit_t 是至少 20 位的类型。
16 位实模式下的地址空间为 1MB,即 20 位。该段允许您影响前四位,并且以 16 字节的粒度(在当时通常称为“段落”)进行。向段值添加 1 会使指针在内存中提前 16 个字节。
您的偏移量被限制为 16 位值,因此要访问超出该值,您必须使用“远指针”。远指针的长度为 32 位。但不是真正的 32 位,高 16 位实际上只是段,与偏移量重叠。
远指针比普通指针要昂贵得多,因为(使用当时的典型编译器)每次取消引用不同的指针时,它都必须加载段寄存器。通常,编译器每次都会继续加载段寄存器。
有几个“模型”(我们当时称之为)。它基本上是近/远代码指针、近/远数据指针的所有组合的矩阵(以及 IIRC 一种拥有 > 64KB 全局变量的方法)。
编译器提供了对每个指针是远还是近的细粒度控制。对频繁访问的代码/数据使用近代码和近数据是一种优化,并在需要时放置额外的扩展指令来声明特定的指针或函数。
Far 调用和返回的影响并不像 far 数据那么严重,因为指针被取消引用的次数比函数被调用的次数多。
x86 CPU 具有处理远调用和返回以及远指针的特殊指令(和前缀)。即使是 32 位操作系统也至少需要初始化段寄存器。一些 x86 系统指令需要使用段寄存器。但是,在 32 位模式下,段寄存器的值与我上面描述的所有内容的含义完全不同。
链接和加载
目标文件有代码和数据块,每一个都注定要进入一个特定的段(按名称)。每个段也被标记为代码或数据等。链接器找出需要什么,计算出每个段有多大,计算出所有内容的地址,并记住可执行文件中任何指针的位置。远调用操作数和初始化远指针将被计算为好像可执行文件的加载地址为零,并且为每一个发出一个重定位条目。
DOS 动态处理内存,因此您的程序将被加载到一个不可预测的地址。为了解决这个问题,DOS 可执行文件具有“重定位”,即指向代码和初始化数据的指针列表。加载时,DOS 遍历该列表并将加载地址的基段添加到重定位条目指向的值。例如,代码重定位将指向代表调用指令中段的字节。这修复了所有已初始化的全局远指针和远调用指令操作数。
(从 286 开始,地址计算实际上几乎变成了 21 位 - 段寄存器在 FFFF + 偏移量 FFFF,地址将是 10FFEF - (1 MB + 64 KB - 16 B)。HIMEM.SYS 实际上有一个API 调用分配高端内存,全部或全部。通常人们通过在他们的 config.sys 中放置 DOS=HIGH 来让 DOS 拥有它。)