[关闭]
@xiaohaizi 2023-03-29T12:26:31.000000Z 字数 71654 阅读 145

80386文档

Intel


1、80386简介

The 80386 is an advanced 32-bit microprocessor。

处理器可寻址4GB的物理内存和64TB的虚拟内存。

1.1 手册的组织

手册分为5个部分:

  1. Part I ── Applications Programming
  2. Part II ── Systems Programming
  3. Part III ── Compatibility
  4. Part IV ── Instruction Set
  5. Appendices

前3个部分是解释性的,后2个部分是作为参考文件的。

80386有3种运行模式:

  1. Available in All Modes Part I ── Applications Programming
  2. Available in Protected Part II ── Systems Programming
  3. Mode Only
  4. Compatibility Modes Part III ── Compatibility

1.1.1 Part I ── Applications Programming

This part presents those aspects of the architecture that are customarily used by applications programmers.

1.1.2 Part II ── Systems Programming

This part presents those aspects of the architecture that are customarily used by programmers who write operating systems, device drivers, debuggers, and other software that supports applications programs in the protected mode of the 80386.

1.1.3 Part III ── Compatibility

1.1.4 Part IV ── Instruction Set

1.1.5 Appendices

● Introduction to the 80386, order number 231252
● 80386 Hardware Reference Manual, order number 231732
● 80386 System Software Writer's Guide, order number 231499
● 80386 High Performance 32-bit Microprocessor with Integrated Memory Management (Data Sheet), order number 231630

1.3 Notational Conventions

1.3.1 Data-Structure Formats

低地址出现在图中的右下方,然后向左上方增长。

1.3.2 Undefined Bits and Software Compatibility

1.3.3 Instruction Operands

  1. label: prefix mnemonic argument1, argument2, argument3

● A label is an identifier that is followed by a colon.
● A prefix is an optional reserved name for one of the instruction prefixes.
● A mnemonic is a reserved name for a class of instruction opcodes that have the same function.
● The operands argument1, argument2, and argument3 are optional. There may be from zero to three operands, depending on the opcode. When present, they take the form of either literals or identifiers for data items. Operand identifiers are either reserved names of registers or are assumed to be assigned to data items declared in another part of the program (which may not be shown in the example). When two operands are present in an instruction that modifies data, the right operand is the source and the left operand is the destination.

2、 Basic Programming Model

基本编程模型由下面几个方面组成:

I/O不包括在基本编程模型中。

2.1 内存组织和分段

2.1.1 The "Flat" Model

In a "flat" model of memory organization, the applications programmer sees a single array of up to 232 bytes (4 gigabytes).

2.1.2 分段模型

A segment is a unit of contiguous address space. Segment sizes may range from one byte up to a maximum of 2^32 bytes (4 gigabytes).

一个完整的地址空间指针由2部分组成(Figure 2-1):

  1. 一个段选择子(segment selector),一个16位的字段,标识某个段。
  2. 一个偏移量,一个32位的

    1. A segment selector, which is a 16-bit field that identifies a segment.
    2. An offset, which is a 32-bit ordinal that addresses to the byte level within a segment.

image_1grnahmup156d104p11os1mb8hi89.png-35.4kB

2.2 Data Types

image_1grnant89ru89ap1hrc128s1i74m.png-50.7kB

A byte is eight contiguous bits starting at any logical
address. The bits are numbered 0 through 7; bit zero is the least significant bit.

A word is two contiguous bytes starting at any byte address. A word thus contains 16 bits. The bits of a word are numbered from 0 through 15; bit 0 is the least significant bit. The byte containing bit 0 of the word is called the low byte; the byte containing bit 15 is called the high byte.

Each byte within a word has its own address, and the smaller of the addresses is the address of the word. The byte at this lower address contains the eight least significant bits of the word, while the byte at the higher address contains the eight most significant bits.

A doubleword is two contiguous words starting at any byte address. A doubleword thus contains 32 bits. The bits of a doubleword are numbered from 0 through 31; bit 0 is the least significant bit. The word containing bit 0 of the doubleword is called the low word; the word containing bit 31 is called the high word.

Each byte within a doubleword has its own address, and the smallest of the addresses is the address of the doubleword. The byte at this lowest address contains the eight least significant bits of the doubleword, while the byte at the highest address contains the eight most significant bits. Figure 2-3
illustrates the arrangement of bytes within words anddoublewords.

image_1grnb08jj114ktod83th3m1vgh13.png-56.4kB

Note that words need not be aligned at even-numbered addresses and doublewords need not be aligned at addresses evenly divisible by four. This allows maximum flexibility in data structures (e.g., records containing mixed byte, word, and doubleword items) and efficiency in memory utilization. When used in a configuration with a 32-bit bus, actual transfers of data between processor and memory take place in units of doublewords beginning at addresses evenly divisible by four; however, the processor converts requests for misaligned words or doublewords into the appropriate sequences of requests acceptable to the memory interface. Such misaligned data transfers reduce performance by requiring extra memory cycles. For maximum performance, data structures (including stacks) should be designed in such a way that, whenever possible, word operands are aligned at even addresses and doubleword operands are aligned at addresses evenly
divisible by four. Due to instruction prefetching and queuing within the CPU, there is no requirement for instructions to be aligned on word or doubleword boundaries. (However, a slight increase in speed results if the target addresses of control transfers are evenly divisible by four.)

2.3 Registers

The 80386 contains a total of sixteen registers that are of interest to the applications programmer. As Figure 2-5 shows, these registers may be grouped into these basic categories:

  1. General registers. These eight 32-bit general-purpose registers are used primarily to contain operands for arithmetic and logical operations.
  2. Segment registers. These special-purpose registers permit systems software designers to choose either a flat or segmented model of memory organization. These six registers determine, at any given time, which segments of memory are currently addressable.
  3. Status and instruction registers. These special-purpose registers are used to record and alter certain aspects of the 80386 processor state.

2.3.1 General Registers

80386的通用寄存器有:EAX, EBX, ECX, EDX, EBP, ESP, ESI, and EDI。它们用于包含一些算术和逻辑操作的操作数,也可以包含用于地址计算的操作数(ESP除外)。如下图所示:

image_1grnc7has1bjc1g8ph981n2f17b61g.png-48kB

8个寄存器的低16位可以有单独的名字并且可以被单独使用。这个特性对处理16位数据以及和8086/80286兼容是有用的。

16位寄存器被命名为AX, BX, CX, DX, BP, SP, SI, and DI.

AX, BX, CX, and DX的每个字节都可以有一个单独的名字并且可以独立使用。8位寄存器被命名为AH, BH, CH, and DH (high bytes); and AL, BL, CL, and DL(low bytes).

所有的通用寄存器都可以用来进行地址计算,以及存储大部分算术和逻辑运算结果。不过,一小部分功能只能使用特定的一些寄存器。

通过隐式选择这些寄存器,80386可以更紧凑的编码指令。这些使用特定寄存器的指令包括:double-precision multiply and divide, I/O, string instructions, translate, loop, variable shift and rotate, and stack operations.

2.3.2 Segment Registers

image_1grnm92f9b7lfclr623hp1t.png-36.5kB

image_1grnm9jut2r0f6n1sc21a8511o42a.png-23.5kB

image_1grnma58518hrnd818a01oq3qsj2n.pn60.2kB

2.3.3 Stack Implementation

三个寄存器有助于栈操作:

  1. 栈段(SS)寄存器。栈是在内存中实现的。一个系统可以有多个栈,这些栈只受最大段数的限制。一个栈可能长达4gb,这是一个段的最大长度。一次一个栈是可直接寻址的──由 SS定位的栈。这是当前栈,通常简称为“the”栈。SS 由处理器自动用于所有栈操作。

  2. 栈指针(ESP)寄存器。ESP 指向下推栈(TOS)的顶部。它被 PUSH 和 POP 隐式引用操作、子程序调用和返回以及中断操作。当一个项被压入栈时(见图 2-7),处理器递减 ESP,然后在新的 TOP写入该项。当一个项目弹出栈时,处理器从 TOP中复制它,然后递增 ESP。 换句话说,栈在内存中向较小的地址递减。

  3. 栈帧基址指针(EBP)寄存器。对于访问栈中的数据结构、变量和动态分配的工作空间,EBP是寄存器的最佳选择。EBP通常用于访问栈上相对于栈上固定点的元素,而不是相对于当前TOS 的元素。它通常标识为当前过程建立的当前栈帧的基址。当 EBP用作偏移量计算中的基址寄存器时,偏移量在当前栈段(即 SS 当前选择的段)中自动计算。因为SS不必明确指定,在这种情况下指令编码更有效。EBP 也可用于索引可通过其他段寄存器寻址的段。

image_1grnnklmi171tb37q9ue6rr8d9.png-29.2kB

2.3.4 Flags Register

标志寄存器是一个名为 e flags 的 32 位寄存器。这些标志控制某些操作并指示 80386 的状态。

EFLAGS 的低 16 位称为 FLAGS,可以作为一个单元来处理。该功能在执行 8086 和 80286 代码时很有用,因为 EFLAGS 的这一部分与 8086 和 80286 的标志寄存器相同。

标志可以分为三组:状态标志、控制标志和系统标志。系统标志的讨论推迟到第二部分。

image_1grnnnoi51kom99o12136q7hvmm.png-74.1kB

2.3.4.1 Status Flags

EFLAGS 寄存器的状态标志允许一个指令影响后续指令。算术指令使用OF,SF、ZF、AF、PF 和 cf。SCAS(扫描字符串)、CMPS(比较字符串)和LOOP指令使用 ZF 来表示它们的操作已经完成。在执行算术指令之前,有设置、清除和补充 CF 的指令。各状态标志的定义请参考附录 C。

2.3.4.2 Control Flag

EFLAGS 寄存器的控制标志 DF 控制字符串指令。DF(方向标志,第 10 位)

设置 DF 会导致字符串指令自动递减;即从高地址到低地址处理字符串。清除 DF 会导致字符串指令自动递增,或者从低位地址到高位地址处理字符串。

2.3.4.3 Instruction Pointer

指令指针寄存器(EIP)包含要执行的下一条顺序指令相对于当前代码段开始的偏移地址。程序员不能直接看到指令指针;它由控制转移指令、中断和异常隐式控制。

如图 2-9 所示,EIP 的低阶 16 位被命名为IP,可以被处理器作为一个单元使用。当执行为 8086 和80286 处理器设计的指令时,这一功能很有用。

image_1grnnutm21bk7pbjebe1n9cpp113.png-19.2kB

2.4 Instruction Format

80386 指令中编码的信息包括要执行的操作的说明、要操作的操作数的类型以及这些操作数的位置。

如果操作数位于内存中,指令还必须显式或隐式地选择哪个当前可寻址的包含该操作数的段。

80386 指令由各种元素组成,具有各种格式。说明的确切格式见附录B;指令的元素描述如下。在这些指令元素中,只有操作码始终存在。其他元素可能存在,也可能不存在,这取决于所涉及的特定操作以及操作数的位置和类型。指令的元素按出现的顺序如下:

2.5 操作数选择

一条指令可以操作零个或多个操作数,这些操作数是由该指令操作的数据。零操作数指令的一个例子是NOP(无操作)。操作数可以位于以下任何位置:

由于必须从内存中取出内存操作数,所以对立即操作数和寄存器中的操作数的访问速度比内存中的操作数更快。寄存器操作数在 CPU 中可用。CPU中也有立即操作数,因为它们是作为指令的一部分被预取的。

在有操作数的指令中,有些指令隐式地指定了操作数;其他的显式指定操作数;还有一些使用隐式和显式规范的组合。

一条 80386 指令可以显式引用一个或两个操作数。双操作数指令,如 MOV、加法、异或等。,通常用结果覆盖两个参与操作数中的一个。因此,可以区分源操作数(不受操作影响的操作数)和目标操作数(被结果覆盖的操作数)。

80386 的显式双操作数指令允许以下类型的操作:
● Register-to-register
● Register-to-memory
● Memory-to-register
● Immediate-to-register
● Immediate-to-memory

然而,某些字符串指令和堆栈操作指令将数据从一个存储器传送到另一个存储器。一些字符串指令的两个操作数都在内存中,并且是隐式指定的。推入和弹出堆栈操作允许在内存操作数和基于内存的堆栈之间传输。

2.5.1 立即操作数

某些指令使用指令本身的数据作为一个(有时两个)操作数。这样的操作数称为立即操作数。操作数可以是32 位、16 位或 8 位长。

2.5.2 寄存器操作数

操作数可能位于 32 位通用寄存器(EAX、EBX、ECX、EDX、ESI、EDI、ESP 或 EBP)之一,16 位通用寄存器(AX、BX、CX、DX、SI、DI、SP 或 BP)之一,或 8 位通用寄存器(AH、BH、CH、DH、al、BL、CL 或 DL)之一。

80386 具有参考段寄存器(CS、DS、ES、SS、FS、GS)的指令。只有当系统设计者选择了分段内存模型时,应用程序才使用这些指令。

80386 也有参考标志寄存器的指令。标志可以存储在堆栈中,并从堆栈中恢复。某些指令直接在 EFLAGS寄存器中改变通常被修改的标志。其他很少修改的标志可以通过堆栈中的标志映像间接修改。

2.5.3 内存操作数

在内存中寻址操作数的数据操作指令必须(直接或间接)指定包含操作数的段以及操作数在段内的偏移量。

访问内存的 80386 数据操作指令使用下列方法之一来指定内存操作数在其段内的偏移量:

  1. 大多数访问内存的数据操作指令都包含一个字节,该字节明确指定了操作数的寻址方法。操作码后面有一个称为modR/M字节的字节,它指定操作数是在寄存器中还是在内存中。如果操作数在内存中,则地址是由段寄存器和下列值计算出来的:基址寄存器、变址寄存器、比例因子、位移。使用索引寄存器时,modR/M 字节后还会跟随另一个字节,用于标识索引寄存器和比例因子。这种寻址方法是最灵活的。

  2. 一些数据操作指令隐含地使用专门的寻址方法:
    ● 对于一些隐式使用 EAX 寄存器的MOV的简短形式,操作数的偏移量在指令中被编码为双字。不使用基址寄存器、变址寄存器或比例因子。
    ● 字符串操作通过 DS:ESI(MOVS,CMPS、OUTS、LODS、SCAS)或通过 ES:EDI (MOVS、CMPS、INS、STOS)。
    ● 堆栈操作通过 SS:ESP 寄存器隐式寻址操作数;例如PUSH、POP、PUSHA、PUSHAD、POPA、POPAD、PUSHF、PUSHFD、POPF、POPFD、CALL、RET、IRET、IRETD、异常和中断。

2.5.3.1 段的选择

数据操作指令不需要明确指定使用哪个段寄存器。对于所有这些指令,指定段寄存器是可选的。对于所有的内存访问,如果指令没有明确指定段,处理器会根据表 2-1 的规则自动选择一个段寄存器。

image_1grnpla201u2u1q531j2t1h341spg1g.png-48.3kB

内存引用的种类和操作数所在的段之间有着密切的联系。通常,内存引用意味着当前数据段(即,隐式段选择器在 DS 中)。

但是,ESP 和 EBP 用于访问堆栈上的项目;因此,当 ESP 或 EBP寄存器用作基址寄存器时,当前堆栈段是隐含的(即 SS 包含选择器)。

可以使用特殊的指令前缀元素来覆盖默认的段选择。段替代前缀允许显式段选择。80386 的每个段寄存器都有一个段覆盖前缀。只有在下列特殊情况下,段前缀才不能覆盖隐含的段选择:

● 在字符串指令中对目标字符串使用 ES。
● SS 在堆栈指令中的使用。
● 使用 CS 进行指令提取。

2.5.3.2 Effective-Address Computation

modR/M 字节提供了最灵活的寻址方法,需要 modR/M字节作为指令第二个字节的指令在 80386 指令集中最常见。对于 modR/M 定义的内存操作数,所需段内的偏移量通过取最多三个分量的和来计算:

● A displacement element in the instruction.
● A base register.
● An index register. The index register may be automatically multiplied by a scaling factor of 2, 4, or 8.

将这些部分相加得到的偏移量称为有效地址。有效地址的每个组成部分都可能有正值或负值。如果所有部分的总和超过 2^32,则有效地址被截断为 32 位。图 2-10 展示了 modR/M 寻址的全部可能性。

image_1grnq5lgfjgstcrhk51is01imj1t.pngg36.7kB

因为displacement被编码在指令中,所以它对于寻址的固定方面是有用的;例如:

● Location of simple scalar operands.
● Beginning of a statically allocated array.
● Offset of an item within a record.

The base and index具有相似的功能。两者都使用同一套通用寄存器。两者都可以用于动态确定的寻址方面;例如:

● Location of procedure parameters and local variables in stack.
● The beginning of one record among several occurrences of the same
record type or in an array of records.
● The beginning of one dimension of multiple dimension array.
● The beginning of a dynamically allocated array.

通用寄存器作为base or index组件的用途在以下方面有所不同:

● ESP 不能用作索引寄存器。
● 当 ESP 或 EBP 用作基址寄存器时,默认段为被SS选中的那个。在所有其他情况下,默认段是 DS。

在通常情况下,当数组元素为 2、4 或 8字节宽时,比例因子允许有效地索引到数组中。变址寄存器的移位由处理器在评估地址时完成,不会造成性能损失。这消除了对单独的移位或乘法指令的需要。

The base, index, and displacement可以用在任何组合;这些组件中的任何一个都可能为空。只有在同时使用index时,才能使用比例因子。每种可能的组合对于高级语言和汇编语言中程序员常用的数据结构都很有用。以下是地址成分的一些不同组合的可能用法。

