第1章:Intel x86架构基础
各位同学,咱们今天聊聊x86架构。说实话,我接触x86已经快二十年了,从最早的8086到现在的酷睿系列,这个架构的生命力确实惊人。你想想看,一个指令集能活四十多年,这本身就说明了很多问题。
1.1 x86指令集概览
x86指令集,说白了就是Intel处理器能听懂的语言。我刚开始做嵌入式时,总觉得x86指令太多太杂,后来慢慢发现,其实常用的也就那么几十条。
指令集主要分这几类:
- 数据传送指令:MOV、PUSH、POP、XCHG等
- 算术运算指令:ADD、SUB、MUL、DIV、INC、DEC
- 逻辑运算指令:AND、OR、XOR、NOT、TEST
- 控制转移指令:JMP、Jcc、CALL、RET、LOOP
- 字符串操作指令:MOVS、CMPS、SCAS、LODS、STOS
- 标志控制指令:CLC、STC、CLD、STD
重点提醒:在工业控制场景下,我最常用的是MOV、CALL、RET和条件跳转指令。其他的,用到再查手册就行,不用死记硬背。
举个例子,一个简单的数据搬运:
MOV AX, 0x1234 ; 将立即数送入AX寄存器
MOV BX, AX ; 将AX的值复制到BX
PUSH AX ; 将AX压入堆栈
POP CX ; 从堆栈弹出到CX
我在项目中遇到过一个问题:有同事用MOV指令直接操作内存地址,结果忘了考虑对齐问题,导致系统偶尔崩溃。嗯,这里要注意,x86虽然支持非对齐访问,但性能会大打折扣。
1.2 寄存器模型
x86的寄存器,我习惯把它们分成几组来记。这样思路清晰,用起来也顺手。
| 寄存器类别 | 32位名称 | 16位名称 | 主要用途 |
|---|---|---|---|
| 通用寄存器 | EAX | AX | 累加器,常用于算术运算 |
| EBX | BX | 基址寄存器 | |
| ECX | CX | 计数器,循环控制 | |
| EDX | DX | 数据寄存器,I/O操作 | |
| 索引寄存器 | ESI | SI | 源索引 |
| EDI | DI | 目的索引 | |
| 指针寄存器 | ESP | SP | 堆栈指针 |
| EBP | BP | 基址指针 | |
| 段寄存器 | CS | CS | 代码段 |
| DS | DS | 数据段 | |
| SS | SS | 堆栈段 | |
| ES/FS/GS | ES/FS/GS | 附加段 |
个人经验:我建议你在写底层代码时,尽量用EAX、EBX这些32位寄存器。虽然16位的也能用,但混用容易出问题。我曾经在一个中断服务程序里混用了AX和EAX,结果调试了整整两天才找到原因。
标志寄存器EFLAGS也很关键。它里面存着运算结果的状态:
- CF(进位标志):加法有进位或减法有借位时置1
- ZF(零标志):运算结果为0时置1
- SF(符号标志):结果为负数时置1
- OF(溢出标志):有符号数溢出时置1
- IF(中断标志):为1时允许可屏蔽中断
1.3 内存寻址模式
x86的内存寻址方式,说实话刚开始学的时候确实有点绕。但搞懂了之后,你会发现它其实很灵活。
常用的寻址模式有这些:
- 立即数寻址:操作数直接写在指令里
- 寄存器寻址:操作数在寄存器里
- 直接寻址:操作数的地址直接给出
- 寄存器间接寻址:地址放在寄存器里
- 基址变址寻址:基址+变址
- 相对基址变址寻址:基址+变址+偏移量
看几个例子就明白了:
; 立即数寻址
MOV AX, 100
; 寄存器寻址
MOV AX, BX
; 直接寻址
MOV AX, [0x1234]
; 寄存器间接寻址
MOV AX, [BX]
; 基址变址寻址
MOV AX, [BX+SI]
; 相对基址变址寻址
MOV AX, [BX+SI+10]
避坑指南:我曾经在写一个数据采集程序时,用了复杂的基址变址寻址,结果代码可读性极差。后来同事接手维护,花了大量时间才看懂。我建议你:能用简单寻址就别用复杂的,代码是写给人看的。
1.4 保护模式与实模式
这个问题,我每次上课都要重点讲。因为很多嵌入式工程师搞不清楚这两种模式的区别。
实模式是x86最原始的工作模式。特点就是简单直接:
- 20位地址线,最大寻址1MB
- 没有内存保护
- 段寄存器直接存段基址
- 物理地址 = 段基址×16 + 偏移量
保护模式就复杂多了,但功能也强大得多:
- 32位地址线,最大寻址4GB
- 有内存保护机制
- 段寄存器存的是选择子
- 通过描述符表来管理内存
- 支持虚拟内存
- 支持多任务
实模式下的地址计算:
; 实模式:物理地址 = CS×16 + IP
; 假设 CS = 0x1000, IP = 0x0020
; 物理地址 = 0x1000×16 + 0x0020 = 0x10020
保护模式下的地址转换:
; 保护模式:通过段选择子查找描述符
; 描述符中包含段基址、段界限、访问权限
; 线性地址 = 段基址 + 偏移量
; 如果启用分页,还要进一步转换
关键区别:实模式是"裸奔",保护模式是"穿盔甲"。在工业控制中,我强烈建议使用保护模式。为什么?因为一旦程序跑飞,实模式下的破坏力是毁灭性的。我见过一个案例,实模式下指针越界,直接把系统关键数据覆盖了,整个产线停了半天。
说到模式切换,这里有个细节要注意。从实模式切换到保护模式,需要经过几个步骤:
- 关闭中断
- 加载GDTR(全局描述符表寄存器)
- 设置CR0寄存器的PE位为1
- 执行远跳转刷新指令流水线
- 加载段寄存器
- 设置页表(如果需要分页)
- 开启中断
我的建议:如果你刚开始接触x86底层开发,先用实模式把基本功能调通。等对硬件足够熟悉了,再切换到保护模式。一口吃不成胖子,做嵌入式更是如此。
好了,这一章的内容就到这里。x86架构的知识点很多,但咱们慢慢来。下一章我会讲中断系统和异常处理,那才是真正考验功底的地方。