什么是"FS"/"GS"寄存器?

Meh*_*dad 85 x86 assembly cpu-registers

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

  • CS =代码段(用于IP)

  • DS =数据段(用于MOV)

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

  • SS =堆栈段(用于SP)

但是以下哪些寄存器用于什么?

  • FS ="文件段"?

  • GS = ???

注意:我不是在询问任何特定的操作系统 - 我问的是CPU的用途是什么,如果有的话.

Ira*_*ter 92

它们的目的是什么,以及它们用于Windows和Linux的用途.

段寄存器背后的初衷是允许程序访问许多不同(大)的内存段,这些内存段旨在独立并且是持久性虚拟存储的一部分.这个想法取自1966年的Multics操作系统,它将文件视为简单的可寻址内存段.没有BS"打开文件,写入记录,关闭文件",只是"将此值存储到该虚拟数据段",脏页刷新.

我们目前的2010年操作系统是一个巨大的倒退,这就是为什么他们被称为"太监".你只能解决您的进程空间的单段,给人一种所谓的"平(恕我直言沉闷)地址空间".x86-32机器上的段寄存器仍然可以用于真正的段寄存器,但没有人感到困扰(前任英特尔总裁安迪格罗夫在上个世纪有一个相当着名的公共场合,当他在所有英特尔工程师花费能量和他实现这个功能的钱,没有人会用它.去吧,安迪!)