The uses of general registers as base or index components differ in the following respects:

2.6 中断和异常

80386 有两种中断程序执行的机制:

  1. 异常是CPU执行指令期间检测到特定事件时的同步事件。

  2. 中断是异步事件,通常由需要注意的外部设备触发。

中断和异常是相似的,因为它们都使处理器暂时中止当前的程序执行,以便执行更高优先级的程序。

这两种中断的主要区别在于它们的来源。异常总是可以通过以下重新执行导致异常的程序和数据来重现,而中断通常独立于当前正在执行的程序。

应用程序员通常不关心中断服务。系统程序员可以在第9章找到更多关于中断的信息。

表 2-2 强调了应用程序员可能感兴趣的异常。

image_1grpg90b61uclpb11865l7ovmb34.png-42.7kB

● 当指令 DIV 或IDIV以零分母执行时,或者当商对于目标操作数来说太大时,会产生除法错误异常。(参见第 3 章对 DIV 和 IDIV 的讨论。)
● 调试异常可以被反映回应用程序,如果它是由陷阱标志(TF)引起的。
● 当执行指令 INT3时,会产生断点异常。这个指令被一些调试器用来在特定点停止程序执行。
● 当执行 INTO 指令并且设置了 OF(溢出)标志时(在设置了 OF 标志的算术运算之后),会产生溢出异常。(有关 INTO 的讨论,请参考第 3 章)。
● 当执行BOUND指令并且它检查的数组索引超出数组的边界时,会导致边界检查异常。(有关BOUND指令的讨论,请参考第 3 章。)
● 一些应用程序可能会使用无效的操作码来扩展指令集。在这种情况下,无效操作码异常提供了模拟操作码的机会。
● 如果程序包含用于协处理器的指令,但系统中没有协处理器,则会出现“协处理器不可用”异常。
● 当协处理器检测到非法时,产生协处理器错误操作。

每当执行 INT 指令时,都会产生一个中断;处理器将该中断视为异常。这种中断的影响(以及所有其他异常的影响)由应用程序提供的异常处理程序例程或作为系统软件的一部分(由系统程序员提供)来确定。INT指令本身将在第3章讨论。有关异常的更完整描述,请参考第 9 章。

3、 Applications Instruction Set

本章概述了程序员可以用来为80386编写在受保护虚拟地址方式下执行的应用软件的指令。这些说明按照相关功能的类别进行分组。

本章没有讨论的指令通常只由操作系统程序员使用。第二部分描述了这些指令的操作。

本章的描述假设80386在保护模式下运行,有效的32位寻址;然而,当 16 位寻址在保护模式、实模式或虚拟 8086 模式下有效时,所有讨论的指令也是可用的。有关各种模式下存在的操作差异。

3.1 数据移动指令

这些指令提供了在存储器和基本架构的寄存器之间移动数据的字节、字或双字的便利方法。它们分为以下几类:

  1. 通用数据移动指令。
  2. 堆栈操作指令。
  3. 类型转换指令。

3.1.1 通用数据移动指令

MOV(移动)将一个字节、一个字或双字从源操作数传送到目标操作数。MOV 指令对于沿着这些路径传输数据是有用的。也有 MOV 的变体在段寄存器上操作。这些将在本章后面的章节中介绍。:
● 从内存到寄存器
● 从寄存器到存储器
● 通用寄存器之间
● 立即数据到寄存器
● 立即数据到存储器

不允许 MOV 指令在内存之间或段寄存器之间移动。但是,可以通过字符串移动指令 MOVS 执行内存到内存的移动。

XCHG(交换)交换两个操作数的内容。这条指令代替了三条 MOV 指令。它不需要一个临时位置来保存一个操作数的内容,同时加载另一个操作数。XCHG对于实现信号量或类似的进程同步数据结构特别有用。

XCHG 指令可以交换两个字节操作数、两个字操作数或两个双字操作数。XCHG 指令的操作数可以是两个寄存器操作数,或者一个寄存器操作数和一个存储器操作数。当与内存操作数一起使用时,XCHG 会自动激活锁定信号。(有关总线锁的更多信息,请参考第 11 章。)

3.1.2 栈操作指令

PUSH (Push)递减堆栈指针(ESP),然后将源操作数转移到 ESP 指示的堆栈顶部(见图3-1)。PUSH通常用于在调用过程之前将参数放在堆栈上;它也是在堆栈上存储临时变量的基本方法。PUSH 指令对内存操作数、立即操作数和寄存器操作数(包括段寄存器)进行操作。

PUSHA (Push All Registers)保存堆栈上八个通用寄存器的内容(见图 3-2)。此指令通过减少在一个过程中保留通用寄存器内容所需的指令数量,简化了过程调用。处理器按以下顺序将通用寄存器推入堆栈:EAX、ECX、EDX、EBX、EAX推入前ESP的初始值、EBP、ESI 和 EDI。PUSHA由 POPA 指令执行相反操作。

POP (Pop)将当前栈顶(由ESP指示)的字或双字传输到目标操作数,然后递增 ESP,指向新的栈顶。参见图3-3。POP将信息从堆栈移动到通用寄存器或内存中。还有一种POP操作段寄存器。这将在本章后面的章节中介绍..

POPA(弹出所有寄存器)恢复由PUSHA保存在堆栈上的寄存器,除了它忽略 ESP 保存的值。参见图 3-4。

image_1grqf8f3a5nq1g4t1vhld1h67u3h.png-45.1kB

image_1grqf8pek7snarb1sla4rb1n5s3u.png-63.8kB

3.1.3 类型转换指令

类型转换指令将字节转换为字,将字转换为双字,将双字转为 64 位项(四字)。这些指令对于转换有符号整数特别有用,因为它们自动用较小项的符号位的值填充较大项的多余位。如图3-5 所示,这种转换叫做符号扩展。

image_1grqfrssa19g7163p1hta1drc13ql4b.png-34.5kB

有两类类型转换指令:

  1. CWD、CDQ、CBW和CWDE只对EAX寄存器中的数据进行操作。
  2. MOVSX 和 MOVZX形式,允许一个操作数在任何通用寄存器中,同时允许另一个操作数在内存或寄存器中。

CWD(将字转换为双字)和CDQ(将双字转换为四字)将源操作数的大小加倍。CWD 扩展了寄存器 AX 至寄存器 DX 中的字。CDQ把 EAX 的双字符号扩展到整个EDX。CWD可用于从字除法之前的字产生双字被除数,而CDQ可用于从双字除法之前的双字产生四字被除数。CBW(将字节转换为字)将寄存器a1的字节符号扩展到整个 AX。CWDE(将字转换为双字扩展)将寄存器AX中的字的符号扩展到整个 EAX。

MOVSX(带符号扩展移动)将 8 位值符号扩展为 16 位值,将 8 位或 16 位值符号扩展为 32 位值。

MOVZX(零扩展移动)通过插入高位零,将 8 位值扩展为 16 位值,将 8 位或 16 位值扩展为 32 位值。

3.2 二进制算术指令

80386 处理器的算术指令简化了二进制编码的数字数据的操作。运算包括标准的加、减、乘、除以及递增、递减、比较和改变符号。支持有符号和无符号二进制整数。二进制算术指令也可以用作对十进制整数执行算术运算的过程中的一个步骤。

许多算术指令对有符号和无符号整数都进行运算。这些指令更新标志 ZF、CF、SF和OF,以便后续指令可以将算术结果解释为有符号或无符号。CF 包含与无符号整数相关的信息;SF 和 OF 包含与有符号整数相关的信息。ZF与有符号和无符号整数都相关;

这些状态标志通过执行两个条件指令系列中的一个来测试:Jcc(条件 cc 跳转)或 SETcc(条件字节设置)。

3.2.1 加法和减法指令

3.2.2 比较和符号改变指令

3.2.3 乘法指令

3.2.4 除法指令

3.3 十进制算术指令

3.4 逻辑指令

该组逻辑指令包括:
● 布尔运算指令
● 位测试和修改指令
● 位扫描指令
● 旋转和移位指令
● 条件下的字节设置

3.4.1 布尔运算指令

逻辑运算是 AND、OR、XOR 和 NOT。

NOT(非)反转指定操作数中的位,形成操作数的一反码。NOT 指令是一元运算,它在寄存器或内存中使用单个操作数。NOT 对标志没有影响。

“与”、“或”和“异或”指令执行标准的逻辑运算“与”、“或”、“异或”。这些指令可以使用以下操作数组合:

● 两个寄存器操作数
● 具有存储器操作数的通用寄存器操作数
● 带有通用寄存器操作数或内存操作数。

“与”、“或”、“与”、“异或”清除OF、CF,不定义 AF,并更新 SF、ZF 和 PF。

3.4.2 位测试和修改指令

这组指令对单个位进行操作,该位可以在内存或通用寄存器中。该位的位置被指定为从操作数的低位开始的偏移量。偏移量的值可以由指令中的立即字节给出,也可以包含在通用寄存器中。

这些指令首先将所选位的值分配给CF,即进位标志。然后,如操作所确定的,新值被分配给所选择的位。OF、SF、ZF、AF、PF 处于未定义状态。表 3-1 定义了这些说明。

image_1grum1pc7kt31f6f1kfj8m98h14o.png-29.5kB

3.4.3 位扫描指令

这些指令扫描一个字或双字来查找某个位,并将第一个设置位的索引存储到寄存器中。被扫描的位串可以在寄存器中或者在存储器中。如果整个字为零(没有找到设置位),则ZF标志被设置;如果发现一位,ZF被清除。如果没有找到设置位,则目标寄存器的值是未定义的。

BSF(位向前扫描)从低阶到高阶扫描(从位索引零开始)。
BSR(位扫描反向)从高阶到低阶扫描(从一个字的位索引 15 或一个双字的索引 31 开始)。

3.4.4 移位和旋转指令

shift 和 rotate 指令重新定位指定操作数内的位。
这些指令分为以下几类:
● Shift指令
● 双Shift指令
● rotate指令

3.4.4.1 Shift指令

字节、字和双字中的位可以进行算术或逻辑移位。根据指定计数的值,位最多可以移位 31 位。

Shift指令可以用三种方式之一来指定计数。

最后一种形式允许移位计数成为程序在执行过程中提供的变量。仅使用 CL 的低 5 位。

CF 总是包含移出目标操作数的最后一位的值。在单位移位中,如果操作改变了高阶(符号)位的值,则OF置位。否则,的将被清除。但是,在多位移位之后,的内容总是未定义。

移位指令提供了一种用二进制幂完成除法或乘法的简便方法。然而,请注意,通过右移进行的有符号数除法与IDIV指令执行的除法不是同一种除法。

3.4.5 条件字节设置指令

这组指令根据status flags定义的16个条件中的任何一个将一个字节设置为 0 或 1。该字节可以在存储器中,或者可以是单字节通用寄存器。这些指令对于在 Pascal等高级语言中实现布尔表达式特别有用。

SETcc(在条件 cc 上设置字节)如果条件 cc 为真,则将一个字节设置为 1;否则将字节设置为零。有关可
能情况的定义,请参考附录 D。

3.4.6 测试指令

TEST(测试)执行两个操作数的逻辑“与”,清除和 CF,不定义 AF,并更新 SF、ZF和PF。这些标志可以通过条件控制转移指令或条件字节设置指令来测试。操作数可以是双字、字或字节。

test和and的区别在于测试不会改变目标操作数。TEST 与 BT 的不同之处在于,TEST适用于在一次操作中测试多个位的值,而 BT 测试单个位。

3.5 控制转移指令

80386 提供有条件和无条件的控制转移指令来指导执行流程。条件控制转移取决于影响标志寄存器的操作结果。

无条件控制转移总是被执行。

3.5.1 无条件转移指令

JMP、CALL、RET、INT和IRET指令将控制从一个代码段位置转移到另一个位置。这些位置可以在同一代码段内(近控制传输),也可以在不同的代码段内(远控制传输)。这些将控制权转移给其它段的指令的变体将在本章的后一节讨论。

如果在一个特定的80386应用程序中使用的存储器组织模型不能使段对应用程序程序员可见,段间控制转移就不能使用。

3.5.1.1 转移指令

JMP(跳转)无条件地将控制权转移到目标位置。

JMP是执行单向转移;它不在堆栈上保存返回地址。

JMP 指令总是执行相同的基本功能,将控制从当前位置转移到新位置。它的实现根据地址是直接在指令中指定还是通过寄存器或存储器间接指定而有所不同。

直接 JMP 指令包括目的地址作为指令的一部分。间接 JMP 指令通过寄存器或指针变量间接获得目的地址。

直接近 JMP。直接JMP使用指令中包含的相对位移值。位移是带符号的,位移的大小可以是字节、字或双字。处理器通过将这个相对位移加到包含在EIP中的地址来形成一个有效地址。当加法运算完成后,EIP 指的是要执行的下一条指令。

间接近 JMP。间接 JMP 指令以几种方式之一指定绝对地址:

  1. 该程序可以 JMP到通用寄存器指定的位置(EAX、EDX、ECX、EBX、EBP、ESI 或 EDI 中的任何一个)。处理器将这个 32 位值移入 EIP 并继续执行。

  2. 处理器可以从指令中指定的存储器操作数获得目的地址。

  3. 寄存器可以修改存储器指针的地址来选择目的地址。

3.5.1.2 call指令

CALL (Call Procedure)激活一个out-of-line过程,将调用后的指令地址保存在堆栈上,供 RET(RET)指令稍后使用。调用将 EIP 的当前值放入堆栈。被调用过程中的RET指令使用这个地址将控制转移回调用程序。

call指令,像 JMP指令一样,有相对的、直接和间接的版本。

间接调用指令以下列方式之一指定绝对地址:
1. 该程序可以调用通用寄存器指定的位置(EAX、EDX、ECX、EBX、EBP、ESI 或 EDI 中的任何一
个)。处理器将这个 32 位值移入 EIP。
2. 处理器可以从指令中指定的存储器操作数获得目的地址。

3.5.1.3 返回和从中断返回指令

RET(RET From Procedure)终止一个过程的执行,并通过堆栈上的反向链接将控制转移给最初调用该过程的程序。RET恢复由前一个 CALL 指令保存在堆栈上的 EIP 的值。

RET 指令可以选择指定一个立即操作数。通过将这个常量添加到新的栈顶指针,RET有效地移除了调用程序在CALL指令执行之前在栈上推送的任何参数。

IRET(从中断返回)将控制返回到被中断的过程。IRET 与 RET 的不同之处在于,它也将堆栈中的标志弹出到标志寄存器中。中断机制将标志存储在堆栈中。

3.5.2 有条件转移指令

条件转移指令是转移,可能转移控制,也可能不转移控制,这取决于指令执行时 CPU 标志的状态。

3.5.2.1 条件转移指令

表 3-2 显示了条件转移助记符及其解释。成对列出的条件转移实际上是同一条指令。汇编程序为程序清单提供了更清晰的可选助记符。

条件转移指令包含一个位移量,如果条件为真,该位移量被加到 EIP 寄存器中。位移可以是一个字节、一个字或一个双字。位移是有符号的;因此,它可以用于向前或向后跳跃。

image_1grunlt4l1l35hk417p68181r7p6c.png-93.8kB

3.5.2.2 loop指令

loop指令是条件转移,使用ECX中的值来指定软件循环的重复次数。

所有loop指令自动递减ECX,直到当ECX=0时,终止循环。五个循环指令中的四个指定了涉及ZF的条件,该条件在ECX到达零之前终止循环。

3.5.2.3 执行循环或重复零次

如果在 ECX 找到零值,JCXZ(如果ECX为零则跳转)分支到指令中指定的标签。JCXZ与循环指令以及字符串扫描和比较指令结合使用时非常有用,所有这些指令都会减少ECX。有时,如果ECX 中的 count 变量被初始化为零,则需要设计一个执行零次的循环。

因为循环指令(和重复前缀)在测试ECX之前会递减它,所以如果程序进入在 ECX用零值循环。程序员可以用JCXZ很方便地解决这个问题,如果JCXZ执行时ECX为零,程序就能在循环中绕过代码分支。当与重复的字符串扫描和比较指令一起使用时,JCXZ 可以确定重复是由于ECX为零还是由于满足扫描或比较条件而终止。

3.5.3 软件产生的中断

INT n、INTO 和BOUND指令允许程序员指定从程序内部到中断服务程序的传输。

BOUND指令包括两个操作数。第一个操作数指定被测试的寄存器。第二个操作数包含有效的两个有符号边界限值的相对地址。绑定指令假设上限和下限在相邻的内存位置。这些极限值不能是寄存器操作数;如果是,则会出现无效操作码异常。

在使用新的索引值访问数组中的元素之前,BOUND对于检查数组边界非常有用。BOUND提供了一种简单的方法,在程序覆盖超出数组限制的位置的信息之前,检查索引寄存器的值。指定数组下限和上限的内存块通常位于数组本身之前。这使得数组边界可以从数组开始处的常量偏移量处访问。

因为数组的地址已经存在于寄存器中,所以这种做法避免了为获得数组边界的有效地址而进行的额外计算。

上限值和下限值可以是一个字或双字。

3.6 String and Character Translation指令

此类指令对串进行操作,而不是对逻辑值或数值进行操作。有关串 I/O 指令(也称为块 I/O)的信息,请参考 I/O 部分。

