Long Mode
本文讨论x86-64CPU(AMD64和Intel的等效EM64T实现)。 IA-64 (Itanium) 是 真的比较独特,这里不讨论。
特性
长模式
长模式将通用寄存器扩展到64位 (RAX,RBX,RIP,RSP,RFLAGS等),并添加了八个额外的整数寄存器 (R8,R9,...,R15) 以及八个SSE寄存器 (XMM8至XMM15) 到CPU。 线性地址扩展到64位(然而,特定的CPU可能实现的小于64位),物理地址空间扩展到52位(特定的CPU可能实现的小于52位)。 实质上,长模式为CPU添加了另一种模式。
长模式不支持硬件任务切换或虚拟8086任务。 在长模式下,当前CS确定当前运行的代码是64位代码(真长模式)还是32位代码(兼容模式),甚至是16位保护模式代码(仍处于兼容模式)。 使用分页已成为强制性的,出于性能原因,分段已被剥离。
英特尔和AMD的第一个64位cpu都支持40位物理地址和48位线性地址。
长模式下的分段
除FS和GS两个寄存器外,长模式中的分段使用flat model。 通过两个特定的 Model Specific Register (MSR),FS.base (C000_0100h) 和GS.base (C000_0101h),可以为这两个段寄存器设置基址。
此外,还有一个名为SWAPGS的长模式特定指令,它交换GS.base和另一个名为KernelGSBase(C000_0102h)MSR的内容。 此指令对于跨上下文切换保存特定逻辑处理器内核的内核信息特别有用。 注意: 这是交换操作。
进一步信息
此功能概述这里并不完整。有关详细信息,请参阅[维基百科上的x86-64文章]。
设置
如何检测CPU是否为64位?
使用EAX = 0x80000001调用CPUID后,所有符合AMD64的处理器都在EDX的扩展功能标志 (位29) 中打开了具有longmode功能的位。 长模式还需要其他位;您可以在AMD通用指令参考(链接已失效,原作者的意思可能是“AMD64体系结构程序员手册第3卷:通用和系统指令”,可在此处找到:http://developer.amd.com/resources/developer-guides-manuals/)
如何启用长模式?
启用长模式的步骤是:
- 禁用分页
- 设置CR4中的PAE使能位
- 用PML4的物理地址加载CR3 (4级页面图)
- 通过在MSR 0xC0000080(又名EFER)中设置LME标志(位8)来启用长模式
- 启用分页
参考: Intel 64和IA-32体系结构软件开发人员手册,第9.8.5节
现在CPU将处于兼容模式,指令仍为32位。 要进入长模式,GDT代码段的D/B位(位22,第二个32位值)必须清零(与16位代码段一样),并且必须设置GDT代码段的L位(位21,第二个32位值)。 完成此操作后,CPU处于64位长模式。
在传统模式下运行的32位代码有限制吗?
x86-64处理器可以在传统模式下运行,它们仍然以实模式启动,并且16位和32位保护模式仍然可用(以及相关的虚拟8086模式)。 这意味着x86操作系统,甚至是DOS,仍然可以正常运行。 唯一的区别是,当使用PAE时,物理地址最多可达52位(或CPU实现的位数)。
但是,在长/兼容模式下不存在Virtual 8086 Mode。
如果您在多处理器系统上运行,则可以将一个处理器的启动IPI发送到加载真实模式程序的真实模式内存地址 (有关更多详细信息,请参阅英特尔多处理器规范)。 这种方法的主要问题是,它依赖于系统中存在多个处理器。
引导直接进入长模式
在激活长模式之前,必须进入保护模式。 必须建立最小保护模式环境,以允许长模式初始化。 此环境必须包括以下内容:
- 保护模式IDT,用于在保护模式下将中断和异常引导到适当的处理程序。
- IDT引用的受保护模式中断和异常处理程序。
- 每个处理程序的门描述符必须加载到IDT中。
- -AMD64 docs,第2卷,第14.4节 (启用保护模式),24593 Rev. 3.10 2005年2月
话虽如此,帖子我们有一个线程其中Brendan展示了如何在没有32位IDT和32位段的情况下启用64位长模式- 但是,请确保,在启用64位IDT之前,在长模式下发生的任何与分页相关的异常都会导致处理器因三重故障而重置。
通知BIOS
为了使系统内置的固件能够优化自身以在长模式下运行,AMD建议操作系统通知BIOS操作系统将在以下两种模式下运行的目标环境:32位模式、64位模式或这两种模式的混合模式。 这可以通过从实模式调用BIOS中断15h来完成,AX设置为0xEC00,BL设置为32位保护模式为1,64位长模式为2,如果两种模式都将使用,则为3。
64位环境模型
您需要考虑三种64位编程模型: LP64,ILP64,llp64。 每种模式都有自己的缺陷。 I/L/P分别代表Int、Long、Pointer;64是每种类型的位数。
LP64表示Long(以及Long Long)和指针为64位宽,Int为32位宽。 LLP64表示Long Long和指针为64位宽,Long和Int为32位宽。 ILP64表示Int、Long(和Long Long)和指针都是64位宽。
大多数 *nixes使用LP64模型,Windows使用LLP64约定。ILP64很少使用。
数据类型
此表列出了各种编程模型中的大小细分。
数据类型 | LP64 | ILP64 | LLP64 | ILP32 | LP32 |
---|---|---|---|---|---|
char | 8 | 8 | 8 | 8 | 8 |
short | 16 | 16 | 16 | 16 | 16 |
_int | 32 | -- | 32 | -- | -- |
int | 32 | 64 | 32 | 32 | 16 |
long | 64 | 64 | 32 | 32 | 32 |
long long | -- | -- | 64 | -- | -- |
pointer | 64 | 64 | 64 | 32 | 32 |
64位操作系统使用的型号
下表列出了当前一些64位操作系统的编程模型。
OS | Mode |
---|---|
Windows XP X64 | LLP64 |
Linux | LP64 |
FreeBSD/OpenBSD | LP64 |
Solaris | LP64 |
DEC OSF/1 Alpha | LP64 |
SGI Irix | LP64 |
HP UX 11 | LP64 |
文本段类型
另一件你必须记住的事情是,尽管地址空间(以及所有指针)是64位宽的,但是文本段中生成的代码很可能不是。 这是因为在默认情况下,GCC编译为只有32位立即数的“mov”指令。 这意味着64位程序仅限于2G,就像32位模式程序一样。
如果您见过这样的错误消息:
relocation truncated to fit: R_X86_64_32 against symbol
您的代码就是遇到这个问题。 对于汇编,您必须使用 “movabs” 指令而不是 “mov”,对于gcc,您需要使用 “-mcmodel” 参数选择不同的文本段模型。
Flag | Text Segment Addressing |
---|---|
-mcmodel=small | 程序及其符号必须链接到地址空间的较低2GB(这是默认型号) |
-mcmodel=large | 该模型不对段的地址和大小做任何假设。 |
-mcmodel=medium | 该程序链接在地址空间较低的2GB中。 小符号也放在那里。 尺寸大于-mlarge-data-threshold的符号被放入large data或bss部分,并且可以位于2GB以上。 |
-mcmodel=kernel | 内核在负2GB的地址空间中运行。这个模型必须用于Linux内核代码。 |
值得注意的是,不同架构的代码模型是不同的,因为它们与指令编码捆绑在一起。 例如,AArch64也有一个 “-mcmodel = tiny”,它允许1m寻址,x86_64未知。 对于AArch64“-mcmodel=small”有4G限制,而不是x86_64的2G限制。
另见
文章
- Intel EM64T
- Creating a 64-bit kernel
- BOOTBOOT bootloader
- Limine bootloader
- X86-64 Instruction Encoding
- Setting up long mode