因此,我知道以下寄存器及其用途是什么:
CS =代码段(用于IP)
DS =数据段(用于MOV)
ES =目标段(用于MOVS等)
SS =堆栈段(用于SP)
但是,以下寄存器打算用于什么?
FS =“文件段”?
GS = ???
注意:我不是在问任何特定的操作系统,而是在问它们打算由CPU使用什么(如果有的话)。
因此,我知道以下寄存器及其用途是什么:
CS =代码段(用于IP)
DS =数据段(用于MOV)
ES =目标段(用于MOVS等)
SS =堆栈段(用于SP)
但是,以下寄存器打算用于什么?
FS =“文件段”?
GS = ???
注意:我不是在问任何特定的操作系统,而是在问它们打算由CPU使用什么(如果有的话)。
Answers:
它们的目的是什么,以及Windows和Linux的用途是什么。
段寄存器的最初意图是允许程序访问许多不同的(大)内存段,这些段旨在独立并且是持久性虚拟存储的一部分。这个想法来自1966年的Multics操作系统,该操作系统将文件视为简单的可寻址内存段。没有BS“打开文件,写记录,关闭文件”,而只是“将这个值存储到该虚拟数据段中”并带有脏页刷新。
我们当前的2010年操作系统向后退了一大步,这就是为什么它们被称为“太监”。你只能解决您的进程空间的单段,给人一种所谓的“平(恕我直言沉闷)地址空间”。x86-32机器上的分段寄存器仍然可以用于实际的分段寄存器,但没有人打扰(前英特尔总裁安迪·格罗夫(Andy Grove)上个世纪颇有名气,当时他发现所有这些英特尔工程师都花了很多精力和精力。他的钱去实现这个功能,没有人会去使用它。安迪!
AMD决定采用64位时,他们决定不在乎是否将Multics作为选择(这是一种慈善的解释;不慈善的是他们对Multics一无所知),因此禁用了64位模式下段寄存器的一般功能。仍然需要线程访问线程本地存储,并且每个线程都需要一个指针……处于立即可访问线程状态的某个位置(例如,在寄存器中)……以访问线程本地存储。由于Windows和Linux在32位版本中都为此目的使用了FS和GS(感谢Nick的澄清),AMD决定让64位段寄存器(GS和FS)本质上仅用于此目的(我想您可以使它们指向您处理空间中的任何位置;如果应用程序代码可以加载或不加载它们,则不知道)。
在架构上更漂亮的恕我直言,使每个线程的内存映射具有一个绝对虚拟地址(例如0-FFF),这是它的线程本地存储空间(不需要[segment]寄存器指针!);我是在1970年代使用8位操作系统完成此操作的,它非常方便,就像可以使用另一堆寄存器一样。
因此,段寄存器现在有点像您的附录。它们起着残余作用。给我们集体造成损失。
那些不知道历史的人注定要重蹈覆辙。他们注定要做些愚蠢的事。
寄存器FS
和GS
是段寄存器。它们没有处理器定义的目的,而是由操作系统运行它们来赋予目的。在Windows 64位中,该GS
寄存器用于指向操作系统定义的结构。 FS
并且GS
通常被OS内核用于访问线程特定的内存。在Windows中,该GS
寄存器用于管理线程特定的内存。linux内核用于GS
访问cpu特定的内存。
*dest++ = lookup[*src++];
,如果dest,lookup和src位于三个不相关的位置,否则它们将很尴尬。
FS用于指向Windows进程上的线程信息块(TIB)。
一个典型的例子是(SEH),它存储了一个指向回调函数的指针 FS:[0x00]
。
GS通常用作指向线程本地存储(TLS)的指针。您之前可能会看到的一个示例是堆栈金丝雀保护(stackguard),在gcc中,您可能会看到类似以下内容:
mov eax,gs:0x14
mov DWORD PTR [ebp-0xc],eax
根据《英特尔手册》,在64位模式下,这些寄存器旨在用作某些线性地址计算中的其他基址寄存器。我从第3.7.4.1节(第4卷中的第86页)中删除了此内容。通常,当CPU处于此模式时,线性地址与有效地址相同,因为在此模式下通常不使用分段。
因此,在这个平坦的地址空间中,FS和GS不仅在寻址本地数据而且在寻址某些操作系统数据结构(第2793页,第3.2.4节)中都发挥着作用,因此这些寄存器旨在供操作系统使用,但是那些特定的设计人员确定。
在32位和64位模式下使用替代时,会有一些有趣的技巧,但这涉及特权软件。
从“原始意图”的角度来看,很难说,它们只是多余的记录。当CPU处于实地址模式时,这就像处理器以8086高速运行,并且这些寄存器必须由程序显式访问。为了进行真正的8086仿真,您应该在virtual-8086模式下运行CPU,并且不会使用这些寄存器。
“ FS” /“ GS”寄存器的用途是什么?
只需访问默认数据段(DS)之外的数据。就像ES。
因此,我知道以下寄存器及其用途是什么:
[...]
好吧,但DS几乎不是“某些”数据段,而是默认数据段。默认情况下所有操作都发生(* 1)。这是所有默认变量都位于-本质上data
和bss
。这是x86代码非常紧凑的部分原因。所有最常用的基本数据(加上代码和堆栈)都在16位速记距离之内。
ES用于访问其他所有内容(* 2),以及DS的64 KiB之外的所有内容。类似于文字处理程序的文本,电子表格的单元格或图形程序的图片数据等。与通常假定的情况不同,该数据的访问量不大,因此使用前缀比使用较长的地址字段所受的伤害小。
类似地,在进行字符串操作时可能必须加载(和重新加载)DS和ES只是一个小麻烦-至少这被其当时最好的字符处理指令集之一所抵消。
真正令人痛心的是,当用户数据超过64 KiB且必须开始操作时。尽管某些操作一次只对单个数据项完成(请考虑A=A*2
),但大多数操作需要两个(A=A*B
)或三个数据项(A=B*C
)。如果这些项目位于不同的段中,则每个操作将重新加载ES几次,从而增加了相当大的开销。
最初,使用来自8位世界(* 3)的小型程序和同样小的数据集,这没什么大不了的,但是它很快就成为了主要的性能瓶颈-尤其是对于程序员(和编译器)。最终,随着386英特尔的加入,英特尔又增加了另外两个段,从而可以进行任何一元,二进制或三元运算,并且元素分布在内存中,而无需始终重新加载ES。
对于编程(至少在汇编中)和编译器设计,这是一个很大的收获。当然,可能还有更多,但是有了三个瓶颈就基本消失了,因此无需过度操作。
明智地将字母F / G命名为E之后的字母连续。至少从CPU设计的角度来看,没有任何关联。
* 1-将ES用于字符串目标是一个例外,因为仅需要两个段寄存器。没有它们就没有太大用处-或始终需要一个段前缀。这可能会杀死令人惊讶的功能之一,即使用(非重复)字符串指令(由于其单字节编码)而导致极高的性能。
* 2-因此,事后看来,“其他所有细分”的命名方式要比“其他细分”更好。
* 3-牢记8086只是在8800完成之前的一个止步措施,始终是非常重要的,它主要用于嵌入式世界,以吸引8080/85客户。