“FS”/“GS”寄存器的作用是什么?

136

我知道以下寄存器及其用途:

  • CS = 代码段 (用于IP)

  • DS = 数据段 (用于MOV)

  • ES = 目标段 (用于MOVS等)

  • SS = 栈段 (用于SP)

但是下面这些寄存器的用途是什么?

  • FS = "文件段"?

  • GS = ???

注意:我不是在问任何特定操作系统 -- 我是在问它们被CPU设计为用于什么,如果有的话。


32
据我所知,这两个字母 F 和 G 并没有代表任何含义。只是因为 CPU(和指令集)中有足够的空间容纳 6 个用户可指定的段寄存器,有人注意到除了“S”栈段外,字母“C”和“D”(代码和数据)连续出现,于是“E”就成为“额外”的段寄存器,然后“F”和“G”也就跟着来了。 - torek
3
除非你当时在场(而我在另一个海岸,远离英特尔设计团队),否则很难知道别人的脑海中究竟在想些什么。所以说这只是假设,无从证实。 - torek
30
想想看如果我们能利用 BS register 会有多少乐趣 :-}。(注:BS register 是计算机中的寄存器名称,全称为 Base Pointer Register,翻译成“基址指针寄存器”或“基址寄存器”较为准确。句子末尾的“:-}”是一种面部表情符号,表示调皮或幽默的意味。) - Ira Baxter
5
我通常将GS作为"图形段"来使用。 :-) - Brian Knoblauch
3
“G”eneral “S”egment怎么样? - S.S. Anne
显示剩余4条评论
6个回答

149

这些寄存器最初的设计意图是允许程序访问许多不同(大)的内存段,这些内存段旨在独立并作为持久虚拟存储的一部分。这个想法来自于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位操作系统中就是这样做的,非常方便,就像有另一个大的寄存器堆栈可以使用。

因此,段寄存器现在有点像你的阑尾。它们具有退化的作用。这是我们共同的损失。

不了解历史的人不会重蹈覆辙,而是注定要做更愚蠢的事情。


12
一个更加简单、更加出色的方案,可以让他们处理比原来多65536倍的存储容量,那就是把段寄存器视作下16位的完整上16位扩展,这本质上就是286、386和Multics所做的。 - Ira Baxter
5
采用这种方法的问题在于,类似于80286风格的段有着相对较高的开销,因此每个段中需要存储许多对象,并且需要在每个指针上同时存储段和偏移量。相比之下,如果愿意将内存分配舍入为16字节的倍数,8086风格的分段允许我们仅使用段作为识别对象的手段。尽管在1980年时将分配舍入为16字节可能会略显麻烦,但如果它将每个对象引用的大小从8个字节减少到4个字节,那么今天采用这种方式将是一种胜利。 - supercat
8
这些寄存器在现代操作系统中确实被使用。它们主要用于指向有关任务控制块的信息,至少在现在可用于x86芯片的两个主要操作系统中是这样。由于它们即使对于最初的用途也不再是“通用”的,因此你不能为太多事情使用它们。最好在x86-64系统上假装它们根本不存在,直到你需要访问线程控制块中所允许访问的信息。 - Ira Baxter
7
关于阑尾类比的说法是基于过时的科学理论,与免疫系统有关,因此绝对不是“退化的”。这种说法会削弱文章的内容。除此之外,这是一个不错的回答。 - code_dredd
11
谢谢你有趣、毫不掩饰地讨论分段内存和扁平内存的优劣。 我自己也曾在6809(有和无页内存)、6502、z80、68k和80[123]?86上编写过代码,所以我的观点是,分段内存是一场灾难,我很高兴它已经被抛弃到历史的垃圾桶里了。使用FS和GS来有效地访问本线程数据是一种愉快的意外后果,源于一个历史性错误。 - Richard Hodges
显示剩余48条评论

64
注册器FSGS是段寄存器。它们没有处理器定义的目的,而是由运行它们的操作系统赋予目的。在 Windows 64 位中,GS 寄存器用于指向操作系统定义的结构。FSGS 经常被操作系统内核用于访问线程特定的内存。在 Windows 中,GS 寄存器用于管理线程特定的内存。Linux 内核使用GS来访问 CPU 特定的内存。

1
它们是为了用于操作系统定义的目的,还是为了方便需要执行类似 *dest++ = lookup[*src++]; 这样的代码,如果 dest、lookup 和 src 位于三个不相关的位置,那么这种操作将会非常笨拙。 - supercat
10
在Windows中,FS确实用于线程特定存储。请参见此处所指向的块的文档化映射:https://en.wikipedia.org/wiki/Win32_Thread_Information_Block - Nedko
2
不仅仅是在Windows上。GS也用于OS X上的TLS。64位内核还使用GS来在上下文切换期间跟踪系统结构。操作系统将使用SWAPGS来实现这一点。 - E.T
在Windows中,GS寄存器用于管理特定线程的内存...不是FS吗? - tuket
@tuket 他们的32位操作系统使用fs寄存器,而64位操作系统使用gs寄存器。Linux则采取了相反的做法。 - Johan Boulé