AMD进入64位后决定他们不关心是否将Multics作为一种选择(这是慈善解释;不可靠的是他们对Multics一无所知),因此在64位模式下禁用了段寄存器的一般功能.仍然需要线程来访问线程本地存储,并且每个线程需要一个指针......在可立即访问的线程状态中的某处(例如,在寄存器中)...来线程本地存储.由于Windows和Linux在32位版本中都使用FS和GS(感谢Nick澄清),AMD决定让64位段寄存器(GS和FS)基本上只用于此目的(我认为你可以让它们指向您的进程空间中的任何位置;如果应用程序代码可以加载它们,则不知道.英特尔在恐慌中没有失去64位的AMD市场份额,而安迪退休后,决定只复制AMD的计划.

如果让每个线程的内存映射都有一个绝对的虚拟地址(例如,0-FFF),那就是它的线程本地存储(不需要[segment]寄存器指针!),这在结构上更漂亮了.我在20世纪70年代的8位操作系统中做到了这一点,它非常方便,就像有另一大堆寄存器一样.

因此,段寄存器现在有点像你的附录.它们具有退化目的.为了我们的集体损失.

那些不了解历史的人注定不会重演; 他们注定要做一些笨蛋.

  • @supercat:一个更简单,更精彩的方案,它可以让它们满足65536倍的存储空间,将段寄存器视为低16位的完全高16位扩展,这实质上就是286,386和Multics做到了. (8认同)
  • @IraBaxter:该方法的问题在于80286样式的段具有足够高的开销,最终必须在每个段中存储许多对象,因此在每个指针上存储段和偏移.相比之下,如果一个人愿意将内存分配多达16个字节的倍数,那么8086风格的分段允许人们单独使用*分段*作为识别对象的手段.在1980年,最多16个字节的舍入分配可能有点令人讨厌,但如果它将每个对象引用的大小从8个字节减少到4个,那么它今天将代表胜利. (3认同)
  • 这些寄存器*用于*现代操作系统。它们主要用于指向有关任务控制块的信息,至少在现在可用于 x86 芯片的两个主要操作系统中。而且,由于它们不再是“通用目的”,即使就其原始意图而言,您也不能使用它们太多。最好在 x86-64 系统上假装它们根本不存在,直到您需要它们允许您在线程控制块中访问的信息。 (3认同)
  • 基于过时的科学,附录类比非常糟糕; 它与免疫系统有关,所以绝对不是*"残留".它减损了实际的帖子.除此之外,这是一个很好的回应. (3认同)
  • 另请注意,此方案需要同一进程的两个线程之间进行上下文切换才能修改页表,但在当前方案中,所有线程共享相同的页表。我认为 TLS 很少被使用。使用它的代码中的一点额外代码大小/性能开销可能比额外的上下文切换开销更可取。当然,可以在架构上以某种方式支持 TLS 区域以避免 TLB 失效。 (3认同)
  • 感谢分段与平面内存的有趣,无限制的处理:)在6809(有和没有分页内存),6502,z80,68k和80 [123]?86上编写代码,我的观点是分段记忆是一个恐怖表演,我很高兴它被托付给历史的垃圾箱.使用FS和GS有效访问thread_local数据是历史错误的一个令人高兴的意外后果. (3认同)
  • 8086的细分非常棒.我知道没有更好的方案允许具有给定寄存器大小的机器访问16倍大的有效地址空间.即使在今天,8086样式的分段(但是32位段寄存器被移位并添加到32位偏移量)可能比线性寻址更好,允许32位对象引用访问64GB的寻址空间.通过一些轻微的调整,可以进一步推出32位对象引用的有用范围. (2认同)
  • 我不清楚的一件事是,由于这些寄存器不在现代操作系统中使用,它们是否可以免费用于一般用途? (2认同)
  • 我很高兴看到另一个人认为整个平坦的记忆迷信被高估了.事实上,如果在某些时候将它重新引入64位模式,我不会感到惊讶.想想看:由于所有的安全丑闻都在这些日子里出现,每个编译器都需要检查各种攻击,比如缓冲区溢出,堆栈溢出攻击......如果分段是这样的话,可以用非常少的开销来完成used(例如,将自上而下的堆栈段的限制设置为代码段的限制). (2认同)
  • 使用实际的零页面听起来是个坏主意,因为在NULL指针取消引用错误时更容易调试。(这非常方便+有时错误地假设NULL的位表示为全零)。相同的想法适用于虚拟地址空间的前几页,或者始终不对低64k进行映射并在其后放置TLS。 (2认同)
  • 我可能正在这里进行线程死灵法术,但是一般来说,Multics和分段的问题不是很复杂吗?即使到了今天,自远古时代以来没人使用过细分,英特尔手册仍然无休止地徘徊。我可以赞扬那些能够用有限的资源解决技术问题的人,但是我在这里看到的对复杂性的崇拜却令人困惑。 (2认同)
  • 这个答案有很多“_古老智慧_”的内容,但实际上只是怀旧,并没有太多事实依据。在 Multics 时代,通过内存段访问文件可能已经很不错了,但今天它在更高级别上得到了更好的解决(即操作系统提供的内存映射文件)。位于绝对地址的线程本地空间将是一个安全问题,因为这会阻碍 ASLR。 (2认同)
  • @IraBaxter是的,内存映射文件需要一些设置,这符合它提供的灵活性,即。如果 ISA 提供同等功能,则也需要对其进行设置/配置。与操作系统提供的功能相比,更新/扩展该功能要困难得多。关于 ASLR,我不知道为什么你认为它是一个 hack。它不像文件权限或密码那样是一种黑客行为 - 它确保只有那些应该拥有访问权限的人才能拥有该访问权限。 (2认同)
  • @IraBaxter multics 支持多少个文件系统?Linux 支持数十种,据我所知,内存映射适用于其中许多。除其他外,这就是灵活性。此外,如果您真的想摆脱文件打开或映射“废话”(如您所说),您可以编写一个库来抽象它,这甚至不会很困难。关于安全性,在 ROP 等技术存在的情况下,执行代码的读或写保护是没有意义的。此外,还有论文描述了 Multics 中的严重安全漏洞。安全很难,没有什么灵丹妙药。 (2认同)
  • @AtticusStonestrom:嗯。没能找到安迪·格罗夫的脾气。我会继续寻找。我确实发现 Multics 的历史非常有趣,包括几次几乎完成的将 Multics 迁移到具有“合理”(Multics 兼容)段寄存器的 486 机器的尝试:https://www.multicians.org/history.html尝试在 x86-64 上复活的提示。 (2认同)

cyt*_*nus 39

寄存器FSGS段寄存器.它们没有处理器定义的目的,而是由操作系统运行它们来实现目的.在Windows 64位中,GS寄存器用于指向操作系统定义的结构. FS并且GSOS内核通常使用它来访问特定于线程的内存.在Windows中,GS寄存器用于管理特定于线程的内存.linux内核用于GS访问特定于cpu的内存.

  • 在Windows上,FS确实适用于特定于线程的存储.请参阅FS指向的块的文档化地图https://en.wikipedia.org/wiki/Win32_Thread_Information_Block (7认同)
  • 它不只是在Windows上.GS还用于OS X上的TLS.64位内核也使用GS来跟踪上下文切换期间的系统结构.操作系统将使用SWAPGS来实现这一目的. (2认同)

Raf*_*ahn 12

TL;博士;

\n
\n

\xe2\x80\x9cFS\xe2\x80\x9d/\xe2\x80\x9cGS\xe2\x80\x9d 寄存器的用途是什么?