80386 串操作的强大功能源于该体系结构的以下特性:

  1. 一组基本的串操作

    • MOVS ──移动串
    • CMPS ──比较串
    • SCAS ──扫描串
    • LODS ──加载串
    • STOS ──存储串
  2. Indirect, indexed addressing, with automatic incrementing or decrementing of the indexes.

    Indexes:

    • ESI ── Source index register
    • EDI ── Destination index register

    Control flag:

    • DF ── Direction flag

    Control flag instructions:

    • CLD ── Clear direction flag instruction
    • STD ── Set direction flag instruction
  3. 重复前缀

    • REP ──当 ECX 不是 0
    • REPE/REPZ 时重复──当等于或为零时重复
    • REPNE/REPNZ ── Repeat while not equal or not zero

基本串操作对串中的一个元素进行操作。串元素可以是字节、字或双字。串元素由寄存器 ESI 和 EDI寻址。每次基本操作后
ESI 和/或 EDI 会自动更新以指向字符串的下一个元素。如果方向标志为零,则索引寄存器递增;如果为1,则递减。增量或减量的数量为1、2 或 4,具体取决于串元素的大小。

3.6.1 重复前缀

重复前缀 REP(当 ECX 不为零时重复),REPE/REPZ(重复
While Equal/Zero)和 REPNE/REPNZ(Repeat While Not Equal/Not Zero)指定串基本的重复操作。

这种形式的迭代使CPU处理字符串的速度比常规软件循环快得多。当一个基本的字符串操作有一个repeat前缀时,该操作被重复执行,每次使用字符串的不同元素。当满足前缀指定的条件之一时,重复终止。

在每次重复原语指令时,串操作可能会被暂时挂起,以便处理异常或外部中断。中断后,串操作可以从中断的地方重新开始。这种处理字符串的方法允许对任意长度的字符串进行操作,而不会影响中断响应。

所有三个前缀使得硬件自动重复相关的字符串原语,直到 ECX=0。重复前缀之间的差异与第二个终止条件有关。REPE/REPZ 和 REPNE/REPNZ专用于SCAS(扫描字符串)和CMPS(比较字符串)原语。当使用这些前缀时,下一条指令的重复依赖于零标志(ZF)以及 ECX 寄存器。ZF在执行重复的字符串指令之前不需要初
始化,因为 SCAS 和 CMPS 都根据它们进行比较的结果来设置 ZF。附表中总结了这些差异。

image_1gruovbuf17p21ss71np5212db6p.png-15.6kB

3.6.2 索引和方向标志控制

串基本指令的操作数的地址由ESI和EDI寄存器。ESI指向源操作数。默认情况下,ESI指的是DS段寄存器所指示的段中的位置。然而,可以使用段覆盖前缀来使 ESI 指代 CS、SS、ES、FS 或 GS。EDI 指向由ES指示的段中的目的操作数;目的操作数的段寄存器不能被覆盖。

在一条指令中使用两个不同的段寄存器允许字符串在不同的段之间移动。ESI 和 DSI 的这种使用分别导致了 ESI 和 EDI 寄存器的描述性名称源索引和目的地索引。总共然而,除了串指令之外,ESI 和 EDI 寄存器也可以用作通用寄存器。

当 ESI 和 EDI 用于串原语时,它们会在to操作后自动递增或递减。方向标志决定它们是递增还是递减。指令CLD将DF置零,使变址寄存器递增;指令STD将1放入DF,导致变址寄存器递减。在过程中使用字符串指令之前,程序员应该总是在 DF 中输入一个已知的值。

3.6.3 串指令

当 REPE 或 REPNE前缀修改SCAS或CMPS原语时,处理器将当前字符串元素的值与双字元素的 EAX 值、字元素的 AX 值或字节元素的 AL 值进行比较。重复操作的终止取决于 ZF 的结果状态以及 ECX 的值。

3.7 块结构语言的说明

本节中的指令为高级语言中常见的函数提供了机器语言支持。这些指令包括 ENTER 和 LEAVE,简化了程序
的编程。

ENTER (Enter Procedure)创建一个栈帧,可用于实现块结构高级语言的作用域规则。过程结束时的LEAVE 指令补充了过程开始时的 ENTER 指令,以简化堆栈管理并控制嵌套过程对变量的访问。

ENTER 指令包括两个参数。第一个参数指定要在正在进入的例程的栈。第二个参数对应于例程的词法嵌套级别(0-31)。(请注意,词法级别与保护权限级别或 I/O 权限级别都没有关系。)

指定的词法级别决定了 CPU 将多少组堆栈帧指针从前一帧复制到新的堆栈帧中。
这个堆栈帧指针列表有时被称为显示。显示的第一个字是指向最后一个堆栈帧的指针。这个指针通过有效
地丢弃最后一个堆栈帧,使 LEAVE 指令能够反转前一个 ENTER 指令的动作。

示例:输入 2048,3
在堆栈上分配 2048 字节的动态存储,并在 ENTER 为此过程创建的堆栈帧中设置指向前两个堆栈帧的指
针。
在 ENTER 为一个过程创建新的显示后,它通过将 ESP 递减第一个参数中指定的字节数来为该过程分配动
态存储空间。ESP 的这个新值用作该过程中所有推送和弹出操作的起点。

3.8 标志控制指令

标志控制指令提供了一种直接改变标志寄存器中位的状态的方法。

3.8.1 进位和方向标志控制指令

进位标志指令与以下指令结合使用非常有用——带进位指令旋转RCL和RCR。在执行将进位位移动到旋转操作数一端的旋转之前,它们可以将进位标志CF初始化为已知状态。

方向标志控制指令专门用于设置或清除方向标志 DF,它控制从左到右或串处理的从右向左方向。如果 DF=0,则每次执行一个字符串原语后,处理器自动递增字符串索引寄存器 ESI 和 EDI。如果 DF=1,处理器递减这些变址寄存器。程序员应该在任何使用字符串指令的过程之前
使用这些指令之一,以确保 DF 设置正确。

image_1grutvv0n1sl0poppbtb4110ap9.png-14.7kB

3.8.2 标志转移指令

尽管存在改变 CF 和 DF 的特定指令,但是没有改变其他面向应用的标志的直接方法。标志转移指令允许程序在将这些标志转移到堆栈或 AH 寄存器后,用位操作指令改变其它标志位。

指令 LAHF 和 SAHF 处理五个状态标志,主要由算术和逻辑指令使用。

image_1gruu807m10ko1rk41voi3e81ogsm.png-25.8kB

PUSHF 和 POPF 指令不仅可用于将标志存储在存储器中,以便对其进行检查和修改,还可用于在执行
过程时保留标志寄存器的状态。

PUSHF (Push Flags)将 ESP 递减 2,然后将 Flags 寄存器的低位字转移到 ESP指向的堆栈顶部的字(见图 3-23)。变量 PUSHFD 将 ESP 递减4,然后将扩展标志寄存器的两个字转移到ESP指向的堆栈顶部(但是,VM 和 RF 标志不会移动)。

POPF(弹出标志)将栈顶字的特定位转移到标志寄存器的低位字节(见图 3-23),然后将 ESP 递增 2。变体
POPFD 将特定位从堆栈顶部的双字传输到扩展标志寄存器(但 RF 和 VM 标志不变),然后将 ESP 递增 4。

3.10 段寄存器指令

这一类别实际上包括几种不同类型的指令。这些不同的类型在这里被组合在一起,因为如果系统设计者
选择一个不分段的存储器组织模型,这些指令没有一个被应用程序员使用。处理段寄存器的指令有:

  1. 段寄存器转移指令。

    • MOV SegReg, ...
    • MOV ..., SegReg
    • PUSH SegReg
    • POP SegReg
  2. 控制转移到另一个可执行段。

    • JMP far ; direct and indirect
    • CALL far
    • RET far
  3. 数据指针指令。

    • LDS
    • LES
    • LFS
    • LGS
    • LSS

请注意,以下中断相关指令是不同的;所有这些都能够将控制转移到另一个段,但是应用程序程序员并不清楚分段的用途。

3.10.1 段寄存器转移指令

MOV、弹出和推入指令也用于加载和存储段寄存器。除了一个操作数可以是段寄存器之外,这些变体的操作
与通用寄存器的操作类似。MOV 不能将段寄存器移动到段寄存器。POP 和MOV都不能将值放入代码段寄存器
CS;只有远控制转移指令可以改变 CS。

3.10.2 遥控转移指令

远控制转移指令通过改变 CS 寄存器的内容将控制转移到另一个段中的位置。

3.10.3 数据指针指令

数据指针指令将指针(由段选择器和偏移量组成)加载到段寄存器和通用寄存器。

3.11 杂项说明

下面的说明不属于前面的任何类别,但仍然很有用。

3.11.1 地址计算指令

LEA(加载有效地址)将源操作数的偏移量(而不是其值)传输到目标操作数。源操作数必须是内存操作数,
目标操作数必须是通用寄存器。此指令对于在执行字符串原语(ESI、EDI)或 XLAT 指令(EBX)之前初始化
寄存器特别有用。LEA 可以执行任何可能需要的索引或缩放。

例如:

  1. LEA EBX, EBCDIC_TABLE

使处理器将标签为 EBCDIC_TABLE 的表格的起始位置地址放入 EBX。

3.11.2 无操作指令

NOP(无操作)占用一个字节的存储空间,但除了指令指针 EIP 之外,它什么也不影响。

3.11.3 翻译指令

XLAT (Translate)用用户编码的转换表中的一个字节替换 AL 寄存器中的一个字节。当 XLAT 被执行
时,AL 应该拥有 EBX 寻址的表的无符号索引。XLAT 将 AL 的内容从表索引更改为表条目。EBX 没有改
变。XLAT 指令可用于从一种编码系统转换到另一种编码系统,例如从 ASCII 转换到 EBCDIC。转换表最
长可达 256 字节。放置在 AL 寄存器中的值用作对应转换值的位置的索引。

4 Systems Architecture

80386 的许多结构特征仅由系统程序员使用。本章概述了体系结构的这些方面。

80386 体系结构的系统级特性包括:

这些功能是通过寄存器和指令实现的,所有这些都将在下面的章节中介绍。这一章的目的不是详细解释每一个特性,而是为了正确理解第二部分的其余章节。本章中提到的每一个寄存器或指令都附有解释或参考下一章的详细信息。

4.1 系统寄存器

为系统程序员设计的寄存器分为以下几类:

4.1.1 系统标志

EFLAGS 寄存器的系统标志控制I/O、可屏蔽中断、调试、任务切换以及在受保护的多任务环境中启用虚拟8086执行。这些标志在图 4-1 中突出显示。

image_1grv18td918cf1sv0v901hmn18ps9.png-76.5kB

4.1.2 内存管理寄存器

80386 的四个寄存器定位控制段存储器管理的数据结构:

这些寄存器指向段描述符表GDT和LDT。有关通过描述符表寻址的说明,请参考第 5 章。

4.1.3 控制寄存器

image_1grv1mapa1qig1q2n9i61lfv1vk6m.png-41kB

图 4-2 显示了 80386 控制寄存器 CR0、CR2 和 CR3 的格式。系统程序员只能通过 MOV 指令的变体来访问这
些寄存器,这种变体允许它们从通用寄存器中加载或存储;例如:

  1. MOV EAXCR0
  2. MOV CR3EBX

当 PG 置位时,CR2用于处理页面错误。处理器将触发故障的线性地址存储在 CR2中。有关页面错误处理的描述,请参考第 9 章。

设置 PG 时使用CR3。CR3使处理器能够定位当前任务的页表目录。有关页表和页转换的描述,请参考第 5 章。

4.1.4 调试寄存器

调试寄存器为 80386带来了高级调试能力,包括数据断点和在不修改代码段的情况下设置指令断点的能力。有关格式和用法的完整描述,请参考第 12 章。

4.1.5 测试寄存器

测试寄存器不是80386结构的标准部分。提供它们只是为了启用转换后备缓冲区(TLB)的置信度测试,该缓冲区用于存储来自页表的信息。第 12 章解释了如何使用这些寄存器。

4.2 系统指令

系统指令处理如下功能:

  1. 指针参数的验证(参见第 6 章):

    • ARPL ──调整 RPL
    • LAR ──加载访问权限
    • LSL ──负载区段极限
    • VERR ──为阅读而验证
    • VERW ──书写验证
  2. 寻址描述符表(参见第五章):

    • LLDT ──装入 LDT 寄存器
    • SLDT ──存储 LDT 寄存器
    • LGDT ──加载 GDT 寄存器
    • SGDT ──存储 GDT 寄存器
  3. 多任务处理(参见第七章):

    • LTR ──加载任务寄存器
    • STR ——存储任务寄存器
  4. 协处理器和多处理器(参见第 11 章):

    • CLTS ──清除任务切换标志
    • ESC ──退出指令
    • WAIT ──等到协处理器不忙
    • LOCK ──断言总线锁定信号
  5. 输入和输出(参考第 8 章):

    • IN
    • OUT
    • INS ──输入串
    • OUTS ──输出串
  6. 中断控制(参见第 9 章):

    • CLI ──清除中断使能标志
    • STI ──设置中断使能标志
    • LIDT ──加载 IDT 寄存器
    • SIDT ──存储 IDT 寄存器
  7. 调试(参见第 12 章):

    • MOV ──移入和移出debug寄存器
  8. TLB 测试(参见第 10 章):

    • MOV ──移入和移出test寄存器
  9. 系统控制:

    • SMSW ──设置MSW
    • LMSW ──加载MSW
    • HLT ──暂停处理器
    • MOV ──移入和移出 control 寄存器

    SMSW 和 LMSW指令是为了与80286处理器兼容而提供的。80386 程序通过 MOV 指令的变体访问 CR0 中的MSW。HLT 停止处理器,直到收到INTR或复位信号。除了上面引用的章节,关于这些指令的详细信息可以在第17章的指令参考章节中找到。

第 5 章、内存管理

80386 分两步将逻辑地址(即程序员认为的地址)转换成物理地址(即物理内存中的实际地址):

  1. 段转换,其中逻辑地址(由段选择器和段偏移)被转换成线性地址。
  2. 页面转换,将线性地址转换成物理地址。这个步骤是可选的,由系统软件设计者决定。

这些翻译是以应用程序程序员看不到的方式执行的。图 5-1 在高抽象层次上说明了两种翻译。

image_1grv2g8np1shn1ah915i0115u9nq13.png-58.2kB

图 5-1 和本章的其余部分给出了80386寻址机制的简化视图。实际上,寻址机制还包括内存保护功能。然而,为了简单起见,保护的主题在另一章即第 6 章中讨论。

5.1 段翻译

图 5-2 更详细地显示了处理器如何把一个逻辑地址转换成一个线性地址。

image_1grv2l1p8meefn3c1v1iv1h0n1g.png-45.3kB

为了执行这种转换,处理器使用以下数据结构:

● 描述符
● 描述符表
● 选择器
● 段寄存器

5.1.1 描述符

段描述符为处理器提供将逻辑地址映射成线性地址所需的数据。描述符是由编译器、连接器、加载器或操作系统创建的,而不是由应用程序员创建的。图5-3说明了两种通用的描述符格式。

image_1grv2pgfj1r42ubm1v1isak1rrp1t.png-67.7kB

所有类型的段描述符都采用这些格式中的一种。段描述符字段包括:

描述符的创建和维护是系统软件的责任,通常需要编译器、程序装入器或系统构建器以及评级系统的配合。

5.1.2 描述符表

段描述符存储在两种描述符表中:

描述符表只是一个包含描述符的 8字节条目的内存数组,如图 5-5 所示。

image_1grv3i25j1amb1rhppv7lpqss22n.png-51.8kB

描述符表的长度是可变的,可以包含多达8192(2^13)个描述符。然而,处理器不使用 GDT 的第一个条目(索引=0)。

处理器通过 GDTR 和 LDTR 寄存器定位内存中的 GDT 和当前 LDT。这些寄存器存储线性地址空间中表的基地址,并存储段LIMIT。LGDT 和 SGDT 给出了进入 GDTR 的指令;LLDT 和 SLDT 给出了进入 LDTR 的指令。

5.1.3 选择器

逻辑地址的选择器部分指定了一个描述符,这个描述符是通过在选择子里指定使用的描述符表,以及描述符表的下标指定的。
对于应用程序来说,选择器可能是指针变量中的一个字段,但是选择器的值通常是由链接器或链接加载器分配(固定)的。图 5-6 显示了选择器的格式。

image_1grv3mqgv1kch1rhe9bb15s1qdo34.png-24.6kB

因为处理器不使用GDT的第一个条目,所以索引为零且表指示符为零的选择器(即,指向GDT的第一个条目的选择器)可以用作空选择器。当一个段寄存器(CS或SS除外)加载了一个空选择器时,处理器不会引起异常。但是,当使用段寄存器访问内存时,它会导致异常。此功能对于初始化未使用的段寄存器很有用,以便捕获意外引用。

5.1.4 段寄存器

80386 将来自描述符的信息存储在段寄存器中,从而避免了每次访问存储器时查阅描述符表的需要。每个段寄存器都有一个“可见”部分和一个“不可见”部分,如图 5-7 所示。

image_1grv44c8j1ro21du81qdgktje13h.png-37.6kB

这些段地址寄存器的可见部分由程序操纵,就像它们只是 16 位寄存器一样。不可见部分由处理器操纵。

加载这些寄存器的操作是正常的程序指令(之前在第3章中描述过)。这些指令分为两类:

  1. 直接加载指令;例如,MOV,POP,LDS,LSS,LGS,LFS。
    这些指令显式引用段寄存器。

  2. 隐含加载指令;例如,call和 JMP。这些指令隐式引用 CS 寄存器,并向其加载新值。

使用这些指令,程序用一个16位选择器加载段寄存器的可见部分。处理器自动从描述符表中取出基址、界限、类型和其它信息,并将它们加载到段寄存器的不可见部分。

因为大多数指令引用段中的数据,而这些段的选择器已经加载到段寄存器中,所以处理器可以将指令提供的段相对偏移量添加到段基址,而无需额外的开销。