23

FS被用来指向Windows进程中的线程信息块(TIB)。

一个典型的例子是(SEH),它在 FS:[0x00] 中存储一个回调函数的指针。

GS通常被用作指向线程本地存储(TLS)的指针。一个你可能见过的例子是栈金丝雀保护(stackguard),在gcc中你可能会看到类似这样的东西:

mov    eax,gs:0x14
mov    DWORD PTR [ebp-0xc],eax

3
这并没有真正回答问题。问题声明了“注意:我不是在问任何特定的操作系统——我在问CPU设计时预期它们用于什么,如果有的话。” - Michael Petch
21
@MichaelPetch,是的,我知道,我只是想把这个作为好资料添加给那些在SO上阅读这个问题/答案的人。 - zerocool

12

简短概述:

“FS”/“GS”寄存器的作用是什么?

它们的作用就是访问超出默认数据段(DS)范围的数据,与ES寄存器的作用类似。


长篇阅读:

所以我知道以下寄存器及其用途应该是什么:

[...]

嗯,几乎是的,但 DS 不是“一些”数据段,而是默认的数据段。所有操作都在此处默认进行(*1)。这就是所有默认变量所在的位置 - 基本上是 databss。这也是 x86 代码相对紧凑的某种原因。所有基本数据(最常访问的数据),加上代码和堆栈,都在 16 位简写距离内。

ES 用于访问 DS 之外的所有内容(*2),即超过 64 KiB 的所有内容。例如文字处理器的文本、电子表格的单元格或图形程序的图像数据等。与通常假定的不同,这些数据并没有被频繁访问,因此需要前缀的影响较小,比使用更长的地址字段要好。

同样,当进行字符串操作时,DS 和 ES 可能需要加载(和重新加载),这只是一个小小的麻烦 - 至少有一个是其时代最佳字符处理指令集之一。

真正痛苦的是当用户数据超过 64 KiB 并且必须进行操作时。虽然有些操作仅一次对单个数据项进行(例如 A=A*2),但大多数操作需要两个(A=A*B)或三个数据项(A=B*C)。如果这些项位于不同的段中,则每次操作都会重新加载 ES,增加了相当多的开销。

在开始时,使用来自 8 位世界的小型程序和同样小的数据集并不是什么大问题,但很快就成为主要性能瓶颈 - 并且对程序员(和编译器)来说真正痛苦。有了 386,英特尔最终通过添加两个更多的段来提供帮助,因此任何元素分散在内存中的一系列unarybinaryternary操作都可以在不时常重新加载 ES 的情况下进行。

对于编程(至少在汇编中)和编译器设计来说,这是相当大的进步。当然,还可以做得更多,但是有了三个段,瓶颈基本消失了,所以没有必要过度做。

从命名上看,字母 F/G 只是 E 后面的字母顺序。至少从 CPU 设计的角度来看,没有关联。


*1 - 对于字符串目标,使用ES寄存器是一个例外,因为只需要两个段寄存器。如果没有它们,将不会很有用 - 或者总是需要一个段前缀。这可能会破坏其中一个令人惊讶的功能,即使用(非重复)字符串指令,由于其单字节编码而导致极高的性能。
*2 - 因此,回过头来看,“Everything Else Segment”比“Extra Segment”更好地命名了。
*3 - 始终要记住,8086只是一种临时措施,直到8800完成,并主要用于嵌入式领域,以保留8080/85客户。

2
哇,谢谢你解释这一切!这解释了很多问题,让人感到非常合理!+1 - user541686

5
根据英特尔手册,在64位模式下,这些寄存器被用作某些线性地址计算中的附加基址寄存器。我从第3.7.4.1节(第4卷第86页)中找到了这个信息。通常情况下,当CPU处于此模式时,由于在此模式下通常不使用分段,线性地址与有效地址相同。
因此,在此扁平地址空间中,FS和GS不仅用于定位本地数据,还可用于定位某些操作系统数据结构(pg2793,第3.2.4节),具体取决于那些设计人员如何使用这些寄存器。
在32位和64位模式下使用覆盖时存在一些有趣的技巧,但这需要使用特权软件。
从“最初意图”的角度来看,除了它们只是额外的寄存器之外,很难说。 当CPU处于“实地址模式”时,就像处理器正在以高速8086形式运行,并且必须通过程序显式访问这些寄存器。为了进行真正的8086仿真,您可以将CPU运行在“虚拟-8086模式”下,而这些寄存器则不会被使用。

1

在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)运行。
80286处理器具有16位保护模式,但没有FS/GS寄存器。因此,在真实的16位模式下,程序必须首先检查是否在80386下运行,然后才能使用这些寄存器。请参见用于MS-DOS真实模式的FS和GS寄存器示例

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接