\n
\n

只是访问默认数据段(DS)之外的数据。和ES一模一样。

\n
\n

长读:

\n
\n

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

\n

[...]

\n
\n

嗯,差不多了,但是 DS 不是“某些”数据段,而是默认的数据段。所有操作均默认进行 (*1)。这是所有默认变量所在的位置 - 本质上data是 和bss。在某种程度上,这也是 x86 代码相当紧凑的部分原因。所有基本数据(即最常访问的数据)(加上代码和堆栈)都在 16 位速记距离内。

\n

ES 用于访问其他所有内容 (*2),即超过 64 KiB DS 的所有内容。就像文字处理器的文本、电子表格的单元格或图形程序的图片数据等等。与通常假设的不同,这些数据没有得到那么多的访问,因此需要前缀比使用更长的地址字段带来的伤害要小。

\n

类似地,在进行字符串操作时可能必须加载(和重新加载)DS 和 ES,这只是一个小烦恼 - 这至少被当时最好的字符处理指令集之一所抵消。

\n

真正令人痛苦的是当用户数据超过 64 KiB 并且必须开始操作时。虽然某些操作只是一次对单个数据项完成(想想A=A*2),但大多数操作需要两个 ( A=A*B) 或三个数据项 ( A=B*C)。如果这些项驻留在不同的段中,则每次操作 ES 将重新加载多次,从而增加相当多的开销。

\n

一开始,对于来自 8 位世界 (*3) 的小程序和同样小的数据集,这没什么大不了的,但它很快就成为一个主要的性能瓶颈 - 对于程序员来说更是一个真正的痛苦(和编译器)。在 386 中,英特尔最终通过添加两个段来缓解压力,因此任何系列一元二元三元运算(元素分布在内存中)都可以进行,而无需始终重新加载 ES。

\n

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

\n

就命名而言,字母 F/G 只是 E 之后的字母延续。至少从 CPU 设计的角度来看,没有任何关联。

\n
\n

*1 - 使用 ES 作为字符串目标是一个例外,因为只需要两个段寄存器。如果没有它们,它们就没有多大用处——或者总是需要一个段前缀。这可能会扼杀令人惊讶的功能之一,即使用(非重复的)字符串指令,由于其单字节编码而导致极高的性​​能。

\n

*2 - 所以事后看来,“Everything Else Segment”将是比“Extra Segment”更好的命名方式。

\n

*3 - 始终要记住,8086 只是在8800完成之前作为权宜之计,主要用于嵌入式世界,以留住 8080/85 客户,这一点始终很重要。

\n

  • 哇,谢谢你解释这一切!这解释了很多并且很有道理!+1 (2认同)

zix*_*ool 7

FS用于指向Windows进程上的线程信息块(TIB)。

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

GS通常用作指向线程本地存储(TLS)的指针。您之前可能会看到的一个示例是堆栈金丝雀保护(stackguard),在gcc中,您可能会看到以下内容:

mov    eax,gs:0x14
mov    DWORD PTR [ebp-0xc],eax
Run Code Online (Sandbox Code Playgroud)

  • @MichaelPetch是的,我知道我只是想为那些在SO中阅读此q / s的人添加此为好信息 (6认同)
  • 这实际上并没有回答这个问题。问题说明_注意:我不是在问任何特定的操作系统——我问的是它们打算被 CPU 用来做什么,如果有的话。_ (3认同)

Rob*_*ton 5

根据 Intel 手册,在 64 位模式下,这些寄存器旨在用作某些线性地址计算中的附加基址寄存器。我从第 3.7.4.1 节(第 4 卷集中的第 86 页)中提取了此内容。通常当CPU处于该模式时,线性地址与有效地址相同,因为该模式下通常不使用分段。

因此,在这个平面地址空间中,FS 和 GS 不仅在寻址本地数据方面发挥作用,而且在寻址某些操作系统数据结构(第 2793 页,第 3.2.4 节)方面发挥作用,因此这些寄存器旨在由操作系统使用,但是那些特定的设计者决定。

在 32 位和 64 位模式下使用覆盖时存在一些有趣的技巧,但这涉及特权软件。

从“初衷”的角度来看,很难说它们只是额外的寄存器。当CPU处于实地址模式时,就像处理器作为高速8086运行一样,这些寄存器必须由程序显式访问。为了实现真正的 8086 仿真,您可以在虚拟 8086 模式下运行 CPU ,并且不会使用这些寄存器。