5.2 页面翻译

在地址转换的第二阶段,80386将线性地址转换成物理地址。这个阶段的地址转换实现了面向页面的虚拟内存系统和页面级保护所需的基本特性。

页面翻译步骤是可选的。仅当CR0的PG位被置位时,页面翻译才有效。该位通常由操作系统在软件初始化期间设置。如果操作系统要实现多个虚拟8086任务、面向页面的保护或面向页面的虚拟存储器,则必须设置 PG 位。

5.2.1 页帧

页帧是物理内存连续地址的4K字节单位。页帧从字节边界开始,大小是固定的。

5.2.2 线性地址

线性地址通过指定页表、该表中的页以及该页中的偏移量来间接引用物理地址。图 5-8 显示了线性地址的格式。

image_1grv4ftbqjar16v710nngbk1uuv3u.png-21.3kB

图 5-9 显示了处理器如何通过查询两级页表将线性地址的 DIR、PAGE 和 OFFSET 字段转换成物理地址。

image_1grv4gs4413e71o441vh6746as04b.png-54.7kB

寻址机制使用 DIR字段作为页目录的索引,使用PAGE字段作为页表的索引,并使用OFFSET字段来寻址由页表确定的页内的一个字节。

5.2.3 页表

页表只是一个 32位页说明符的数组。页表本身就是一个页,因此包含 4kb 内存或最多 1K 个 32 位条目。

二级表用于寻址内存中的一页。更高层是页目录。页目录寻址高达 1K 的二级页表。二级页表寻址高达1K页。因此,由一个页面目录寻址的所有表可以寻址1M个页面(2^20)。因为每页包含 4K 字节 2^12字节),一个页目录的表可以跨越 80386 的整个物理地址空间(2^20 乘以 2^12 = 2^32)。

当前页目录的物理地址存储在CPU寄存器CR3中,也称为页目录基址寄存器(PDBR)。内存管理软件可以选择为所有任务使用一个页面目录,为每个任务使用一个页面目录,或者两者结合使用。

有关 CR3初始化的信息,参见第 10 章。参见第 7 章,了解 CR3 在每项任务中的变化。

5.2.4 页表条目

任一级页表中的条目具有相同的格式。图5-10说明了这种格式。

image_1grv4qsii1min18k8127fnb91vm94o.png-52.8kB

5.2.5 Page Translation Cache

为了实现最高的地址转换效率,处理器将最近使用的页表数据存储在片内缓存中。只有当必要的分页信息不在高速缓存中时,才必须引用两级页表。

页面转换高速缓存的存在对应用程序程序员是不可见的,但对系统程序员是可见的;每当页表改变时,操作系统程序员必须刷新缓存。这页面转换缓存可以通过以下两种方法之一刷新:

  1. 通过用 MOV 指令重载 CR3 例如:MOV CR3, EAX
  2. 通过执行任务切换到具有不同于当前 TSS 的 CR3 映像的 TSS。(有关任务切换的更多信息,请参考第 7 章。)

5.3 组合分段和页面翻译

图 5-12 结合图5-2和图5-9总结了从逻辑地址到物理地址转换的两个阶段
分页已启用。通过适当选择两个阶段的选项和参数,内存管理软件可以实现几种不同风格的内存管理。

image_1gs10fhp81i7ubkr17v519d51fag5i.png-47.4kB

5.3.1 “扁平”架构

当 80386 用于执行为没有分段的体系结构设计的软件时,有效地“关闭”80386的分段功能可能是有利的。80386没有禁用分段的模式,但通过最初用包含整个32位线性地址空间的描述符的选择器加载段寄存器,可以达到相同的效果。一旦加载,就不需要改变段寄存器。80386指令使用的32位偏移足以寻址整个线性地址空间。

5.3.2 Segments Spanning Several Pages

80386 的体系结构允许段大于或小于一页的大小(4千字节)。

例如,假设一个数据段用于寻址和保护一个跨越132KB的大型数据结构。在支持分页虚拟内存的软件系统中,没有必要将整个结构同时放在物理内存中。该结构被分成33页,任何数量的页都可能不存在。应用程序员不需要知道虚拟存储器子系统以这种方式对结构进行分页。

5.3.3 Pages Spanning Several Segments

另一方面,段可能小于页面的大小。例如,考虑一个小型数据结构,如信号量。由于段提供的保护和共享(参见第6章),为每个信号量创建一个单独的段可能是有用的。但是,因为一个系统可能需要许多信号量,为每个信号量分配一个页面是没有效率的。因此,在一个页面中聚集许多相关的片段可能是有用
的。

5.3.4 不对齐的页面和段边界

80386 的体系结构不强制页面和段的边界之间的任何对应关系。一个页面包含一个段的结束和另一个段的开始是完全允许的。同样,一个段可能包含一页的结束和另一页的开始。

5.3.5 对齐的页面和段边界

然而,如果内存管理软件在页面和段边界之间实施某种对应,它可能会更简单。例如,如果段仅以一页为单位分配,则段和页分配的逻辑可以合并。不需要逻辑来说明部分使用的页面。

5.3.6 Page-Table per Segment

一种进一步简化空间管理软件的空间管理方法是保持段描述符和页目录条目之间的一一对应,如图5-13所示。

image_1gs11q1r11jc81srqub52j2os25v.png-73.4kB

每个描述符都有一个基地址,该基地址的第22位是0,换句话说,基址由页表的第一个条目映射。一个段可以有从 1 到 4 兆字节的任何限制。
根据限制,该段包含在 1 到 1K 的页帧中。因此,一个任务被限制为 1K 个段(对于许多应用来说足够
了),每个段最多包含 4 兆字节。描述符、相应的页目录条目和相应的页表可以同时被分配和解除分
配。

6、 Protection

6.1 为什么要保护?

80386 保护功能的目的是帮助检测和识别故障。80386支持由数百或数千个程序模块组成的复杂应用程序。在这样的应用程序中,问题是如何尽可能快地发现并消除bug,以及如何严格限制它们的损害。为了帮助更快地调试应用程序并使它们在生产中更加健壮,80386 包含了验证memory访问和指令执行是否
符合保护标准的机制。根据系统设计目标,可以使用或忽略这些机制。

6.2 80386 保护机制概述

80386 中的保护有五个方面:
1. Type checking
2. Limit checking
3. Restriction of addressable domain
4. Restriction of procedure entry points
5. Restriction of instruction set

80386 的保护硬件是内存管理硬件的一个组成部分。保护同时适用于段转换和页面转换。

硬件会检查对内存的每个引用,以验证它是否满足保护标准。所有这些检查都是在存储周期开始之前进行的;任何违反都会阻止该循环开始,并导致异常。由于检查是与地址形成同时执行的,因此不会有性能损失。

无效的内存访问尝试会导致异常。有关异常机制的解释,请参考第 9 章。本章定义了导致异常的违反保护行为。

“privilege”的概念是保护的几个方面的核心(前面列表中的数字 3、4 和 5)。应用于过程时,特权是可以信任过程不会犯可能影响其他过程或数据的错误的程度。应用于数据时,特权是数据结构应该具有的免受不太可信的过程影响的保护程度。

privilege的概念适用于段保护和页面保护。

6.3 段级保护

保护的所有五个方面都适用于段转换:
1. Type checking
2. Limit checking
3. Restriction of addressable domain
4. Restriction of procedure entry points
5. Restriction of instruction set

段是保护单位,段描述符存储保护参数。当段描述符的选择器被加载到段寄存器中时,以及每次段访问时,保护检查由 CPU 自动执行。段寄存器保存当前可寻址段的保护参数。

6.3.1 描述符存储保护参数

图 6-1 突出显示了段描述符的保护相关字段。

image_1gs12rkvc1qm3139j97e68t1gku6p.png-99.5kB

创建描述符时,系统软件会将保护参数放在描述符中。一般来说,应用程序员不需要关心保护参数。

当程序将一个选择器装入段寄存器时,处理器不仅装入段的基址,还装入保护信息。每个段寄存器在不可见部分都有用于存储基数、限制、类型和特权级别的位;因此,对同一段的后续保护检查不会消耗额外的时钟周期。

6.3.1.1 type检查

描述符的type字段有两个功能:
1. 它区分不同的描述符格式。
2. 它指定了一个段的预期用途。

除了应用程序通常使用的数据和可执行段的描述符外,80386 还有操作系统和门使用的特殊段的描述符。表6-1列出了为系统段和gate定义的所有类型。

image_1gs12tl2q1ovu1op2qe2i710cq76.png-39.5kB

注意,不是所有的描述符都定义段;门描述符有不同的用途,将在本章后面讨论。

数据和可执行段描述符的Type字段包括进一步定义段用途的位(参见图 6-1):

Type检查可用于检测试图以程序员不希望的方式使用段的编程错误。处理器在两种情况下检查类型信息:

  1. 当描述符的选择器被加载到段寄存器中时。某些段寄存器只能包含某些描述符类型;例如:
    • CS 寄存器只能用可执行文件的选择器来加载段。
    • 不可读的可执行段的选择器不能被载入数据段寄存器。
    • 只有可写数据段的选择器才能加载到 SS 中。
  2. 当指令(隐式或显式)引用段寄存器时。指令只能以某些预定义的方式使用某些段;例如:
    • 任何指令都不能写入可执行段。
    • 如果可写位为0,则没有指令可以写入数据段。
    • 任何指令都不能读取可执行段,除非可读位已经准备好了。
6.3.1.2 LIMIT检查

处理器使用段描述符的limit字段来防止程序在段外寻址。处理器对LIMIT值的解释取决于G(粒度)位的设置。对于数据段,处理器对极限的解释还取决于e位(扩展方向位)和B位(大位)(参见表 6-2)。

image_1gs13bk644r51up81o4471ul397j.png-36.7kB

对于除向下展开数据段之外的所有类型的段,limit的值比段的size值小1。在以下任何情况下,处理器都会导致一般保护异常:

对于向下扩展的数据段,LIMIT具有相同的功能,但有不同的解释。在这些情况下,有效地址的范围是从limit + 1 到 64K 或 2^32-1(4gb ),具体取决于B位。当limit为零时,向下扩展的段具有最大大小。

向下扩展功能可以通过将栈复制到更大的段来扩展栈的大小,而无需更新栈内指针。

处理器使用描述符表的描述符LIMIT字段来防止程序选择描述符表之外的表条目。描述符表的limit标识了表中最后一个描述符的最后一个有效字节。因为每个描述符的长度是 8 个字节,所以对于一个最多可以包含N个描述符的表,限制值是 N * 8 - 1。

limit检查捕捉编程错误,如下标失控和无效指针计算。这种错误在发生时被检测到,从而更容易识别原因。如果没有限制检查,这种错误可能会破坏其他模块;这种错误的存在将不会被发现,直到后来被破坏的模块不正确地运行,并且难以识别原因。

6.3.1.3 特权级别

Privilege的概念是通过给处理器识别的关键对象分配一个从 0 到 3 的值来实现的。这个值称为特权级别。值0代表最高权限,值 3 代表最低权限。下面的处理器识别的对象包含特权级别:

处理器通过将 CPL与一个或多个其他特权级别进行比较,自动评估程序访问另一个段的权限。在描述符的选择器被加载到段寄存器时,执行评估。用于评估数据访问的标准不同于用于评估将控制转移到可执行段的标准;因此,以下各节将分别考虑这两种类型的访问。

图 6-2 显示了如何将这些特权级别解释为保护环。

image_1gs144l971i7akvn1lb7ee918k80.png-51.3kB

中心是包含最关键软件的部分,通常是操作系统的内核。外环用于不太重要的软件部分。

没有必要使用所有四个权限级别。被设计成只使用一个或两个特权级别的现有软件可以简单地忽略80386提供的其他级别。一级系统应该使用零级特权;两级系统应该使用0级和3级权限。

6.3.2 Restricting Access to Data

为了在内存中寻址操作数,80386程序必须将数据段的选择器装入数据段寄存器(ds,ES,FS,GS,ss)。处理器通过比较特权级别来自动评估对数据段的访问。在将目标段描述符的选
择器加载到数据段寄存器时,执行评估。

image_1gs1ep6qbmrgfn2c5i1ae81tu28d.png-70.3kB

如图 6-3 所示,三种不同的权限级别进入这种类型的权限检查:

  1. CPL(当前特权级别)。
  2. 用于指定目标段的选择器的 RPL(请求者的权限级别)。
  3. 目标段描述符的 DPL。
  1. CPL <= DPL
  2. RPL <= DPL

只有当目标段的 DPL 在数值上大于或等于 CPL 和选择器的 RPL 的最大值时,指令才可以加载数据段寄存器(并随后使用目标段)。换句话说,一个过程只能访问相同或更低特权级别的数据。

任务的可寻址域随着CPL的变化而变化。当CPL为零时,所有权限级别的数据段都是可访问的;当 CPL为1时,只有特权级别1 到 3 的数据段是可访问的;当 CPL 为 3时,只有特权级别为 3 的数据段是可访问的。例如,80386的这一特性可以用来防止应用程序读取或改变操作系统的表。

6.3.2.1 访问代码段中的数据

使用代码段存储数据比使用数据段更不常见。代码段可以合法地保存常数;不可能写入被描述为代码段的段。下列访问代码段中数据的方法是可行的:

  1. 将nonconforming、可读的、可执行的段的选择器填入数据段寄存器。
  2. 将一个conforming、可读的、可执行的段的选择器填入一个数据段寄存器。
  3. 使用 CS 覆盖前缀读取一个可读的可执行段,该段的选择器已经加载到 CS 寄存器中。

这3种特权级检查:

6.3.3 限制控制权转移

在 80386 中,控制转移是由指令JMP、call、RET、INT和IRET 以及异常和中断机制来完成的。异常和中断是第9章讨论的特殊情况。本章只讨论 JMP、CALL 和 RET 指令。

JMP、CALL 和 RET的“near”形式在当前代码段内传输,因此只接受LIMIT检查。处理器确保JMP、调用或RET指令的目的地不超过当前可执行段的LIMIT。该LIMIT值缓存在CS寄存器中;因此,对near转移的保护检查不需要额外的时钟周期。

JMP 和 CALL 的“far”形式的操作数引用其他段;因此,处理器执行特权检查。JMP 或call引用另一个段有两种方式:

  1. 操作数选择另一个可执行段的描述符。
  2. 操作数选择一个调用门描述符。这种门控形式的传输将在后面的调用门部分讨论。

image_1gs1git4f1vhtq2v664m7f15s28q.png-58.8kB

image_1gs1l0nb51ok41puocsf109r1ds897.png-39.3kB

如图 6-4 所示,对于不使用调用门的控制转移,4种不同的权限级别进入权限检查:

  1. CPL(当前特权级别)。
  2. 目标段描述符的 DPL。
  3. RPL
  4. C

    • 对于非一致性代码段:

    CPL == DPL && RPL <= CPL

    控制转移前后,CPL的值保持不变,即使RPL≠CPL。

    • 对于一致性代码段:

    CPL <= DPL,与RPL无关。

    控制转移前后,CPL的值保持不变,即使RPL≠CPL。

通常,CPL 等于处理器当前正在执行的程序段的DPL。然而,如果在当前可执行段的描述符中设置了符合C位,则CPL可能大于 DPL。处理器保留CS寄存器中缓存的CPL的记录;该值可以不同于代码段描述符中的 DPL。

仅当满足以下特权规则之一时,处理器才允许JMP或直接call另一个段:

An executable segment whose descriptor has the conforming bit set is called a conforming segment. conforming机制允许共享可以从各种特权级别调用的过程,但是应该在调用过程的特权级别执行。这种过程的例子包括数学库和一些异常处理程序。当控制权转移到一致的分部时,CPL 不变。这是 CPL可能不等于当前可执行段的DPL的唯一情况。

大多数代码段都是nonconforming。上述特权的基本规则意味着,对于nonconforming的段,控制可以在没有门的情况下只
转移到具有相同特权级别的可执行段。然而,需要将控制转移给(数字上)较小特权级别;当与调用门描述符一起使用时,CALL 指令满足了这一需求,这将在下一节中解释。JMP指令可能
永远不会将控制转移到 DPL 不等于 CPL的nonconforming段。

6.3.4 Gate Descriptors Guard Procedure Entry Points

为代码段之间的控制转移提供保护在不同的权限级别,80386 使用门描述符。有四种门描述符:

本章只涉及call门。task门用于任务切换,因此将在第7章中讨论。第 9 章解释了异常和中断是如何使用陷阱门和中断门的。图 6-5 说明了呼叫门的格式。

image_1gs1mk83l6skvl5143s80vroh9k.png-35.5kB

call门描述符可能位于GDT 或 LDT,但不在IDT。

call门有两个主要功能:
1. 定义过程的入口点。
2. 指定入口点的特权级别。

call和jmp指令使用call gate描述符的方式与代码段描述符相同。当硬件识别出目的地选择器引用了门描述符时,指令的操作将根据call gate的内容进行扩展。

门的选择器和偏移量字段形成了指向过程入口点的指针。call gate保证到另一个段的所有转换都进入一个有效的入口点,而不是可能进入一个过程的中间(或者更糟,进入一条指令的中间)。控制转移指令的远指针操作数不指向目标指令的段和偏移量;相反,指针的选择器部分选择一个门,并且不使用偏移量。

图 6-6 说明了这种寻址方式。

image_1gs1mo3bs96s4mj1kme1rdg1ggca1.png-50.4kB

image_1gs1mrgk27ou1d7ienp17m5radae.png-52.4kB

