段寄存器(Segment Registers)。在历史上,它们是实现内存分段机制的核心,但在现代64位操作系统(长模式)下,它们的作用已经发生了根本性变化,更多是作为“遗产”和“模式标记”存在。
简单来说,CS、SS、DS、ES是CPU内部用于访问内存的“基地址指针”,但它们指向的不是内存地址,而是内存段描述符表(GDT/LDT)中的一个索引,该索引指向一个描述符,其中定义了段的基地址、长度、访问权限和类型。
各段寄存器的作用
| 段寄存器 | 全称 | 主要用途 | 现代长模式下的角色 |
|---|---|---|---|
| CS | Code Segment(代码段) | 指向当前正在执行的代码段,包含RIP(指令指针)。 | 必须,且L=1(64位模式)、DPL=3(用户态)或0(内核态),其基址通常视为0。 |
| SS | Stack Segment(栈段) | 指向当前栈段,与RSP(栈指针)配合使用。 | 在64位模式下,基址固定为0,但仍用于特权级检查(如iret判断是否切换栈)。 |
| DS | Data Segment(数据段) | 指向默认的数据段,用于大部分数据访问。 | 基址固定为0,在64位模式下,大多数情况下被忽略,仅用于少数特殊指令(如mov到段寄存器)。 |
| ES | Extra Segment(附加段) | 用于字符串操作指令(如movs、cmps)的附加数据段。 | 基址固定为0,仅用于少数特定指令(如rep movs的扩展)。 |
| FS/GS | 附加数据段(用户态/内核态) | 用于线程局部存储(TLS)、CPU核心数据结构等。 | 可自定义基址(通过IA32_FS_BASE/IA32_GS_BASEMSR),是64位系统中唯一仍广泛使用的段寄存器。 |
| FS/GS(用户态) | 线程本地存储(TLS) | 每个线程有其自己的FS基址,指向TEB(线程环境块)等。 | 通过wrfsbase等指令切换,或通过swapgs在内核态/用户态间切换。 |
段选择子(Segment Selector)的结构
段寄存器中存储的是一个16位的段选择子,它不是一个直接地址,而是GDT(全局描述符表)或LDT(局部描述符表)中的一个偏移量。
+----------------+----------------+----------+ | Index (13位) | TI (1位) | RPL (2位)| +----------------+----------------+----------+
Index:在GDT/LDT表中的索引。
TI:0 表示GDT,1 表示LDT。
RPL:请求的特权级(0~3),用于权限检查。
段描述符(Segment Descriptor)的结构
段选择子指向的段描述符,才是真正定义段属性的地方。它包含了段的基地址、大小、类型和权限。
+------------------+------------------+------------------+------------------+ | Base (31:24) | Flags (G|D/B|L|AVL) | Limit (19:16) | Access Rights | +------------------+------------------+------------------+------------------+ | Base (23:16) | Base (15:0) | Limit (15:0) | +------------------+------------------+------------------+
Base:段的线性地址基址(在长模式下,通常为0)。
Limit:段的大小(在长模式下,通常忽略)。
Access Rights:段类型(代码/数据)、可读/可写、特权级(DPL)等。
64位长模式下的段寄存器
在IA-32e长模式(即x86_64)下,段寄存器的行为与保护模式有显著不同:
| 特性 | 保护模式(32位) | 长模式(64位) |
|---|---|---|
| 基址 | 可任意指定(最大4GB) | CS/SS/DS/ES基址强制为0 |
| 大小 | 由段限制(Limit)定义 | CS/SS/DS/ES大小视为全64位地址空间 |
| FS/GS | 可自定义基址 | FS和GS基址可由MSR独立设置(IA32_FS_BASE/IA32_GS_BASE) |
| 权限检查 | 通过段描述符中的DPL和选择子中的RPL共同决定 | 仍通过CS.DPL和RPL进行特权级检查(如iret) |
| 使用场景 | 几乎所有内存访问都通过段寄存器 | 除CS/SS用于权限控制外,DS/ES几乎被忽略,FS/GS用于特殊用途(TLS、CPU数据) |
Linux内核中swapgs的妙用
在x86_64上,FS和GS是少数仍被广泛使用的段寄存器。Linux内核通过IA32_GS_BASEMSR(在entry_64.S中由swapgs指令切换)来实现内核态与用户态之间的高效切换。
; 进入内核时 swapgs ; 将用户态GS基址存入 MSR,加载内核态GS基址 mov rax, [gs:0x10] ; 访问当前CPU的per_cpu数据结构 ; 返回用户态时 swapgs ; 恢复用户态GS基址 iretq
总结
| 寄存器 | 现代64位下的用途 | 备注 |
|---|---|---|
| CS | 标记当前特权级(内核/用户),基址固定为0 | 必须存在,L=1表示64位模式 |
| SS | 仅在iret等特权级切换时使用,基址固定为0 | 用于判断是否需要栈切换 |
| DS / ES | 基本弃用,基址固定为0 | 仅少数指令(如movs)会使用,但通常不设置 |
| FS | 线程局部存储(TLS) | 用户态指向TEB,通过wrfsbase切换 |
| GS | 内核CPU数据结构(如per_cpu) | 通过swapgs在用户态/内核态间切换 |