IA32支持最基本的三个操作模式 (modes of operations):
1)Real-Address Mode
(当 CPU 启动或者重启,CPU就处于real-address mode.)
2)Protected Mode
3) System management mode (SMM)
操作模式决定哪些指令和哪些体系结构的特性可以被使用。
基本执行环境:
在IA32CPU上的运行的所有程序都使用IA32CPU提供的一组资源来执行指令,储存指令、数据、状态等信息。那些资源组成了IA32CPU的基本执行环境。
1)Address space
任何在IA32CPU上运行的程序、任务都可以使用多达4G的线性地址空间(liner address space)和多达64G的物理地址空间。
2) Basic registers
8个通用寄存器(generation-purpose registers), 6个段寄存器(segment registers),EFLAGS寄存器,EIP寄存器。
这些寄存器构成了执行通用指令的基本的执行环境。
3) Stack
支持过程调用和参数传递。
4) x87 FPU registers
5) MMX registers
6) XMM registers
除了基本执行环境提供的资源,IA32体系结构还提供如下资源来支持操作系统和系统级软件的开发:
1) I/O ports
从IO端口传输数据。
2) Control registers
5个控制寄存器 CR0~CR4,决定CPU运行模式和当前任务特性。
3) Memory management registers
GDTR,IDTR,LDTR,task register。
4) Debug registers
DR0~DR7控制、监视CPU的Debug操作。
5) Memory type range registers(MTRRS)
给一段内存赋予内存类型
6) Machine specific registers (MSRs)
控制和报告CPU的运行情况。
7) Machine check registers
发现并报告硬件错误。
8) Performance monitoring counters
内存模式 (memory mode)
一般情况,程序不会直接访问物理内存,而是使用一下内存模式
1) Flat memory model (平台内存模式)
对程序来说,内存是一个、连续的地址空间,代码、数据、栈都被包含在这个地址空间里。这个地址空间叫做线性地址空间(liner address space)。程序使用线性地址(liner address)进行寻址。
2) Segmented memory model
对程序来说,内存是一组、独立的地址空间(即:段),代码、数据、栈都被包含在这些各自独立的段内。程序使用逻辑地址(logical address)进行寻址。逻辑地址包含segment selector和offset。使用段的主要是因为增加系统、程序的可靠性,例如单独的stack段可以防止stack上的数据覆盖到code、data空间。
在内部,所有的系统定义的所有segment都被映射到liner address space。为了访问一个内存位置,CPU会把每个logical address 翻译成 liner address,这个翻译的过程对程序来说是透明的。
3) Real-address mode memory model
分页和虚拟内存
在 flat 或者segmented memory model时,线性地址(liner address space)(flat直接使用线性地址空间,segmented 会被翻译到线性地址空间)会被直接映射和分页映射到物理内存上。
直接映射(direct mapping)(paging disabled),每个线性地址一一对应到物理地址。线性地址超出物理地址的部分不会映射。
分页映射,线性地址空间被分成页。根据实际需要,虚拟内存中的页被映射到物理内存。当操作系统使用分页机制,分页机制对应用程序来说是透明的,应用程序只能看到线性地址空间。
基本程序运行寄存器(Basic Program Execution registers)
1) 通用寄存器(General-purpose registers)
8个32bit的通用寄存器:
EAX - 操作结果;
EBX - DS 段中指向数据的指针;
ECX - 字符串、循环操作的计数器;
EDX - I/O指针;
ESI - 指向DS段中的数据;字符串操作的源指针;
EDI - 指向ES段中的数据;字符串操作的目的指针;
ESP - SS 段中的栈指针;
EBP - SS段中的栈框指针;
2) 段寄存器(Segment registers)
6个16bit的段寄存器,每个段寄存器用来装载16bit的段选择子(segment selector )。段选择子是用来指向内存中段的特殊的指针,为了访问一个段,相应段的段选择子必须被加载到对应的段寄存器(即:如果访问Code段,那么Code段的段选择子必须被加载到段寄存器CS中)。
段寄存器的使用取决于操作系统使用的内存管理模式(memory management model)。
Flat memory model:每个段寄存器中的段选择子都指向同一个段。这个段就是整个线性地址空间。PS:一般有两个段,代码在一个段、栈和其他数据在另一个段,CS寄存器指向代码段,而其他寄存器指向栈和数据的段。
segmented memory model:一般每个段寄存器包含不同的段选择子,每个段选择子指向线性地址空间中的不同的段。同一时间内,一个程序能访问线性地址空间中的6个段,因为6个寄存器,指向6个段,如果访问其他段,先要让相应段寄存器加载指向该段的段选择子。
每个段寄存器关联三种存储类型(code、data、stack)中的一种。
CS 寄存器包含Code segment的段选择子,当一个执行要被执行,CPU会先从code segment中取出指令。CPU使用逻辑地址:segment selector->CS 寄存器:EIP->offset。CS寄存器不会被应用程序显示加载,只会被指令或者CPU内部操作来改变。
DS、ES、FS、GS 寄存器指向四个数据段。
3) EFLAGS
32bit 的EFLAGS寄存器包含着一组状态flags、控制flags和一组系统flags。其中一些位可以通过通用指令直接修改,但是IA32中没有指令可以对整个EFLAGS寄存器进行检验或修改。
当suspend一个task的时候(使用多任务机制),处理器自动把EFLAGS寄存器的状态储存在即将被suspend的task的TSS段中,然后把新task的TSS中的相应数据储存到EFLAGS寄存器中。
当发生中断处理或者异常处理时,处理器自动在stack上保存ELAGS的状态。如果通过任务切换(task switch)来进行中断处理或者异常处理,那么EFLAGS被保存在即将被suspend的task的TSS段中。
状态flags:CF、PF、AF、ZF、SF、OF;用来指明算数指令(ADD、SUB、MUL、DIV)的运行结果。
系统flags:系统flags和IOPL域用来控制操作系统。并且不能被应用程序修改。
TF - Trap flag :1表示允许debug的单步执行;0表示禁止单步执行。
IF - Interrupt enable flag : 控制处理器对可屏蔽中断的反应。1表示允许可屏蔽中断(maskable interrupt);0表示禁止可屏蔽中断。
IOPL - I/O privilege level field :表示当前程序或者当前任务的I/O优先级。当前运行程序或者当前任务的CPL(current privilege level - CPL)必须小于或者等于
要访问的I/O地址空间的I/O优先级。这个域只能在CPI等于0的时候,被POPF和IRET指令修改。
NT - 控制被中断或者被调用的任务链(任务的TSS含一个指针,指向前一个任务的TSS,从而,形成一个任务链)。1表示当前任务link前一个任务;0表示当
前任务没有link任何任务。
RF - Resume flags。
VM - Virtual-8086 mode flag。
AC - Alignment check flag。
VIF - Virtual interrupt flag。
VIP - Virtual interrupt pending flag。
ID - Identification flag。
4) EIP
指令指针寄存器(EIP)包含有当前code段中下一条将被执行的指令的offset。
EIP不能被软件直接访问(读&写),它被控制转移指令(JMP、CALL、RET)、中断、异常隐式修改。
读取EIP的唯一方法是调用CALL指令,然后从stack中的读取返回指令的地址,即是EIP。
修改EIP可以通过修改stack中的返回地址值、然后return(RET or IRET)的方法来间接修改。
所有的IA32处理器都预取指令,从而造成当load一个指令的时候,从总线上读到的指令地址和EIP中的值不匹配(即,不仅load了将要被执行的指令--EIP中的值,而且还load了将要被执行指令后面的许多指令)。甚至不同处理器使用不同的预取策略,但是所有IA32的处理器都的EIP指向下一条要被执行的指令。
操作数大小和地址大小
操作数大小:决定了操作数的大小(多少bits)。当强制操作数大小是16bits的时候,一般操作数大小是8bits或者16bits;当强制操作数大小是32bits的时候,操作数一般是8bits或者32bits。
地址大小:决定了寻址的范围(多少bits)。当强制地址大小是16bits的时候,段中的偏移是16bits,即一段最大是64KB(2^16 Bytes - 内存是以Bytes为单位访问,所以一个段最大是2^16 Bytes);当强制地址大小是32bits的时候,段中的偏移是32bits,地址空间达到了4GByts。
操作数寻址方式
IA32机器指令一般有0个或者多个操作数。有的操作数是显示指定的,有的是隐含的。
源操作数本身一般位于:
当一个指令返回操作结果给目的操作数, 目的操作数一般位于:
- 指令本身(立即数)
- 寄存器
- 内存中
- I/O端口
1) 立即数操作数
- 寄存器
- 内存中
- I/O端口
有的指令把源操作数写到指令本身里。
eg :
ADD EAX,14
除了DIV和IDIV指令外,所有的算数指令都允许吧源操作数放到指令本身里。
2) 寄存器操作数
根据不同指令,目的操作数和源操作数可以是寄存器。
eg:通用寄存器(8个)、段寄存器(6个)、EFLAGS、控制寄存器(CR0/CR2/CR3/CR4)、table指针寄存器(GDTR/LDTR/IDTR)、任务寄存器(task register)、debug registers(DR0/DR1/DR2/DR3/DR6/DR7)、MSR寄存器、x87 FPU 寄存器、MMX寄存器、XMM寄存器。
3) 内存操作数
内存中的源操作数和目的操作数可以用通过 "segment selector + offset" 的形式加以使用。段选择子(segment selector)指定包含该操作数的段,offset指定操作数 在段中的偏移。
- 指定段选择子
段选择子可以被隐式或者显式指定。
最普通的做法是,指定一个段选择子,然后把该段选择子装进一个段寄存器,而后处理器就可以使用这个寄存器了,相当于隐式指定了段选择子。隐式--因为处理器以后就不会指定段选择子了而是通过保存段选择子的寄存器来代表段选择子。一般都是使用隐式的,因为比如使用stack -- SS:[ESP],实际上SS寄存器已经装载了指向SS段的段选择子。(如果这里描述不清楚,那么相对看一下下面的“显式”就会清楚了。)
MOV ES:[EBX], EAX // 这个MOV指令把寄存器EAX中的值复制到ES寄存器指定的段中的EBX偏移处。(段选择子已经加载到了ES段寄存器中)
有些指令需要显式指定一个段选择子。
MOV DS, BX // MOV指令把BX寄存器中存放的段选择子(segment selector)放到DS段寄存器中。
- 指定offset
offset可以被指定为一个静态的值(displacement),或者通过计算得到。