如图 6-7 所示,使用四种不同的特权级别来检查通过调用门的控制转移的有效性:
1. CPL(当前特权级别)。
2. 用于指定调用门的选择器的 RPL(请求者的权限级别)。
3. 门描述符的 DPL。
4. 目标可执行段的描述符的 DPL。

门描述符的 DPL字段决定了什么特权级别可以使用门。一个代码段可以有几个供不同权限级别使用的过程。例如,一个操作系统可能有一些服务是供应用程序使用的,而另一些服务可能只供其他系统软件使用。

门可用于将控制转移到数字上较小的特权级别或相同的特权级别(尽管它们对于转移到相同的级别不是必需的)。只有call指令可以使用门转移到更小的特权级别。JMP指令只能使用门来传送到具有相同特权级别的可执行段或conforming的段。

对于nonconforming的JMP指令,必须满足以下两个特权规则:否则,会导致一般保护异常。

  1. MAX (CPL,RPL) gate DPL
  2. target segment DPL = CPL

对于一个call指令(或者对于一个JMP指令到一个conforming段),必须满足以下两个特权规则(否则,会导致一般保护异常):。

  1. MAX (CPL,RPL) gate DPL
  2. target segment DPL CPL
6.3.4.1 栈切换

如果call gate的目的代码段与CPL处于不同的特权级别,则请求级间转移。

为了保持系统的完整性,每个特权级别都有一个单独的栈。这些栈确保有足够的栈空间来处理来自较低特权级别的调用。没有它们,如果调用过程没有在调用方的栈上提供足够的空间,可信过程将无法正常工作。

处理器通过任务状态段(task state segment)定位这些栈(见图 6-8)。

image_1gs23mdej14hn4hlu7vo21u159.png-62kB

每个任务有一个单独的TSS,因此允许任务有单独的栈。系统软件负责创建 TSS并在其中放置正确的栈指针。TSS中的初始栈指针是严格的只读值。处理器在执行过程中从不改变它们。

当调用门用于改变特权级别时,通过从任务状态段(TSS)加载指针值来选择新的堆栈。这处理器使用目标代码段(新的 CPL)的 DPL 来索引 PL 0、PL 1 或 PL 2 的初始栈指针。

新栈数据段的 DPL必须等于新的CPL如果没有,就会发生栈异常。系统软件负责为所有使用的特权级别创建栈和栈段描述符。每个栈必须包含足够的空间来保存旧的SS:ESP、返回地址以及处理调用可能需要的所有参数和局部变量。

与层内调用一样,子例程的参数放在栈上。为了使特权转换对被调用的过程透明,处理器将参数复制到新的栈中。调用门的计数字段告诉处理器要从调用者的栈复制多少个双字(最多 31 个)到新的栈。如果计数为零,则不复制任何参数。

处理器在执行级间调用时执行以下与栈相关的步骤。

  1. 检查新栈以确保它足够大以容纳参数和链接;如果不是,就会发生堆栈错误,错误代码为0。
  2. 栈寄存器 SS:ESP 的旧值作为两个双字压入新栈。
  3. 参数被复制。
  4. 将指向 CALL指令之后的指令的指针(CS:EIP的旧值)压到新栈上。SS:ESP 的最终值指向新栈上的这个返回指针。

image_1gs23vh69275srg10nvqe118b5m.png-51.3kB

图 6-9 显示了一次成功的层间调用后的堆栈内容。TSS没有特权级 3 栈的栈指针,因为特权级3不能被任何其它特权级的任何过程调用。

可能从另一个权限级别调用并且需要31个以上双字作为参数的过程必须使用保存的SS:ESP链接来访问最后复制的双字以外的所有参数。

通过调用门的调用不检查复制到新栈上的字的值。被调用的过程应该检查每个参数的有效性。后面的部分将讨论如何使用 ARPL、VERR、VERW、LSL 和 LAR 指令来检查指针值。

6.3.4.2 从程序中返回

RET 指令的“近”形式在当前代码段内转移控制,因此只接受LIMIT检查。从栈中弹出相应call后的指令的偏移量。处理器确保该偏移量不超过当前可执行段的限制。

RET 指令的“远”形式弹出由前一个远调用指令推入堆栈的返回指针。在正常情况下,返回指针是有效的,因为它与前面的调用或 INT 有关。然而,处理器执行特权检查,因为当前过程可能改变了指针或未能正确维护栈。CS 选择器的 RPL 弹出
通过返回指令离开堆栈标识调用过程的特权级别。

段间返回指令可以改变特权级别,但只能针对较低特权的过程。当 RET 指令遇到RPL数值大于CPL的保存的CS值时,会发生级间返回。

image_1gs3slmee6e3r2492e2qd1soi9.png-99.7kB

这种返回遵循以下步骤:

  1. 进行了表 6-3所示的检查,CS:EIP和SS:ESP被加载了它们之前保存在栈中的值。

  2. 旧的 SS:ESP(从当前栈的顶部)值根据RET指令中指示的字节数进行调整。得到的ESP值不会与堆栈段的限制进行比较。如果 ESP 超出了限制,则直到下一次栈操作才会识别出这一事实。(不保留返回过程的 SS:ESP 值;通常,该值与 TSS 中包含的值相同。)

  3. 检查 DS、ES、FS和GS段寄存器的内容。如果这些寄存器中的任何一个引用了DPL大于新CPL的段(不包括符合的代码段),则段寄存器加载空选择器(INDEX=0,TI=0)。在这些情况下,RET指令本身不会发出异常信号;但是,任何试图使用包含空选择器的段寄存器的后续内存引用都将导致一般的保护异常。这可以防止特权较低的代码使用特权较高的过程留在段寄存器中的选择器来访问特权较高的段。

6.3.5 有些指令是为操作系统保留的

能够影响保护机制或影响一般系统性能的指令只能由可信程序执行。80386 有两类这样的指令:

  1. 特权指令──用于系统控制的指令。
  2. 敏感指令──用于输入输出和与输入输出相关的指令活动。
6.3.5.1 特权指令

影响系统数据结构的指令只能在CPL为零时执行。当CPL大于零时,如果 CPU 遇到这些指令之一,它会发出一个一般保护异常信号。这些说明包括:

6.3.5.2 敏感指令

处理 I/O 的指令需要受到限制,但也需要由以非零特权级别执行的过程来执行。限制I/O操作的机制在第8章“输入/输出”中有详细介绍。

6.3.6 Instructions for Pointer Validation

指针验证是定位编程错误的一个重要部分。指针验证对于维护权限级别之间的隔离是必要的。指针验证由以下步骤组成:

  1. 检查指针的提供者是否有权访问该段。
  2. 检查段类型是否适合其预期用途。
  3. 检查指针是否违反了段限制。

尽管 80386 处理器在指令执行期间自动执行第2和第3项检查,但软件必须协助执行第一项检查。非特权指令ARPL就是为此目的而提供的。软件也可以显式地执行步骤2和3来检查潜在的违规(而不是等待异常)。非特权指令 LAR、LSL、VERR 和 VERW 就是为此目的而提供的。

image_1gs3t6pkibueiu61l99rn107nm.png-38.9kB

6.3.6.1 Descriptor Validation

80386 有两个指令,VERR和VERW,它们决定一个选择器是否指向一个在当前特权级别可以读写的段。如果结果是否定的,则两个指令都不会导致保护故障。

6.3.6.2 指针完整性和 RPL

请求者的特权级别(RPL)功能可以防止指针的不当使用,这种不当使用会破坏特权级别较低的代码或数据的操作。

一个常见的例子是文件系统过程FREAD(file_id,n_bytes,buffer_ptr)。这个假设的过程将数据从一个文件读入一个缓冲区,覆盖那里的所有内容。通常,FREAD在用户级别可用,只提供指向文件系统过程和数据的指针,这些文件系统过程和数据在特权级别上定位和操作。通常,这种过程会阻止用户级过程直接更改文件表。然而,在缺乏检查指针有效性的标准协议的情况下,用户级过程可能会提供一个指向文件表的指针来代替其缓冲区指针,从而导致 FREAD 过程在无意中损坏它们。

使用 RPL 可以避免这样的问题。RPL字段允许将权限属性分配给选择器。这个特权属性通常表示生成选择器的代码的特权级别。80386 处理器自动检查装入段寄存器的任何选择器的RPL,以确定 RPL 是否允许访问。为了利用处理器对 RPL 的检查,被调用的过程只需要确保传递给它的所有选择器的 RPL 至少与原调用者的CPL一样高(数值上)。这个动作保证选择器不会比它们的提供者更可信。如果其中一个选择器用于访
问调用者不能直接访问的段,即RPL在数值上大于DPL,那么当该选择器被加载到段寄存器中时,将导致保护故障。

ARPL(调整请求者的特权级别)将选择器的RPL字段调整为其原始值和指定寄存器中RPL字段值中的较大值。后者通常从堆栈上调用者的 CS 寄存器的映像中加载。如果调整改变了选择器的 RPL,ZF(零标志)被设置;否则,ZF 是清白的。

6.4 页面级保护

两种保护与页面相关:
1. 可寻址域的限制。
2. 类型检查。

6.4.1 Page-Table Entries Hold Protection Parameters

图 6-10 突出显示了控制页面访问的 PDE 和 pte 字段。

image_1gs40k3d81amg168o11qe18d11p6k13.png-52.4kB

6.4.1.1 限制可寻址域

页面特权的概念是通过将每个页面分配到两个级别之一来实现的:
1. 管理员级别(U/S=0)──用于操作系统和其他系统软件和相关数据。
2. 用户级(U/S=1) ──用于应用程序和数据。

当前级别(U 或 S)与CPL相关。如果CPL为0、1或2,则处理器在管理程序级别执行。如果CPL为3,则处理器在用户级执行。

当处理器在管理员级别执行时,所有页面都是可寻址的,但是,当处理器在用户级别执行时,只有属于用户级别的页面是可寻址的。

6.4.1.2 类型检查

在页面寻址级别,定义了两种类型:

  1. 只读访问(R/W=0)
  2. 读/写访问(R/W=1)

当处理器在管理员级别执行时,所有页面都是可读和可写的。

当处理器在用户级执行时,只有属于用户级并被标记为读/写访问的页面是可写的;从用户级别看,属于主管级别的页面既不可读也不可写。

6.4.2 组合两级页表的保护

对于任何一个页面,其页目录项的保护属性可能不同于其页表项的保护属性。80386通过检查页目录和页表中的保护属性来计算页面的有效保护属性。表6-5显示了保护属性的可能组合所提供的有效保护。

image_1gs40s0uh1fot15ll1em76r61n9m1g.png-65.2kB

6.4.3 Overrides页面保护

即使 CPL = 3,也会检查某些访问,就像它们是权限级别为 0 的引用一样:

● LDT,GDT,TSS,IDT 参考文献。
● 在跨环调用/INT 期间访问内部堆栈。

6.5 结合页面和段保护

启用分页时,80386首先评估段保护,然后评估页保护。如果处理器在段级或页级检测到保护违反,则所请求的操作不能继续进行;而是发生保护异常。例如,可以定义一个大的数据段,它包含一些只读的子单元和一些读写的子单元。在这种情况下,只读子单元的页面目录(或页面表)条目将具有设置为 x0 的 U/S 和 R/W 位,指示对该目录条目描述的所有页面(或对单个页面)没有写权限。

例如,这种技术可以在类似UNIX的系统中用来定义一个大的数据段,其中一部分是只读的(对于共享数据或 ROMmed 常量)。这使得类UNIX系统能够将“平面”数据空间定义为一个大段,使用“平面”指针在这个“平面”空间内寻址,同时还能够保护共享数据、映射到虚拟空间的共享文件和管理区域。

7、多任务处理

为了提供高效、受保护的多任务处理,80386采用了几种特殊的数据结构。然而,它不使用特殊的指令来控制多任务处理;相反,当普通的控制转移指令引用特殊的数据结构时,它会以不同的方式解释它们。支持多任务的寄存器和数据结构有:

有了这些结构,80386可以快速地从一个任务切换到另一个任务,保存原始任务的上下文,以便以后可以重新启动该任务。除了简单的任务切换,80386还提供了另外两个任务管理功能:

  1. 中断和异常会导致任务切换(如果系统设计需要的话)。处理器不仅自动切换到处理中断或异常的任务,而且在处理完中断或异常后自动切换回被中断的任务。中断任务可以中断较低优先级的中断任务到任何深度。

  2. 每次切换到另一个任务时,80386 也可以切换到另一个 LDT 和另一个页面目录。因此,每个任务可以有一个不同的逻辑到线性映射和不同的线性到物理映射。这是另一个保护特性,因为任务可以被隔离,防止相互干扰。

7.1 任务状态段

处理器管理任务所需的所有信息都存储在一个特殊类型的段中,即任务状态段(TSS)。图 7-1 显示了执行 80386 任务的 TSS 格式。(另一种格式用于执行 80286 任务;参考第 13 章。)

image_1gs43hto11m731ab0gnaccj14cj1t.png-58kB

TSS 的字段属于两类:

  1. 处理器在每次从任务切换时更新的一个动态集合。该集合包括存储以下内容的字段:

    • 通用寄存器(EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI)。
    • 段寄存器(ES、CS、SS、DS、FS、GS)。
    • 标志寄存器(eflags)。
    • 指令指针(EIP)。
    • 先前执行的任务的TSS的selector(更新仅当预期有回报时)。
  2. 处理器读取但不改变的静态集合。该集合包括存储以下内容的字段:

    • 任务 LDT 的选择器。
    • 包含任务的基址的寄存器(PDBR)页面目录(启用分页时只读)。
    • 指向特权级别 0-2 的栈的指针。
    • T 位(调试陷阱位),当任务切换发生时,它使处理器引发调试异常。(有关调试的更多信息,请参考第 12 章。)
    • I/O 映射map(有关的更多信息,请参考第 8 章)I/O 映射的使用)。

任务状态段可以驻留在线性空间中的任何地方。唯一需要注意的情况是当 TSS跨越一个页面边界并且更高地址的页面不存在。在这种情况下,如果处理器在任务切换期间读取TSS时遇到不存在的页面,则会引发异常。这种异常可以通过两种策略中的任何一种来避免:
1. 通过分配 TSS 使其不跨越页面边界。
2. 通过确保在任务切换时两个页面或者都存在或者都不存在。如果两页都是不存在,那么在重新启动导致任务切换的指令之前,缺页处理程序必须使两个页面都存在。

7.2 TSS 描述符

像所有其他段一样,任务状态段是由描述符定义的。图 7-2 显示了 TSS 描述符的格式。

image_1gs43u41d19mc1ksn1a9i1o0vf2c2q.png-22kB

TYPE字段中的 B位表示任务是否繁忙。TYPE值为9时表示非繁忙任务;TYPE值为11表示任务繁忙。任务不可重入。B位允许处理器检测切换到已经繁忙的任务的尝试。

BASE、LIMIT和 DPL字段以及G位和P位的功能类似于它们在数据段描述符中的对应部分。但是,LIMIT字段的值必须等于或大于 103。试图切换到TSS描述符的限制小于103的任务会导致异常。允许更大的限制,如果存在I/O权限映射,则需要更大的限制。如果附加数据存储在与 TSS相同的段中,则更大的限
制对于系统软件也可能是方便的。

可以访问 TSS 描述符的过程可以导致任务切换。在大多数系统中,TSS 描述符的DPL字段应该被设置为零,以便只有可信软件有权执行任务切换。

对 TSS 描述符的访问并不赋予过程读取或修改TSS的权利。读取和修改只能通过将TSS重新定义为数据段的另一个描述符来完成。试图将 TSS描述符加载到任何段寄存器(CS、SS、DS、ES、FS、GS)都会导致异常。

TSS 描述符只能驻留在GDT中。试图用TI=1(表示当前LDT)的选择器识别 TSS 会导致异常。

7.3 任务寄存器

任务寄存器(TR)通过指向 TSS来识别当前正在执行的任务。图 7-3 显示了处理器访问当前 TSS 的路径。

image_1gs449g3k1siglj71kqp113h25j37.png-44.9kB

TR具有“可见”部分(即,可以被指令读取和改变)和“不可见”部分(由处理器维护以对应于可见部分;不能被任何指令读取)。

可见部分的选择器选择GDT中的TSS描述符。处理器使用不可见部分来缓存来自TSS描述符的基值和极限值。将基数和极限保存在寄存器中使得任务的执行更有效,因为当处理器引用当前任务的 TSS 时,它不需要重复地从存储器中取出这些值。

指令 LTR 和 STR用于修改和读取任务寄存器的可见部分。两条指令都接受一个操作数,一个位于内存或通用寄存器中的 16 位选择器。

7.4 任务门描述符

任务门描述符提供了对 TSS 的间接的、受保护的引用。图7-4 说明了任务门的格式。

image_1gs44h36ao246j5lig1vd319e73k.png-79.7kB

任务门的选择器字段必须引用TSS描述符。处理器不使用该选择器中的 RPL 值。

任务门的 DPL 字段控制使用描述符来引起任务切换的权利。除非选择器的 RPL和过程的CPL的最大值在数值上小于或等于描述符的 DPL,否则过程不能选择任务门描述符。此约束防止不受信任的过程导致任务切换。(注意,当使用任务门时,目标 TSS 描述符的 DPL 不用于特权检查。)

