第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的内存寻址方式,说实话刚开始学的时候确实有点绕。但搞懂了之后,你会发现它其实很灵活。

常用的寻址模式有这些:

  1. 立即数寻址:操作数直接写在指令里
  2. 寄存器寻址:操作数在寄存器里
  3. 直接寻址:操作数的地址直接给出
  4. 寄存器间接寻址:地址放在寄存器里
  5. 基址变址寻址:基址+变址
  6. 相对基址变址寻址:基址+变址+偏移量

看几个例子就明白了:

; 立即数寻址
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

保护模式下的地址转换:

; 保护模式:通过段选择子查找描述符
; 描述符中包含段基址、段界限、访问权限
; 线性地址 = 段基址 + 偏移量
; 如果启用分页,还要进一步转换

关键区别:实模式是"裸奔",保护模式是"穿盔甲"。在工业控制中,我强烈建议使用保护模式。为什么?因为一旦程序跑飞,实模式下的破坏力是毁灭性的。我见过一个案例,实模式下指针越界,直接把系统关键数据覆盖了,整个产线停了半天。

说到模式切换,这里有个细节要注意。从实模式切换到保护模式,需要经过几个步骤:

  1. 关闭中断
  2. 加载GDTR(全局描述符表寄存器)
  3. 设置CR0寄存器的PE位为1
  4. 执行远跳转刷新指令流水线
  5. 加载段寄存器
  6. 设置页表(如果需要分页)
  7. 开启中断

我的建议:如果你刚开始接触x86底层开发,先用实模式把基本功能调通。等对硬件足够熟悉了,再切换到保护模式。一口吃不成胖子,做嵌入式更是如此。

好了,这一章的内容就到这里。x86架构的知识点很多,但咱们慢慢来。下一章我会讲中断系统和异常处理,那才是真正考验功底的地方。