我知道以下寄存器及其用途:
CS = 代码段 (用于IP)
DS = 数据段 (用于MOV)
ES = 目标段 (用于MOVS等)
SS = 栈段 (用于SP)
但是下面这些寄存器的用途是什么?
FS = "文件段"?
GS = ???
注意:我不是在问任何特定操作系统 -- 我是在问它们被CPU设计为用于什么,如果有的话。
我知道以下寄存器及其用途:
CS = 代码段 (用于IP)
DS = 数据段 (用于MOV)
ES = 目标段 (用于MOVS等)
SS = 栈段 (用于SP)
但是下面这些寄存器的用途是什么?
FS = "文件段"?
GS = ???
注意:我不是在问任何特定操作系统 -- 我是在问它们被CPU设计为用于什么,如果有的话。
这些寄存器最初的设计意图是允许程序访问许多不同(大)的内存段,这些内存段旨在独立并作为持久虚拟存储的一部分。这个想法来自于1966年Multics操作系统,该系统将文件视为可寻址的内存段。没有“打开文件,写入记录,关闭文件”的麻烦,只需使用脏页刷新将值存储到虚拟数据段中。
我们当前的2010年操作系统是一个巨大的倒退,这就是为什么它们被称为“Eunuchs”的原因。您只能访问您的进程空间的单个段,从而获得所谓的“平坦(我认为很无聊)地址空间”。x86-32机器上的段寄存器仍然可以用作真正的段寄存器,但是没有人费心去使用它们(安迪·格鲁夫(Andy Grove),前英特尔总裁,在上个世纪曾经非常公开地愤怒,因为他发现所有那些英特尔工程师花费了他的钱和精力来实现这个功能,却没有人会使用它。加油,安迪!)
AMD在进入64位时决定不考虑Multics作为选择(这是慈善解释;不慈善的解释是他们对Multics一无所知),因此禁用了64位模式下段寄存器的通用能力。仍然需要线程访问线程本地存储,并且每个线程需要一个指针...在立即可访问的线程状态中(例如,在寄存器中)...到线程本地存储。由于Windows和Linux在32位版本中都使用FS和GS(感谢Nick的澄清)来实现此目的,因此AMD决定让64位段寄存器(GS和FS)基本上仅用于此目的(我认为您可以使它们指向进程空间中的任何位置;我不知道应用程序代码是否可以加载它们)。英特尔在惊恐中不想在64位上失去市场份额,而Andy已经退休,决定只复制AMD的方案。在我看来,从架构上讲,让每个线程的内存映射具有绝对虚拟地址(例如,0-FFF)作为其线程本地存储(不需要[段]寄存器指针!)更加美观;我在20世纪70年代的8位操作系统中就是这样做的,非常方便,就像有另一个大的寄存器堆栈可以使用。
因此,段寄存器现在有点像你的阑尾。它们具有退化的作用。这是我们共同的损失。
不了解历史的人不会重蹈覆辙,而是注定要做更愚蠢的事情。
FS
和GS
是段寄存器。它们没有处理器定义的目的,而是由运行它们的操作系统赋予目的。在 Windows 64 位中,GS
寄存器用于指向操作系统定义的结构。FS
和 GS
经常被操作系统内核用于访问线程特定的内存。在 Windows 中,GS
寄存器用于管理线程特定的内存。Linux 内核使用GS
来访问 CPU 特定的内存。*dest++ = lookup[*src++];
这样的代码,如果 dest、lookup 和 src 位于三个不相关的位置,那么这种操作将会非常笨拙。 - supercatFS被用来指向Windows进程中的线程信息块(TIB)。
一个典型的例子是(SEH),它在 FS:[0x00]
中存储一个回调函数的指针。
GS通常被用作指向线程本地存储(TLS)的指针。一个你可能见过的例子是栈金丝雀保护(stackguard),在gcc中你可能会看到类似这样的东西:
mov eax,gs:0x14
mov DWORD PTR [ebp-0xc],eax
“FS”/“GS”寄存器的作用是什么?
它们的作用就是访问超出默认数据段(DS)范围的数据,与ES寄存器的作用类似。
所以我知道以下寄存器及其用途应该是什么:
[...]
嗯,几乎是的,但 DS 不是“一些”数据段,而是默认的数据段。所有操作都在此处默认进行(*1)。这就是所有默认变量所在的位置 - 基本上是 data
和 bss
。这也是 x86 代码相对紧凑的某种原因。所有基本数据(最常访问的数据),加上代码和堆栈,都在 16 位简写距离内。
ES 用于访问 DS 之外的所有内容(*2),即超过 64 KiB 的所有内容。例如文字处理器的文本、电子表格的单元格或图形程序的图像数据等。与通常假定的不同,这些数据并没有被频繁访问,因此需要前缀的影响较小,比使用更长的地址字段要好。
同样,当进行字符串操作时,DS 和 ES 可能需要加载(和重新加载),这只是一个小小的麻烦 - 至少有一个是其时代最佳字符处理指令集之一。
真正痛苦的是当用户数据超过 64 KiB 并且必须进行操作时。虽然有些操作仅一次对单个数据项进行(例如 A=A*2
),但大多数操作需要两个(A=A*B
)或三个数据项(A=B*C
)。如果这些项位于不同的段中,则每次操作都会重新加载 ES,增加了相当多的开销。
在开始时,使用来自 8 位世界的小型程序和同样小的数据集并不是什么大问题,但很快就成为主要性能瓶颈 - 并且对程序员(和编译器)来说真正痛苦。有了 386,英特尔最终通过添加两个更多的段来提供帮助,因此任何元素分散在内存中的一系列unary、binary或ternary操作都可以在不时常重新加载 ES 的情况下进行。
对于编程(至少在汇编中)和编译器设计来说,这是相当大的进步。当然,还可以做得更多,但是有了三个段,瓶颈基本消失了,所以没有必要过度做。
从命名上看,字母 F/G 只是 E 后面的字母顺序。至少从 CPU 设计的角度来看,没有关联。
在80386处理器下的16位实模式或16位保护模式中,FS和GS段寄存器非常有用,例如在MS-DOS中只有64KB的段。
当1985年引入80386处理器时,拥有640KB RAM的PC电脑在MS-DOS下很普遍。RAM很昂贵,大多数PC都在实模式下运行,并且最多只能使用这么多RAM。
因此,通过使用FS和GS,您可以有效地从程序中寻址另外两个64KB内存段,而无需在需要寻址其他DS或ES未加载的段时更改DS或ES寄存器。基本上,Raffzahn已经回答,这些寄存器在处理分散在内存中的元素时非常有用,以避免一直重新加载其他段寄存器如ES。但是我想强调,这仅适用于16位实模式或16位保护模式下的64KB段。
16位保护模式是一种非常有趣的模式,提供了此后未见过的功能。段可以具有1到65536字节的长度范围。每次内存访问时,CPU实现了对段大小的范围检查(检查选择器表中为该段指定的大小),并在访问超出段大小的内存时引发中断。这防止了硬件层面上的缓冲区溢出。您可以为每个内存块分配自己的段(总数受某些限制)。有编译器(如Borland Pascal 7.0)可以使用其自己的DOS扩展程序使在MS-DOS下运行的程序以16位保护模式(DOS Protected Mode Interface,DPMI)运行。