可以访问任务门的过程有能力引起任务切换,就像可以访问 TSS 描述符的过程一样。除了 TSS 描述符之外,80386 还具有任务门,以满足三种需求:

  1. 一个任务需要有一个忙碌位(B)。因为 busy 位存储在 TSS 描述符中,所以每个任务应该只有一个这样的描述符。然而,可能有几个选择单个 TSS 描述符的任务门。

  2. 需要提供对任务的选择性访问。任务门满足了这个需求,因为它们可以驻留在 ldt 中,并且可以有一个不同于 TSS 描述符的 DPL 的DPL。如果在GDT(通常DPL为0)中没有足够特权使用 TSS描述符的过程可以访问其LDT中该任务的任务门,则该过程仍然可以切换到另一个任务。通过任务门,系统软件可以限制将任务切换到特定任务的权利。

  3. 需要中断或异常来引起任务切换。任务门也可以驻留在 IDT 中,使得中断和异常能够引起任务切换。当中断或异常指向包含任务门的IDT入口时,80386就切换到指定的任务。因此,系统中的所有任务都可以受益于与中断任务隔离所提供的保护。图 7-5 说明了LDT中的任务关口和IDT中的任务关口如何识别同一任务。

image_1gs44hpr485m1lkn11v18tn120341.png-49.3kB

7.5 任务切换

在以下四种情况下,80386 会将执行切换到另一个任务:
1. 当前任务执行引用 TSS 描述符的 JMP 或CALL。
2. 当前任务执行引用任务门的 JMP 或CALL。
3. 一个中断或异常被引导到 IDT 中的一个任务门。
4. 当 nt 标志被置位时,当前任务执行 IRET。

JMP、CALL、IRET、中断和异常都是80386的普通机制,可以用在不需要任务切换的环境中。所引用的描述符类型或标志字中的 NT(嵌套任务)位区分了标准机制和导致任务切换的变体。

为了引起任务切换,JMP或call指令可以指向TSS描述符或任务门。这两种情况的效果是一样的:80386切换到指示的任务。

当异常或中断指向IDT中的任务门时,会导致任务切换。如果它指向 IDT 中的中断或陷阱门,则不会发生任务切换。有关中断机制的更多信息,请参考第 9 章。

无论是作为任务调用还是作为被中断任务的过程调用,中断处理程序总是将控制返回给被中断任务中的被中断过程。但是,如果设置了 NT 标志,则处理程序是一个中断任务,IRET切换回被中断的任务。

任务切换操作包括以下步骤:

  1. 检查当前任务是否允许切换到指定任务。数据访问权限规则适用于 JMP 或call指令。TSS描述符或任务门的DPL必须小于或等于CPL的最大值和门选择器的RPL。不管目标任务门或 TSS 描述符的 DPL如何,都允许异常、中断和IRETs切换任务。

  2. 检查新任务的TSS描述符是否被标记为存在并具有有效的LIMIT。到目前为止,任何错误都发生在传出任务的上下文中。错误是可重启的,并且可以以对应用程序透明的方式来处理。

  3. 保存当前任务的状态。处理器找到缓存在任务寄存器中的当前 TSS 的基址。它将寄存器复制到当前的TSS中(EAX、ECX、EDX、EBX、esP、EBP、ESI、EDI、ES、CS、SS、DS、FS、GS 和 flag寄存器)。TSS的EIP字段指向导致任务切换的指令之后的指令。

  4. 用进入任务的TSS描述符的选择器加载任务寄存器,将进入任务的 TSS 描述符标记为忙,并设置MSW的TS(任务切换)位。选择器或者是控制转移指令的操作数,或者取自任务门。

  5. 从任务的 TSS中装入进入任务的状态并继续执行。加载的寄存器是 LDT 寄存器;标志寄存器;通用寄存器EIP、EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI段寄存器ES、CS、SS、DS、FS 和GS;还有PDBR。在此步骤中检测到的任何错误都发生在传入任务的上下文中。对于异常处理程序来说,似乎新任务的第一条指令还没有执行。注意,当任务切换发生时,总是保存去话任务的状态。如果该任务的执行被恢复,则它在导致任务切换的指令之后开始。当任务停止执行时,寄存器被恢复到它们所保持的值。

每个任务切换设置MSW(机器状态字)中的ts(任务切换)位。当协处理器(例如数字协处理器)存在时,TS标志对系统软件是有用的。TS 位表示协处理器的上下文可能不对应于当前的80386 任务。第 11 章更详细地讨论了 TS 位和协处理器。

异常处理程序在引入的任务中处理任务切换异常(由于表 7-1 中的测试 4 到 16导致的异常)时,应该小心采取任何可能加载导致异常的选择器的操作。这样的操作可能会导致另一个异常,除非异常处理程序

首先检查选择器并修复任何潜在的问题。在传入任务中恢复执行的特权级别既不受传出任务正在执行的特权级别的限制,也不受其影响。因为任务由它们单独的地址空间和TSS隔离,并且因为特权规则可以用来防止对 TSS的不适当访问,所以不需
要特权规则来约束任务的 CPL 之间的关系。新任务开始在从 TSS 加载的 CS 选择器值的 RPL 所指示的特权级别上执行。

image_1gs453nrsuig12rl2vc1je514ir4e.png-101.1kB

7.6 任务链接

TSS 的反向链接字段和标志字的 NT(嵌套任务)位一起允许 80386 自动返回到调用
另一个任务或被另一个任务中断。当一个调用指令、一个中断指令、一个外部中断或一个异常引起切换
到一个新任务时,80386 自动用输出任务 TSS 的选择器填充新 TSS 的反向链路,同时,
在新任务的标志寄存器中设置 NT 位。NT 标志指示反向链接字段是否有效。新任务通过执行 IRET 指令来
释放控制。当解释 IRET 时,80386 检查 NT 标志。如果设置了 NT,80386 将切换回 back-link 字段选择的
任务。表 7-2 总结了这些字段的用途。

image_1gs48u4n91g44d2jol0tcj1kib4r.png-64.9kB

7.6.1 Busy Bit Prevents Loops

TSS 描述符的 B 位(繁忙位)确保反向链路的完整性。当中断任务中断其他中断任务时,或者当被调用
任务调用其他任务时,反向链接链可以增长到任意长度。busy 位确保 CPU 可以检测到任何创建循环的
企图。循环将指示试图重新进入已经忙碌的任务;然而,TSS 不是一个可重入的资源。
处理器使用 busy 位的方式如下:
1. 当切换到一个任务时,处理器自动设置新任务的 busy 位。
2. 从一个任务切换时,如果该任务不放在上,处理器自动清除旧任务的 busy 位
反向链接链(即,导致任务切换的指令是 JMP 或 IRET)。如果任务被放置在反向链路链上,其
busy 位保持置位。
3. 当切换到一个任务时,如果新任务的 busy 位已经置位,处理器会发出一个异常信号。
通过这些动作,处理器防止一个任务切换到它自己或一个反向链接链上的任何任务,从而防止无效地重
新进入一个任务。
busy 位即使在多处理器配置中也是有效的,因为处理器在设置或清除 busy 位时会自动断言总线锁
定。这个动作确保两个处理器不会同时调用同一个任务。(有关多处理的更多信息,请参考第 11 章。)

7.6.2 修改任务链接

对任务的链接顺序的任何修改应该仅由可以被信任来正确更新反向链接和忙碌位的软件来完成。在中断
任务之前,可能需要这样的改变来恢复被中断的任务。从反向链接链中删除任务的可信软件必须遵循以
下策略之一:
1. 首先更改中断任务的 TSS 中的 back-link 字段,然后清除从列表中删除的任务的 TSS 描述符中的
busy 位。
2. 确保在更新反向链路链和 busy 位之间没有中断发生。
7.7 任务地址空间
TSS 的 LDT 选择器和 PDBR 字段使软件系统设计者在利用 80386 的段和页面映射功能时具有灵活性。通过
为每个任务适当选择段和页映射,任务可以共享地址空间,可以具有彼此大不相同的地址空间,或者可
以在这两个极端之间具有任何程度的共享。
任务拥有不同地址空间的能力是 80386 保护的一个重要方面。一个任务中的模块不能干扰
如果模块不能访问相同的地址空间,则在另一个任务中调用模块。80386 灵活的内存管理特性允许系统设
计者将共享地址空间的区域分配给不同任务的模块,这些模块被设计成相互协作。

7.7.1 任务线性到物理空间映射
安排任务的线性到物理映射的选择分为两大类:
1. 所有任务共享一个线性到物理的映射。
当分页未启用时,这是唯一的可能性。如果没有页表,所有线性地址都会映射到相同的物理地
址。
当启用分页时,这种线性到物理的映射方式是对所有任务使用一个页面目录的结果。如果操作
系统还实现了页面级虚拟内存,那么所利用的线性空间可能会超过可用的物理空间。
2. 几个部分重叠的线性到物理映射。
这种风格通过为每个任务使用不同的页面目录来实现。因为 PDBR(页目录基址寄存器)是在每次
任务切换时从 TSS 加载的,所以每个任务可能有不同的页目录。
理论上,不同任务的线性地址空间可以映射到完全不同的物理地址。如果不同页目录的条目指向不同的
页表,并且页表指向物理存储器的不同页,则任务不共享任何物理地址。
实际上,所有任务的线性地址空间的某些部分必须映射到相同的物理地址。任务状态段必须位于公共空
间中,以便在任务切换期间,当处理器正在读取和更新 TSS 时,TSS 地址的映射不会改变。由 GDT 映射
的线性空间也应该被映射到公共物理空间;否则,GDT 的目的就失败了。图 7-6 显示了两个任务的线性
空间如何通过共享页表在物理空间中重叠。
7.7.2 任务逻辑地址空间
普通的线性空间到物理空间的映射本身并不能实现任务间的数据共享。为了共享数据,任务还必须有
一个公共的逻辑到线性的空间映射;即,它们还必须能够访问指向共享线性地址空间的描述符。有三
种方法可以创建常见的逻辑到物理的地址空间映射:
1. 经由 GDT。所有任务都可以访问 GDT 中的描述符。如果这些描述符指向一个线性地址空间,该
空间被映射到所有任务的公共物理地址空间,那么这些任务可以共享数据和指令。
2. 通过共享 ldt。如果 TSS 中的 LDT 选择器选择了相同的 LDT 段,两个或多个任务可以使用相
同的 LDT。那些
指向映射的线性空间的 LDT 常驻描述符

允许任务共享物理内存。这种分享方法比 GDT 的分享更有选择性;共享可以限于特定的任务。
系统中的其他任务可能具有不同的 ldt,这些 ldt 不允许它们访问共享区域。
3. 通过 ldt 中的描述符别名。不同 ldt 的某些描述符可能指向相同的线性地址空间。如果该线性
地址空间通过所涉及的任务的页面映射被映射到相同的物理空间,则这些描述符允许任务共享
公共空间。这种描述符通常被称为“别名”。这种分享方法比前两种更具选择性;ldt 中的其
他描述符可以指向不同的线性地址或不共享的线性地址。

8 Input/Output

本章从以下角度介绍 80386 的 I/O 功能:
● I/O 端口的寻址方法
● 导致输入输出操作的指令
● 适用于 I/O 指令和 I/O 端口使用的保护地址。

8.1 I/O Addressing

80386 允许以两种方式进行输入/输出:
● 通过独立的 I/O 地址空间(使用特定的 I/O指令)
● 通过内存映射 I/O(使用通用操作数操作说明)。

8.1.1 I/O Address Space

80386 提供了一个独立的I/O地址空间,不同于物理内存,可用于寻址用于外部 16 设备的I/O端口。I/O 地址空间由 2^16 (64K)个可单独寻址的8位端口组成;任何两个连续的8位端口可以被视为一个16 位端口;并且可以处理四个连续的 8 位端口作为 32 位端口。因此,I/O 地址空间最多可容纳 64K 个 8 位端口、32K 个 16 位端口或 16K 个 32 位端口。

程序可以用两种方式指定端口的地址。使用立即字节常量,程序可以指定:

使用 DX 中的值,程序可以指定:
- 编号为 0 到 65535 的 8 位端口
- 编号为 0、2、4、.。。, 65532, 65534
- 编号为 0、4、8、.。。, 65528, 65532

80386 可以一次向位于I/O空间的设备传输32、16或8位。像内存中的双字一样,32位端口应该在能被4整除的地址上对齐,这样 32 位就可以传入单一总线访问。像存储器中的字一样,16 位端口应在偶数地址对齐,以便16位可以在单次总线访问中传输。8 位端口可以位于偶数或奇数地址。

指令 IN 和OUT在I/O地址空间的寄存器和端口之间移动数据。指令 INS 和 OUTS在内存地址空间和I/O地址空间的端口之间移动数据串。

8.1.2 Memory-Mapped I/O

I/O 设备也可以放在80386内存地址空间中。只要由于这些设备的响应类似于内存组件,因此处理器无法区分它们。

内存映射 I/O 提供了额外的编程灵活性。任何引用存储器的指令都可以用来访问位于存储器空间中的I/O端口。例如,MOV 指令可以在任何寄存器和端口之间传输数据;and、OR 和 TEST 指令可用于操作设备内部寄存器中的位(见图8-1)。

image_1gs65t1nu195t1ju61nmk1s20fpr9.png-34.6kB

通过完整指令集执行的内存映射 I/O 保持用于选择所需 I/O 设备的全套寻址模式(例如,直接地址、间接地址、基址寄存器、变址寄存器、缩放)。

与任何其他内存引用一样,内存映射的I/O在保护模式下执行时会受到访问保护和控制。关于存储器保护的讨论,请参考第 6 章。

8.2.2 Block I/O Instructions

块(或串)I/O 指令INS和OUTS在I/O端口和内存空间之间移动数据块。块 I/O 指令使用DX寄存器来指定I/O地址空间中的端口地址。

ins 和 outs 使用 DX 来指定:
- 编号为 0 到 65535 的 8 位端口
- 编号为 0、2、4、.。。, 65532, 65534
- 编号为 0、4、8、.。。, 65528, 65532

块 I/O 指令使用SI或DI来指定源或目的memory地址。对于每次传输,SI 或 DI会根据flags寄存器中方向(D)位的指定自动递增或递减。

当 ins 和 outs与repeat前缀一起使用时,会导致块输入或输出操作。重复前缀 REP 修改了 ins和outs,提供了一种在I/O 端口和内存之间传输数据块的方法。这些块I/O指令是串原语(关于字符串原语的更多信息,请参考第3章)。它们无需使用单独的循环指令或中间寄存器来保存数据,从而简化了编程并提高了数据传输速度。

串 I/O 原语可以操作字节串、字串或双字串。每次传输后,对于字节操作数,ESI或EDI中的内存地址更新1;对于字操作数,更新 2;对于双字操作数,更新4。方向标志(DF)中的值决定了处理器是自动递增ESI还是EDI(DF=0),还是自动递减这些寄存器(DF=1)。

8.3 Protection and I/O

两种机制为 I/O 功能提供保护:

  1. EFLAGS 寄存器中的IOPL域定义了使用I/O相关指令的权利。
  2. 80386 TSS 段的I/O权限位图定义了使用I/O地址空间中端口的权利。

这些机制仅在保护模式下运行,包括虚拟8086模式;它们不在实模式下运行。在实模式下,没有对I/O空间的保护;任何程序都可以执行 I/O指令,任何I/O端口都可以由I/O指令寻址。

8.3.1 I/O特权级别

处理 I/O 的指令需要受到限制,但也需要由以非零特权级别执行的过程来执行。因此,处理器使用标志寄存器的两位来存储 I/O 特权级别(IOPL)。IOPL定义了特权级别需要执行与I/O 相关的指令。

只有当 CPL ≤ IOPL 时,才能执行以下指令:

这些指令被称为“敏感”指令,因为它们对IOPL很敏感。要使用敏感指令,过程必须在至少与IOPL(CPL≤IOPL)指定的特权级别相同的特权级别上执行。特权较低的过程使用敏感指令的任何尝试都会导致一般保护异常。

因为每个任务都有自己唯一的标志寄存器副本,所以每个任务可以有不同的 IOPL。主要功能是执行I/O(设备驱动程序)的任务可以从 IOPL 为 3中受益,从而允许该任务的所有过程执行 I/O。其他任务通常将IOPL设置为零或一,为最有特权的过程保留执行 I/O 指令的权利。

一个任务只能用POPF指令改变IOPL;然而,这样的改变是有特权的。任何过程都不能改变IOPL(标志寄存器中的I/O特权级别),除非该过程在特权级别执行0。特权较低的过程试图改变IOPL 不会导致异常;IOPL 只是保持不变。

除了 CLI 和 STI之外,还可以使用POPF指令来改变中断使能标志(IF);然而,POPF对IF的修改是IOPL敏感。仅当在至少与 IOPL 一样的特权级别上执行时,过程才可以用POPF指令来改变 IF。特权较低的过程以这种方式改变IF的尝试不会导致异常;如果只是保持不变。

8.3.2 I/O Permission Bit Map

直接引用处理器I/O空间中地址的I/O指令有in、INS、OUT、OUTS。80386 能够有选择地trap对特定I/O地址的引用。启用选择性trap的结构是 TSS 段中的 I/O 权限位图(见图 8-2)。

image_1gs6746s51nr41kqi1f8v1trk11icm.png-48.5kB

I/O 权限映射是一个位向量。位图的大小及其在TSS段中的位置是可变的。处理器定位通过TSS固定部分中的I/O映射base字段进行 I/O 许可映射。I/O 映射base字段为 16 位宽,包含 I/O 权限映射开始的偏移量。I/O权限映射的上限与TSS段的限制相同。

在保护模式下,当遇到I/O指令(In、INS、OUT或OUTS)时,处理器首先检查 CPL 是否≤ IOPL。如果这个条件为真,I/O 操作可以继续进行。如果不为真,处理器检查I/O权限映射。(在虚拟 8086 模式下,处理器查询映射而不考虑IOPL。参考第 15 章。)

映射中的每个位对应一个 I/O 端口字节地址;例如,端口 41 的位位于 I/O map base+ 5,位偏移 1。处理器测试与 I/O 操作跨越的 I/O地址相对应的所有位;例如,双字操作测试对应于四个相邻字节地址的四个位。如果设置了任何测试位,
处理器发出一般保护异常信号。如果所有测试的位都是零,则 I/O 操作可以继续进行。

I/O 权限图没有必要代表所有的 I/O 地址。未被映射跨越的 I/O 地址被视为在映射中有一个位。例如,如果 TSS限制等于 I/O 映射基数+ 31,则映射前 256 个 I/O 端口;任何大于 255 的端口上的 I/O 操作都会导致异常。

如果 I/O 映射base大于或等于 TSS 限制,则 TSS 段没有 I/O 权限映射,当 CPL > IOPL 时,80386 程序中的所有 I/O 指令都会引起异常。因为I/O权限映射在TSS段中,所以不同的任务可以有不同的映射。因此,操作系统可以通过改变任务
的 TSS 中的 I/O 权限映射来为任务分配端口。

9 异常和中断

中断和异常是特殊类型的控制转移;它们的工作有点像未编程的调用。它们改变正常的程序流程来处理外部事件或报告错误或异常情况。中断和异常的区别在于,中断用于处理处理器外部的异步事件,而异常则处理处理器本身在执行指令过程中检测到的情况。

外部中断有两个来源,异常有两个来源:

  1. 中断

    • 可屏蔽中断,通过 INTR 引脚发出信号。
    • 不可屏蔽中断,通过NMI发出信号(不可屏蔽中断)引脚。
  2. 例外

    • Processor detected。这些被进一步分类为 faults, traps, and aborts。
    • Programmed。INTO、INT3、INT n和BOUND指令可以触发异常。这些指令通常被称为“软件中断”,但是处理器将它们作为异常来处理。

本章解释了 80386在保护模式下执行时控制和响应中断的特性。

9.1 识别中断

处理器将标识号与每个不同类型的中断或异常相关联。

NMI 和处理器识别的异常被分配0到31范围内的预定标识符。80386 目前并没有使用所有这些号码;此范围内未分配的标识符由英特尔保留,用于将来可能的扩展。

可屏蔽中断的标识符由外部中断控制器(如 Intel 的 8259A 可编程中断控制器)确定,并在处理器的中断应答序列期间与处理器通信。8259A PIC分配的数字可以由软件指定。可以使用32到255范围内的任何数字。表 9-1 显示了中断和异常标识符的分配。

image_1gs6f7ir5lcc38g539vlmeuh13.png-57.5kB

根据报告异常的方式以及是否支持重新启动导致异常的指令,异常可分为faults, traps, or aborts。

9.2 启用和禁用中断

处理器只在一条指令结束和下一条指令开始之间处理中断和异常。当 repeat 前缀用于重复字符串指令时,可能会发生中断和异常重复之间。因此,对长字符串的操作不会延迟中断响应。

某些条件和标志设置会导致处理器禁止指令边界处的某些中断和异常。

9.2.1 NMI Masks Further NMIs

当 NMI 处理程序正在执行时,处理器会忽略NMI引脚上的其他中断信号,直到执行下一条 IRET 指令。

9.2.2 IF Masks INTR

IF(中断使能标志)控制通过 INTR 引脚接收外部中断信号。当 IF=0 时,INTR 中断被禁止;当IF=1时,INTR中断使能。与其他标志位一样,处理器在响应复位信号时会将 IF 清零。

指令 CLI 和 STI 改变IF 的设置。

CLI(清除中断使能标志)和 STI(设置中断使能标志)明确改变 IF(标志寄存器的位9)。只有当CPL≤IOPL时,才可以执行这些指令。如果在 CPL > IOPL 时执行它们,则会出现保护异常。

IF 还受到以下操作的隐含影响:

9.2.3 RF Masks Debug Faults

EFLAGS 中的 RF位控制调试故障的识别。这允许给定指令最多出现一次调试错误,而不管该指令重启多少次。(有关调试的更多信息,请参考第 12 章。)

9.2.4 MOV 或 POP 到 SS 屏蔽一些中断和异常

需要改变栈段的软件往往使用一对指令;例如:

  1. MOV SS, AX
  2. MOV ESP, StackTop

如果在SS改变后,ESP改变前处理中断或异常,那么在中断处理程序或异常处理程序期间,栈指针SS:ESP的两个部分不一致。

为了防止这种情况,在 MOV 到SS和弹出到SS指令之后,80386 在改变 SS 的指令之后的指令边界处禁止NMI、INTR、调试异常和单步陷阱。

一些exceptions仍可能发生;即页面故障和一般保护故障。始终使用 80386 LSS 指令,问题就不会出现。

9.3 同步中断和异常之间的优先级

如果在一个指令边界上有一个以上的中断或异常是未决的,处理器一次服务其中的一个。中断和异常源类别的优先级如表9-2 所示。

image_1gs6gtoei1fgcj4r1073124t1h7b1g.png-40.1kB

处理器首先处理来自具有最高优先级的类的未决中断或异常,将控制权转移给中断处理程序的第一条指令。较低优先级的异常被丢弃;较低优先级的中断被挂起。丢弃的异常当中断处理程序将控制返回到中断点时,将被重新发现。

9.4 中断描述符表

中断描述符表(IDT)将每个中断或异常标识符与服务相关事件的指令描述符相关联。像 GDT 和 ldt 一样,IDT 是一个 8 字节的描述符数组。与GDT和ldt不同,IDT的第一个条目可以包含描述符。

为了形成IDT 的索引,处理器将中断或异常标识符乘以8。因为只有 256 个标识符,所以IDT不需要包含超过256个描述符。它可以包含少于256个条目;只有实际使用的中断标识符才需要条目。

image_1gs6h97lv15hn1h87m31dvv1iij1t.png-56.9kB

IDT 可以驻留在物理内存中的任何地方。如图9-1所示,处理器通过 IDT 寄存器(IDTR)来定位IDT。LIDT 和 SIDT 指令在 IDTR 执行。两条指令都有一个显式操作数:6字节区域的内存地址。图 9-2 显示了这个区域的格式。

image_1gs6h9k7p1su41jk519541jq8d592a.png-22.3kB

9.5 IDT 描述符

IDT 可以包含三种描述符中的任何一种:
- Task gates
- Interrupt gates
- Trap gates

图 9-3 说明了任务门和 80386 中断门和陷阱门的格式。(IDT 中的任务门与第 7 章中已经讨论过的任务门相同。)

image_1gs6hcdiu1inh11ggu931gvb1m5q2n.png-100.2kB

9.6 中断任务和中断程序

正如call指令可以调用过程或任务一样,中断或异常也可以“调用”中断处理程序,它可以是过程,也可以是任务。当响应中断或异常时,处理器使用中断或异常标识符来索引IDT中的描述符。如果处理器索引到一个中断门或陷阱门,它以类似于调用调用门的方式调用处理程序。如果处理器找到一个任务
门,它以类似于调用任务门的方式引起任务切换。

9.6.1 中断程序

image_1gs6hi8408p915vaj1oeer1koi34.png-57.1kB

如图 9-4 所示,一个中断门或陷阱门间接指向一个将在当前执行任务的上下文中执行的程序。门的选择器指向 GDT或当前 LDT 中的可执行段描述符。门的OFFSET字段指向中断或异常处理程序的开始。

80386 调用中断或异常处理过程的方式与它调用过程的方式非常相似;下面几节解释了这些差异。

9.6.1.1 中断程序栈

正如call指令引起的控制转移一样,对中断或异常处理过程的控制转移使用栈来存储返回原始过程所需的信息。

image_1gs6hmb3u1gn519ue1qkfm0a1k6j3h.png-83.6kB

如图 9-5 所示,在指针指向被中断的指令之前,中断将EFLAGS寄存器推到栈上。

某些类型的异常还会导致错误代码被压入堆栈。异常处理程序可以使用错误代码来帮助诊断异常。

9.6.1.2 从中断程序返回

中断过程在退出过程的方法上也不同于正常过程。IRET指令用于从中断程序中退出。IRET 类似于RET,除了 IRET 将 EIP 额外增加四个字节(因为栈上的标志)并将保存的标志移入eflags 寄存器。

仅当CPL为零时,EFLAGS的IOPL字段才会改变。仅当 CPL ≤ IOPL 时,IF 标志才会改变。

9.6.1.3 中断程序的标志使用

通过中断门或陷阱门引导的中断导致TF(陷阱标志)在 TF 的当前值作为 EFLAGS的一部分保存在堆栈上后被重置。通过这个动作,处理器防止使用单步调试活动影响中断响应。随后的 IRET 指令将 TF 恢复为堆栈上 EFLAGS 映像中的值。

中断门和陷阱门的区别在于对IF(中断使能标志)的影响。引导通过中断门的中断复位IF,从而防止其他中断干扰当前的中断处理程序。随后的 IRET 指令将 IF 恢复为堆栈上 EFLAGS 映像中的值。通过陷阱门的中断不会改变IF。

9.6.1.4 中断程序中的保护

管理中断过程的特权规则类似于过程调用的特权规则:CPU不允许中断将控制权转移给比当前特权级别低的特权段(数字上更高的特权级别)中的过程。试图违反此规则会导致一般保护异常。

因为中断的发生通常是不可预测的,所以该特权规则有效地限制了中断和异常处理过程可以执行的特权级别。可以采用以下策略中的任何一种来确保永远不会违反特权规则。

9.6.2 中断任务

IDT 中的任务门间接指向一个任务,如图9-6所示。

image_1gs6j1i9qnr51eftub29q7qv63u.png-37.3kB

门的选择器指向 GDT 中的 TSS 描述符。当中断或异常指向 IDT 中的任务门时,会导致任务切换。用单独的任务处理中断有两个好处:

处理器执行任务切换所采取的动作将在第7章中讨论。中断任务通过执行IRET指令返回到被中断的任务。如果任务切换是由具有错误代码的异常引起的,则处理器自动将错误代码推送到与中断任务中要执行的第一条指令的特权级别相对应的堆栈上。

当在 80386 的操作系统中使用中断任务时,实际上有两个调度程序:软件调度程序(操作系统的一部分)和硬件调度程序(处理器中断机制的一部分)。软件调度程序的设计应该考虑到这样一个事实,即硬件调度程序可以在中断启用时分派中断任务。

9.7 错误代码

对于与特定段相关的异常,处理器将一个错误代码压入异常处理程序(无论是过程还是任务)的栈中。错误代码的格式如图 9-7 所示。

image_1gs6ja0ca1h0ohgk1ika7811t164o.png-45.6kB

错误代码的格式类似于选择器的格式;但是,错误代码包含两个一位项目,而不是 RPL 字段:
1. 如果程序外部的事件导致了异常,处理器将设置 EXT 位。
2. 如果错误代码的索引部分涉及IDT中的门描述符,则处理器设置 I 位(IDT 位)。

如果 I 位未置位,TI 位表示错误代码是指 GDT(值 0)还是 LDT(值 1)。剩余的 14 位是所涉及的段选择器的
高 14 位。在某些情况下,堆栈上的错误代码为空,即低位字中的所有位都为零。

9.8 异常条件

以下部分详细描述了每种可能的异常情况。每个描述都将异常分类为故障、陷阱或中止。这种分类提供了系统程序员重新启动发生异常的过程所需的信息:

9.8.1 Interrupt 0 ── Divide Error

当除数为零时,在DIV或IDIV指令期间会出现除法错误fault。

9.8.3 Interrupt 3 ── Breakpoint

INT 3 指令导致了这个trap。INT 3指令的长度是一个字节,这使得用断点操作码替换可执行段中的操作码变得很容易。操作系统或调试子系统可以使用可执行段的数据段别名,将 INT 3放在任何便于停止正常执行的地方,以便执行某种特殊处理。调试器通常使用断点作为显示寄存器、变量等的方式。,在任务的关键点。保存的CS:EIP值指向断点后面的字节。如果调试器用有效的操作码替换植入的断点,它必须在返回之前从保存的 EIP 值中减去 1。关于调试的更多信息,请参考第 12 章。

9.8.6 中断 6 ──无效操作码

当执行单元检测到无效的操作码时,就会出现此fault。(在试图执行无效操作码之前,不会检测到异常;即,预取无效操作码不会导致该异常。)没有错误代码被压入栈。该异常可以在同一任务中处理。

当操作数的类型对于给定的操作码无效时,也会发生此异常。示例包括引用寄存器操作数的段间JMP,或带有寄存器源操作数的 les 指令。

9.8.14 中断 14 ──页面错误

当分页使能(PG=1)且处理器在将线性地址转换为物理地址时检测到以下情况之一时,会出现该异常:

处理器向页面fault处理器提供两项有助于诊断异常并从中恢复的信息:

image_1gs6kb1au1llt1st9qgvlir6ic9.png-124kB

9.9 异常摘要

image_1gs6kjpogo4n1m3t155l1pe2d5j1g.png-64.6kB
image_1gs6kk5o6bsi14q914n14d11gcm1t.png-12.7kB

9.10 错误代码总结

image_1gs6kilj9fr714oitcq4of1c9r13.png-51.5kB

10、初始化

RESET 引脚上出现信号后,80386的某些寄存器被设置为预定义值。这些值足以执行一个引导程序,但是在处理器的所有功能被利用之前,软件必须执行额外的初始化。

10.1 复位后的处理器状态

EAX 的内容取决于通电自检的结果。复位结束时,可以通过置位 BUSY#从外部请求自测。如果80386通过测试,EAX寄存器保持零。自检后 EAX中的非零值表示特定的80386单元有故障。如果未请求自检,则复位后 EAX 的内容未定义。

image_1gs6meu8b1q8n1haa122pq7j1bvc2a.png-40.3kB

如图 10-1 所示,DX在复位后保存一个组件标识符和版本号。DH 包含 3,表示80386成分。DL包含revision级别的唯一标识符。

image_1gs6mimf414429lq19td1ara60r2n.png-45.9kB

控制寄存器零(CR0)包含图 10-2 所示的值。如果配置中存在 80387,则 CR0 的ET位置1(根据复位后ERROR#引脚的状态)。如果 ET 被复位,配置要么包含80287,要么不包含协处理器。需要进行软件测试来区分后两种可能性。

其余寄存器和标志设置如下:

EFLAGS =00000002H
IP =0000FFF0H
CS selector =000H
DS selector =0000H
ES selector =0000H
SS selector =0000H
FS selector =0000H
GS selector =0000H
IDTR:
base =0
limit =03FFH

上面没有提到的所有寄存器都是未定义的。这些设置意味着处理器从实地址模式开始,中断被禁用。

10.2 实地址模式的软件初始化

在实地址模式下,在程序能够利用这种模式下所有可用的特性之前,必须初始化一些结构。

10.2.1 栈

在加载堆栈段寄存器(SS)之前,不能使用使用堆栈的指令。SS 必须指向 RAM 中的一个区域。

10.2.2 中断表

80386 的初始状态使中断禁用;但是,如果发生异常或不可屏蔽中断(NMI ),处理器仍会尝试访问中断表。初始化软件应采取以下措施之一:

10.2.3 第一条指令

在复位之后,地址线A{31-20}被自动断言用于指令提取。这个事实,加上 CS:IP的初始值,导致指令执行从物理地址FFFFFFF0H 开始。近(段内)形式的控制转移指令可用于将控制传递给地址空间的高 64K字节中的其它地址。第一个远(段间)JMP 或 CALL 指令使A{31-20}下降到低电平,80386 继续在较低的 1
兆物理内存中执行指令。这种地址线的自动断言{31-20}允许系统设计人员在地址空间的高端使用 ROM 来初始化系统。

10.3 切换到保护模式

设置 CR0 中 MSW的PE位会导致80386开始在保护模式下执行。当前特权级别(CPL)从零开始。段寄存器继续指向与实地址模式中相同的线性地址(在实地址模式中,线性地址是相同的物理地址)。

设置 PE 标志后,初始化代码必须立即通过执行JMP指令来刷新处理器的指令预取队列。80386在指令和地址被使用之前提取和解码它们;然而,在变为保护模式后,预取的指令信息(属于实地址模式)不再有效。JMP 迫使处理器丢弃无效信息。

10.4 保护模式的软件初始化

保护模式所需的大部分初始化可以在切换到保护模式之前或之后完成。但是,如果在保护模式下完成,初始化过程不得使用尚未初始化的保护模式功能。

10.4.1 中断描述符表

IDTR 可以以实地址或保护模式加载。然而,保护模式的中断表格式不同于实地址模式的中断表格式。不能同时改变保护模式和中断表格式;因此,不可避免的是,如果IDTR选择了一个中断表,它有时会有错误的格式。此时发生的中断或异常将会产生不可预测的结果。为了避免这种不可预测性,中断应该保持禁用状态,直到中断处理程序就绪,并且在保护模式下创建了有效的 IDT。

10.4.2 栈

SS 寄存器可以在实地址模式或保护模式下加载。如果在实地址模式下加载,ss在切换到保护模式后继续指向相同的线性基地址。

10.4.3 全局描述符表

在保护模式下更改任何段寄存器之前,GDT寄存器必须指向有效的 GDT。GDT 和GDTR的初始化可以在实地址模式下完成。GDT(以及 ldt)应该驻留在RAM中,因为处理器会修改描述符的访问位。

10.4.4 页表

CR3 中的页表和PDBR可以在实地址模式或保护模式下初始化;然而,在处理器处于保护模式之前,CR0的分页使能(PG)位不能置位。PG 可以与 PE 同时设置,也可以稍后设置。当 PG 被设置时,CR3 中的PDBR应该已经用指向有效页目录的物理地址初始化。初始化过程应采用以下策略之一,以确保启用分页前后的一致寻址:

10.4.5 第一个任务

初始化过程可以在保护模式下运行一段时间,而无需初始化任务寄存器;但是,在第一次任务切换之前,必须满足以下条件:

10.6 TLB 测试

80386 提供了一种测试转换后备缓冲器(TLB)的机制,该缓冲器用于将线性地址转换为物理地址。尽管TLB硬件出现故障的可能性极小,但用户可能希望在80386的其它加电信心测试中包括 TLB 信心测试。

测试 TLB 时,建议关闭分页(CR0中PG=0 ),以避免干扰写入 TLB 的测试数据。

10.6.1 TLB 的结构

TLB 是一个四路集联存储器。图10-3说明了TLB的结构。

image_1gs6o8ar0bgt1uvoptc7t819lm34.png-57.3kB

有四组,每组八个条目。每个条目由一个标签和数据组成。标签为 24 位宽。它们包含线性地址的高20位、有效位和三个属性位。每个条目的数据部分包含物理地址的高 20 位。

10.6.2 测试寄存器

两个测试寄存器如图 10-4 所示,用于测试目的。

image_1gs6on3fj6k7pmcgb234157k3h.png-39.2kB

TR6 是测试命令寄存器,TR7 是测试数据寄存器。这些
寄存器由 MOV 的变体访问指令。测试寄存器可以是源操作数或目标操作数。

MOV 指令是在实地址模式和保护模式下定义的。测试寄存器是特权资源;在保护模式下,访问它们的MOV指令只能在特权级别 0 下执行。当以任何其他特权级别执行时,试图读取或写入测试寄存器会导致一般保护异常。

测试命令寄存器(TR6)包含命令和用于执行命令的地址标签:

11、Coprocessing and Multiprocessing

80386 对多个并行处理单元有两种级别的支持:

11.1 Coprocessing

协处理器接口的组件包括:
● ET bit of control register zero (CR0)
● The EM, and MP bits of CR0
● The ESC instructions
● The WAIT instruction
● The TS bit of CR0
● Exceptions

11.1.1 协处理器标识

80386 设计用于与 80287或80387数学协处理器一起运行。CR0 的 ET 位指示存在哪种类型的协处理器。复位后,ET 由80386 根据 ERROR#输入上检测到的电平自动设置。如果需要,也可以通过向 CR0 加载 MOV指令来设置或复位 ET。如果设置了 ET,80386 使用80387 的 32 位协议;如果复位,80386 使用 80287 的 16 位协议。

11.1.2 ESC 和WAIT指令

80386 将指令的前五位中的模式11011B解释为用于协处理器的操作码。如此标记的指令称为ESCAPE或ESC指令。在将指令发送到协处理器之前,CPU 在遇到 ESC 指令时执行以下功能:

WAIT 指令不是 ESC 指令,但是 WAIT会使CPU执行一些与遇到 ESC 指令时相同的测试。处理器对WAIT指令执行以下动作:

11.1.3 EM 和 MP 标志

CR0 的 EM 和 MP标志控制处理器如何对协处理器指令做出反应。

EM 位指示是否要模拟协处理器功能。如果处理器在执行 ESC 指令时发现EM置位,它会发出异常7的信号,给异常处理程序一个模拟 ESC指令的机会。

MP(监控协处理器)位表示是否实际连接了协处理器。MP标志控制WAIT指令的功能。如果在执行WAIT指令时,CPU发现MP设置,则它测试 ts 标志;否则,它不会在等待指令期间测试TS。如果发现 TS 在这些条件下设置,CPU 发出异常 7 信号。

EM 和 MP 标志可以在使用CR0作为目标操作数的MOV指令的帮助下更改,并在使用CR0作为源操作数的MOV指令的帮助下读取。这些形式的 MOV 指令只能在零特权级别执行。

11.1.4 任务切换标志

CR0 的 TS 位有助于确定何时协处理器的上下文与 80386 CPU 执行的任务的上下文不匹配。80386每次执行任务切换时都会设置 ts(无论是由软件还是硬件中断触发)。如果在解释一个 ESC 指令时,CPU 发现 TS已经设置,它将导致异常 7。如果 TS 和 MP 都被设置,等待指令也会导致异常7。操作系统可以使用这个异常来切换协处理器的上下文,以对应于当前任务。有关示例,请参考 80386 系统软件作者指南。

CLTS 指令(仅在特权级别为零时合法)重置 TS 标志。

11.1.5 协处理器异常

三个异常有助于与协处理器接口:中断7(协处理器不可用)、中断 9(协处理器段溢出)和中断 16(协处理器错误)。

11.2 General Multiprocessing

通用多处理接口的组件包括:

11.2.1 LOCK and the LOCK# Signal

LOCK指令前缀及其相应的输出信号LOCK#可用于防止其他总线主控器中断数据移动操作。

当下列 80386指令修改内存时,LOCK只能与它们一起使用。在除以下指令之外的任何指令之前使用LOCK会导致未定义操作码异常:

锁定的指令只能保证锁定由目标操作数定义的内存区域,但它可能会锁定更大的内存区域。例如,典型的 8086 和 80286 配置锁定整个物理内存空间。由目标操作数定义的存储器区域保证被锁定,防止在完全相同的存储器区域上执行锁定指令的处理器访问,即,具有相同起始地址和相同长度的操作数。

锁的完整性不受内存字段对齐的影响。锁定信号在更新整个操作数所需的多个总线周期内有效。

11.2.2 Automatic Locking

在一些情况下,处理器本身在数据总线上启动活动。为了帮助确保此类活动在多处理器配置中正常运行,处理器会自动断言 LOCK#信号。这些例子包括:

11.2.3 Cache Considerations

系统程序员在更新可能也存储在片内寄存器和缓存中的共享数据时必须小心。对于 80386,此类共享数据包括:

系统设计者可以使用处理器间中断来处理上述情况。当一个处理器改变可能被其他处理器缓存的数据时,它可以发送一个中断信号给所有其他可能受该变化影响的处理器。如果中断由中断任务提供服务,任务切换会自动刷新段寄存器。如果中断任务的 PDBR(CR3 的内容)不同于所有其他任务的 PDBR,任
务切换也会刷新页表缓存。在需要来自CPU的可缓存性信号的多处理器系统中,建议使用物理地址引脚A31来指示可缓存性。这样的系统可以拥有高达2gb的物理内存。程序员可用的虚拟地址范围不受此约定的影响。

14、 80386 Real-Address Mode

80386 的实地址模式执行为在 8086、8088、80186 或 80188 处理器上执行而设计的目标代码,或为在80286的实模式下运行。

实际上,80386 在这种模式下的结构与 8086、8088、80186 和 80188 几乎相同。对程序员来说,实地址模式下的 80386 看起来像一个高速的 8086,带有指令集和寄存器的扩展。第 2 章和第 3 章定义了这种体系结构的主要特征。本章讨论了某些附加主题,以完善系统程序员对80386在实地址模式下的看法:

● 地址形成。
● 寄存器和指令的扩展。
● 中断和异常处理。
● 进入和离开实地址模式。
● 实地址模式异常。
● 与 8086 的区别。
● 与 80286 实地址模式的区别。

14.1 Physical Address Formation

80386 为 8086 程序提供了1兆字节+64千字节的存储空间。在 8086 中执行段重定位:段选择器中的16位值左移4位,形成段的基址。

image_1gs74s2s315hshss1n6at7di4k3u.png-37.8kB

如图 14-1所示,有效地址用四个高位零扩展,并加到base上形成一个线性地址。(线性地址相当于物理地址,因为在实地址模式下不使用分页。)

与 8086 不同,最终的线性地址最多可有21个有效位。基址加到有效地址就有进位的可能。在 8086 上,进位被截断,而在 80386 上,进位存储在线性地址的第 20 位。

与 8086 和 80286不同,可以生成32位有效地址(通过地址大小前缀);但是,32位地址的值不能超过65535,否则会导致异常。为了与 80286 实地址模式完全兼容,如果在 0 到 65535 范围之外产生有效地址,则会出现伪保护故障(无错误代码的中断 12 或 13)。

14.2 寄存器和指令

实地址模式下可用的寄存器集包括为8086定义的所有寄存器以及 80386 引入的新寄存器:FS、GS、调试寄存器、控制寄存器和测试寄存器。显式地在段寄存器FS和GS上操作的新指令是可用的,并且新的段覆盖前缀可以用于使指令利用 FS 和 GS 进行地址计算。通过使用操作数大小前缀,指令可以利用 32 位操作数。

引起未定义操作码陷阱(中断6)的指令代码包括操作或询问80386 选择器和描述符的保护模式的指令;即VERR, VERW, LAR, LSL, LTR, STR,
LLDT, and SLDT。

在实地址模式下执行的程序能够利用通过引入80186/80188、80286和80386而添加到架构中的新的面向应用的指令:

● 80186/80188 和 80286 引入的新指令。

● 80386 引入的新指令。

14.3 Interrupt and Exception Handling

80386 实地址模式下的中断和异常与8086一样有效。中断和异常通过中断表指向中断程序。处理器将中断或异常标识符乘以 4,以获得中断表的索引。中断表的条目是指向中断或异常处理程序入口点的远指针。当中断发生时,处理器将 CS:IP 的当前值推送到堆栈上,禁用中断,清除TF(单步标志),然后
将控制转移到中断表中指定的位置。在处理程序过程结束时的 IRET 指令在将控制返回到被中断的过程之前反转这些步骤。

与 8086 相比,80386中断处理的主要区别在于中断表的位置和大小取决于 IDTR(IDT寄存器)的内容。通常,这一事实对程序员来说并不明显,因为在复位后,IDTR 包含一个基地址 0 和一个与 8086 兼容的极限3FFH。然而,LIDT指令可以在实地址模式下使用,以改变IDTR中的基值和极限值。有关IDTR、LIDT 和 SIDT 指令的详细信息,请参考第9章。如果发生中断,且中断表中的相应条目超出了IDTR中存储的限值,则处理器会引发异常 8。

14.4 进入和离开实地址模式

RESET 引脚上出现信号后,实地址模式生效。即使系统将在保护模式下使用,在为保护模式初始化时,启动程序也将暂时在实地址模式下执行。

14.4.1 切换到保护模式

离开实地址模式的唯一方法是切换到保护模式。

当 MOV 到 CR0 指令将 CR0 中的 PE(保护使能)位置位时,处
理器进入保护模式。(为了与80286兼容,LMSW指令也可用于设置 PE 位。)

关于切换到保护模式的其他方面,请参考第 10 章“初始化”。

14.5 切换回实地址模式

如果软件用 MOV到CR0指令清除了CR0中的PE位,则处理器重新进入实地址模式。然而,尝试这样做的过程应该如下进行:

  1. 如果启用了分页,请执行以下序列:

    • 将控制转移到具有标识映射的线性地址;即线性地址等于物理地址。
    • 清除 CR0 中的 PG 位。
    • 将 0 移动到 CR3 以清除分页缓存。
  2. 将控制转移到一个限制为 64K (FFFFH)的段。这将向 CS 寄存器加载实模式下所需的限值。

  3. 使用一个选择器加载段寄存器SS、DS、ES、FS和GS,该选择器指向包含以下值的描述符,这些值适用于实模式:

    • Limit = 64K (FFFFH)
    • Byte granular (G = 0)
    • Expand up (E = 0)
    • Writable (W = 1)
    • Present (P = 1)
    • Base = any value
  4. 禁用中断。CLI指令禁用INTR中断。NMIs可以通过外部电路禁用。

  5. 将 PE 位清零。

  6. 使用远 JMP 跳转到要执行的实模式代码。此操作会刷新指令队列,并将适当的值放入 CS 寄存器的访问权限中。

  7. 使用 LIDT 指令加载实模式中断向量表的基址和极限。

  8. 启用中断。

  9. 根据实模式代码的需要加载段寄存器。

14.6 实地址模式异常

80386在实际地址模式下执行时报告的一些异常与在保护模式下执行不同。表 14-1 详细说明了实地址模式的异常。

image_1gs762ebqv70a7d11hh6igkqb4b.png-63.1kB

14.7 与 8086 的区别

一般来说,80386在实地址模式下将正确执行为8086、8088、80186 和 80188 设计的基于 ROM 的软件。以下是 80386 和 8086 上的 8086 执行之间的细微差别列表。

  1. 指令时钟计数。

    对于大多数指令,80386比8086/8088占用更少的时钟。最有可能受到影响的领域是:

    • 输入输出设备在输入输出操作之间所需的延迟。
    • 假设 8086/8088 与 8087 并行运行时出现延迟。
  2. Divide 异常指向 DIV 指令。

    80386 上的Divide异常总是让保存的CS:IP值指向失败的指令。在 8086/8088 上,CS:IP 值指向下一条指令。

  3. 未定义的 8086/8088 操作码。

    没有为 8086/8088定义的操作码将导致异常或者将执行为 80386 定义的新指令之一。

  4. 推入 SP 写入的值。

    与 8086/8088相比,80386将不同的值压入推式SP的栈。作为推送操作的一部分,80386在SP递增之前推送SP的值;8086/8088 在 SP 的值增加后将其推入。如果推送的值很重要,请用以下三条指令替换推送 SP 指令:

    1. PUSH BP
    2. MOV BP, SP
    3. XCHG BP, [BP]

    该代码的功能相当于 80386 上的8086/8088推式SP指令。

  5. 移位或旋转超过 31 位。

    80386 将所有移位和旋转计数屏蔽到低五位。MOD32操作将计数限制在最大31位,从而限制了指令执行时中断响应延迟的时间。

  6. 多余的前缀。

    80386 对指令长度设置了15字节的限制。违反这一限制的唯一方法是在指令前加上多余的前缀。如果违反了指令长度限制,则会出现异常 13。8086/8088 没有指令长度限制。

  7. 操作数越过偏移量 0 或 65,535。

    在 8086 上,试图访问越过偏移量65,535(例如,将一个字 MOV 到偏移量 65,535)或偏移量0(例如,当 SP = 1 时推入一个字)的存储器操作数会导致偏移量以 65,536 为模回绕。在这些情况下,80386 会引发一个异常──异常 13,如果该段是数据段(即,如果使用 CS、DS、ES、FS 或 GS 来寻址该段),如果该段是堆栈段(即,如果使用 ss),则例外 12。

  8. 跨偏移量 65,535 的顺序执行。

    在 8086 上,如果指令的顺序执行超过了偏移量65,535,处理器就从同一段的偏移量 0 取下一个指令字节。在 80386 上,处理器在这种情况下会引发异常 13。

  9. 锁定仅限于某些指令。

    锁定前缀及其相应的输出信号应仅用于防止其他总线主控器中断数据移动操作。80386 总是在带有存储器的 XCHG 指令期间断言锁定信号(即使没有使用锁定前缀)。当更新内存
    时,LOCK 只能与下列80386指令一起使用:BTS, BTR, BTC, XCHG, ADD, ADC, SUB, SBB, INC, DEC,
    AND, OR, XOR, NOT, and NEG。未定义操作码异常(中断6)是在任何其他指令之前使用 LOCK 导致的。

  10. 单步外部中断处理程序。

    80386 单步异常的优先级不同于8086/8088。如果程序单步执行时发生中断,此更改可防止外部中断处理程序单步执行。80386 单步异常的优先级高于任何外部中断。80386仍将单步执行由INT 指令或异常调用的中断处理程序。

  11. 80H 或 8000H 商的 IDIV 例外。

    80386 能产生最大的负数作为 IDIV指令的商。8086/8088 反而导致异常零。

  12. 栈中的标志。

    PUSHF、中断和异常存储的标志位设置不同于 8086 存储的位位置12 到 15 岁。在 8086 上,这些位存储为 1,但在 80386 实地址模式下,位 15 总是 0,位 14 到 12
    反映了最后装入的值。

  13. NMI 中断 NMI 处理程序。

    在 80386 上识别出 NMI 后,NMI 中断被屏蔽,直到IRET 指令被执行。

  14. 协处理器错误向量到中断 16。

    任何带有协处理器的 80386 系统都必须使用中断向量 16 来处理协处理器错误异常。如果8086/8088 系统对 8087 中断使用另一个向量,两个向量都应该指向协处理器错误异常处理程序。

  15. 数字异常处理程序应该允许前缀。

    在 80386 上,为协处理器异常保存的 CS:IP 值指向 ESC 指令前的任何前缀。在 8086/8088 系统上,保存的 CS:IP 指向 ESC 指令。

  16. 协处理器不使用中断控制器。

    80386 的协处理器错误信号不通过中断控制器(8087 INT 信号通过)。如果处理中断控制器,协处理器错误处理程序中的一些指令可能需要删除。

  17. 六个新的中断向量。

    80386 增加了六个例外,只有当8086程序有隐藏的错误时才会出现。建议添加异常处理程序,将这些异常视为无效操作。这个附加软件不会对现有的8086软件产生显著影响,因为中断通常不会发生。这些中断标识符不应该已经被8086软件使用,因为它们在 Intel 保留的范围内。表14-2 描述了新的 80386 异常。

    image_1gs77822tpek1vk71aji1tbfh834o.png-81.2kB

  18. 一兆字节环绕(wraparound)。

    在实地址模式下,80386 不在 1 兆字节处包装地址。在 8086 系列的成员上,可以指定大于一兆字节的地址。例如,选择器值为 0FFFFH,偏移量为 0FFFFH,则有效地址为 10FFEFH (1 兆字节+65519)。8086只能形成最长20位的地址,它截断高位,从而将该地址“包装”成 0FFEFH。然而,80386可以形成长达 32 位的地址,它不能截断这样的地址。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注