intel汇编学习

基础知识

  • 多字节数据排放

    高址结尾4C6A 低址结尾4C6A 高址结尾40ACB139 低址结尾40ACB139
    m+4
    m+3 39 40
    m+2 B1 AC
    m+1 6A 4C AC B1
    m 4C 6A 40 39


    80386用低址结尾(Little Endian)

  • 整数

    8位带符号整数,-128~127

    8位无符号整数,0~256

    16位带符号整数,-32K~(32K-1)

    16位无符号整数,0~64K

    32位带符号整数,-2G~(2G-1)

    32位无符号整数,0~4G

  • 无符号整数

  • 带符号整数

    1. 偏移值

      由于偏移值表示法具有便于进行数据比较的优点,常用来表示浮点数的指数.在原来正数或负数之上再加一个偏移量,便得到该数偏移值.

    2. 带符号的值

      所谓带符号的值即冠以符号的值, 有一位来表示(0为正1为负)

    3. 二进制反码

      对于正整数,二进制反码与带符号的值具有相同的形式,最高有效位为0(表示正数),其余各位为值的二进制表示.而负整数的二进制反码,由该数绝对值的二进制码逐位取反得到

    4. 二进制补码

      二进制反码之上再加1,最高位也是符号位,0为正,1为负

    5. 只有80386才支持位操作

    6. BCD码

  • 机器语言是机器指令的集合

  • 机器语言难于辨别和记忆,于是产生了汇编语言

  • 想让一个cpu工作,就必须提供指令和数据。指令和数据在存储器中存放,也就是我们平时所说的内存

  • 指令和数据在内存或者磁盘上,没有任何区别,都是二进制信息

8086

8086CPU的寄存器

  • 8086一次可以存储两种尺寸数据: 字节(byte) , 字(word)

  • 寄存器

    寄存器 作用
    AX(accumulator,累加寄存器) 16位通用寄存器,可分为AH和AL
    BX(base,基址寄存器) 16位通用寄存器,可分为AH和AL
    CX(counter,计数寄存器) 16位通用寄存器,可分为AH和AL
    DX(data,数据寄存器) 16位通用寄存器,可分为AH和AL
    CS(counter,计数寄存器) 代码段地址寄存器
    DS(data segment,数据段寄存器) 数据段地址寄存器
    SS(stack segment,栈段寄存器) 栈的段地址
    IP 指令指针寄存器
    ES(extra segment,附加段寄存器) 附加段寄存器
    SP(stack pointer,栈指针寄存器) 栈的偏移地址
    SI(source index,源变址寄存器) 变址寄存器
    DI(destination index,目的变址寄存器) 变址寄存器
    BP(base pointer,基址指针寄存器)
    PSW
    FS()
    GS()
  • 一个存储单元为1 byte,8086有20位地址总线,可以寻址2^20个单元,达到1MB寻址能力

  • 8086只有16位,通过两个16位地址,一个称为段地址,另一个称为偏移地址,通过一个地址加法器的部件结合成一个20位的物理地址。物理地址=段地址*16 + 偏移地址

  • 一个段的起始地址一定是16的倍数

  • 内存并没有分段,段的划分来自于cpu

  • 段的偏移地址为16位,所以一个段的长度最大为64KB

  • 8086加电后,cs和ip被设置为cs=FFFFH,IP=0000H,cpu从内存FFFF0H处读取执行第一条指令

  • 不能用mov改变cs,ip

  • jmp可以修改cs:ip,jmp 段地址:偏移地址

  • 将一段内存当作代码段,仅仅是我们在编程时的一种安排,cpu不会由于这种安排就自动将我们定义的代码段中的指令当作指令来执行

  • Debug使用方法

    命令 作用
    r 查看、改变cpu寄存器内容
    d 查看内存中的内容
    e 改写内存中的内容
    u 将内存中机器指令翻译成汇编指令
    t 执行一条机器指令
    a 以汇编指令的格式在内存中写入一条机器指令

8086处理器寻址方式

寄存器寻址

;最简单的寻址方式,操作的数位于寄存器中
mov ax,cx
add bx,0xf000
inc dx

立即寻址

;又叫立即数寻址,指令的操作数是一个立即数
add bx,0xf000
mov dx,label_a

内存寻址

  • 直接寻址
  ; 使用[]括起来的数,表示内存地址,ds*0x10 + []中的数字
  mov ax,[0x5c0f] ; ax = (ds) * 0x10 + 5c0f
  add word [0x230],0x5000 ; (ds) * 0x10 + 0x230 += 0x5000
  xor byte [es:label_b],0x05 ; (es) * 0x10 + label_b xor 0x05
  • 基址寻址
  ; 顺序存放的数据,使用循环来处理,可以使用基址寻址,在指令的地址部分使用基址寄存器bx或者bp提供偏移
  mov [bx],dx   ; (ds)*0x10 + (bx) = dx
  add byte [bx],0x55 ; (ds)*0x10 + (bx) += 0x55

  ; 也可以使用bp, 使用了bp后,默认的段寄存器是ss,它常用于访问栈
  mov ax,[bp]; ax = (ss)*0x10 + (bp) 形成20位物理地址

  ; 当使用push和pop时,如果需要取0x5就必须先弹出0x7
  mov ax,0x5
  push ax
  mov ax,0x7
  push ax
  ; 有时候想直接访问栈中内容,例如高级语言参数都在栈中,想访问那些栈中参数,又不能破坏sp,就得使用bp
  mov ax,0x5
  push ax
  mov bp,sp
  mova ax,0x7
  push ax
  mova dx,[bp]
  ; bp还可以使用一个偏移量
  mov dx,[bp-2] ; dx = (ss)*0x10 + (bp) - 2

  • 变址寻址
  ;变址寻址类似基址寻址,不同的是寻址方式使用变址寄存器(索引寄存器)si和di
  mov [si],dx ; 默认使用ds (ds)*0x10 + (si) = dx
  add ax,[di]
  xor word [si],0x8000
  • 基址变址寻址
  ;使用(bx或者bp)外加(si或di)
  mov ax,[bx+si] ; ax=(ds)*0x10 + (bx) + (si) 形成20位地址
  add word [bx+di],0x3000

[BX]和loop指令

  • [bx]表示ds:bx
  • 段前缀,显式的给出内存单元段地址所在段寄存器, ds:[bx], cs:[bx], ss:[bx], cs:[bx]
  • mov ax,[200+bx] 相当于(ax)=((ds)*16+(bx)+200)
  • 基址变址寻址 mov ax,[bx+si]或者mov ax,[bx][si] 相当于 (ax)-((ds)*16+(bx)+(si))
  • si,di与bx功能相近,si和di不能够分成两个8位寄存器来使用
  • mov ax,[bx+si+200]相当于(ax)=((ds)*16+(bx)+(si)+200)
    表示方法有
    mov ax,[bx+200+si]
    mov ax,[200+bx+si]
    mov ax,200[bx][si]
    mov ax,[bx].200[si]
    mov ax,[bx][si].200

转移指令

  • 修改cs或者ip,转移指令就是可以控制cpu执行内存某处代码的指令

  • 转移分为几类

jmp ax      段内转移,短转移:ip的修改范围-128~127。近转移:ip的修改范围-32768~32767,近转移:ip的修改范围-32768~32767

jmp 1000:0  段间转移
  • 转移指令分为:无条件转移指令,条件转移指令,循环指令,过程,中断

  • offset是由编译器处理的符号

  assume cs:codesg
  codesg segment
    start:mov ax,offset start ;相当于mov ax,0
    s: mov ax,offset s ;相当于mov ax,3 第一条指令长度3个字节
  codesg ends
  end start
  • jmp指令,无条件转移指令,可以修改ip或者同时修改cs和ip,jmp指令要给出两种信息:转移的目的地址,转移的距离

  • jmp short 标号 段内短转移

call 指令

  • cpu执行call两步操作
  1. 将当前的IP或CS和IP压入栈中
  2. 转移


  1. (sp)=(sp)-2
    ((ss)*16+(sp))=(IP)
  2. (IP)=(IP)+16位位移
  • call指令不能实现短转移

  • call和jmp原理相同

转移的目的地址在指令中的call指令

  • call far ptr 标号 实现段间转移
  (sp)=(sp)-2
  ((ss)*16+(sp))=(CS)
  (sp)=(sp)-2
  ((ss)*16+(sp))=(IP)

  (CS)=标号所在段的段地址
  (IP)=标号所在段中偏移地址

call和ret的配合使用

  • 示例
  assume cs:code

  stack segment
    db 8 dup (8)
    db 8 dup (7)
  stack ends

  code segment
    start:
        mov ax, stack
        mov ss, ax
        mov sp, 16
        mov ax, 1000
        call s ;call执行后,call下一句的(IP)会被压入栈中,然后(IP)=(IP)+s的偏移量
        mov ax, 4c00h
        int 21h
    s:
        add ax, ax
        ret ;ret执行后,相当于 pop IP
  code ends

  end start

参数和结果传递

  • 示例
  assume cs:code
  data segment
    dw 1,2,3,4,5,6,7,9
    dd 0,0,0,0,0,0,0,0
  data ends

  code segment
  start:
    mov ax, data
    mov ds, ax
    mov si, 0
    mov di, 16

    mov cx, 8
    s:
        mov bx, [si]   ; bx=ds:si 待乘的数值
        call cube
        mov [di], ax   ; ds:di=ax  乘法结果 低8位
        mov [di].2, dx ; ds:(di+2)=dx 乘法结果 高8位
        add si, 2
        add di, 4
        loop s

        mov ax, 4c00h
        int 21h

  cube:
    mov ax, bx
    mul bx
    mul bx
    ret
  code ends
  end start

批量数据传递

  • 示例
assume cs:code

  data segment
    db 'conversation'
  data ends

  code segment
    start:
        mov ax, data
        mov ds, ax
        mov si, 0
        mov cx, 12
        call capital
        mov ax, 4c00h
        int 21h
  capital:
    and byte ptr [si], 11011111b
    inc si
    loop capital
    ret
  code ends
  end start

寄存器冲突的问题

  • 示例
   assume cs:code
   data segment
    db 'word', 0
    db 'uninx', 0
    db 'wind', 0
    db 'good', 0
   data ends

   code segment
    start:
        mov ax, data
        mov ds, ax
        mov bx, 0

        mov cx, 4
    s:
        mov si, bx
        call capital
        add bx, 5
        loop s

        mov ax, 4c00h
        int 21h
   capital:
    push cx ;子程序使用cx,将cx入栈
    push si ;子程序使用si,将si入栈
   change:
    mov cl, [si]
    mov ch, 0
    jcxz ok
    and byte ptr [si],  11011111b
    inc si
    jmp short change
    ok:
        pop si
        pop cx
        ret
   code ends
   end start

标志寄存器

名称 作用
0 CF 进位标志位
1
2 PF 相关指令执行后,结果中所有bit位中1个数为偶数pf=1,奇数,pf=0
3
4 AF
5
6 ZF 计算结果为0时,zf=1.结果不为0,zf=0
7 SF 计算结果位负数,sf=1,非负数,sf=0
8 TF
9 IF
10 DF 方向标志位。df=0 si,di递增。df=1 si,di递减
11 OF 溢出标记
12
13
14
15

pushf和popf

  • pushf将标志寄存器值压栈
  • popf栈中弹出数据,送入标志寄存器

内中断产生

  • 除法错误
  • 单步执行
  • 执行into指令
  • 执行int指令

中断向量表

  • 8位的中断向量码
  • 保存在内存中,存放256个中断源所对应的入口
  • 8086的中断向量表放在内存地址0处,内存0000:0000到0000:03FF
  • 一个表项占两个字,高地址存放段地址,低地址存放偏移地址

中断过程

  • 进入中断

  • 取得中断类型码N

  • pushf
  • TF=0, IF=0
  • push CS
  • push IP
  • (IP)=(N x 4), (CS)=(N x 4+2)

  • iret

  • pop IP

  • pop CS
  • popf

中断示例

  • rep movsb

英文move string byte缩写,搬移ds:si一个字节到es:di地址上,重复cx上的此书,每搬移一次,si和di会自动指向下一个地址,cx减一

中断示例

assume cs:code

code segment
start:
    mov ax,cs
    mov ds,ax
    mov si,offset do0
    mov ax,0
    mov es,ax
    mov di,200h
    mov cx,offset do0end-offset do0
    cld
    rep movsb

    mov ax,0
    mov es,ax
    mov word ptr es:[0*4], 200h
    mov word ptr es:[0*4+2], 0

    int 0

do0: 
    jmp short do0start
    db "overflow!"

do0start:
    mov ax, cs
    mov ds, ax
    mov si, 202h

    mov ax, 0b800h
    mov es, ax
    mov di, 12*160+36*2
    mov cx, 9
    s:
        mov al, [si]
        mov es:[di],al
        inc si
        add di,2
        loop s
        mov ax,4c00h
        int 21h
do0end:nop
code ends
end start

int指令

引导扇区

  • 如果硬盘是首选启动,BIOS 将试图读取硬盘的 0 面 0 道 1 扇区。这就是主引导扇区(Main Boot Sector, MBR)
  • 主引导扇区数据有 512 字节
  • bios程序将它加载到0x0000:0x7c00处
  • 一个有效主引导扇区数据,最后两个字节应当是0x55和0xaa

早期显卡和显存

  • 计算机系统设计者决定把显存映射到处理器直接访问的地址空间,内存空间
  • 8086可以访问1MB内存,其中,0x00000~0x9FFFF属于常规内存,0xF0000~0xFFFFF由主板芯片bios提供,中间0xA0000~0xEFFFF未使用,共 320KB
  • 由于历史的原因 ,个人计算机上使用显卡,加电后都会初始化到80x25文本模式
  • 0xB800作为段地址,段内偏移0就是左上角第一个字符

可用于加载用户程序的空间范围

20位地址划分

FFFFF

​ ROM BIOS

C0000 BFFFF

​ 显存地址空间

A0000 9FFFF

​ 可用的空间

10000 0FFFF

​ 主引导扇区程序(加载器) 及其堆栈

00000

I/O端口和端口访问

  • 连接硬盘的PATA/SATA接口有几个端口,分别是命令端口(当向该端口写入0x20时,表示硬盘读数据;写入0x30时,表明向硬盘写数据)
端口 地址 数据宽度
硬盘读 0x20
硬盘写 0x30
状态端口
参数端口
数据端口 16位
  • 有些计算机系统中,端口号映射到内存地址空间比如,0x00000~0xE0000 是真实的物理内存地址,而 0xE0001~0xFFFFF 是从很多 I/O 接口那里映射过来的,当访问这部分地址时,实际上是在访问 I/O 接口

  • 而在另一些计算机系统中,端口是独立编址的,不和内存发生关系。如图 8-10 所示,在这种计算机中,处理器的地址线既连接内存,也连接每一个 I/O 接口。但是,处理器还有一个特殊的引脚 M/IO#,在这里,“#”表示低电平有效。也就是说,当处理器访问内存时,它会让 M/IO#引脚呈高电平,这里,和内存相关的电路就会打开;相反,如果处理器访问 I/O 端口,那么 M/IO#引脚呈 低平,内存电路被禁止。与此同时,处理器发出的地址和 M/IO#信号一起用于打个某个 I/O 接口,如果该 I/O 接口分配的端口号与处理器地址相吻合的话。

  • Intel 处理器,早期是独立编址的,现在既有内存映射的,也有独立编址的。

  • 每个 PATA 和 SATA 接口分配了 8个端口。但是,ICH 芯片内部通常集成了两个 PATA/SATA 接口,分别是主硬盘接口和副硬盘接口。这样一来,主硬盘接口分配的端口号是 0x1f0~0x1f7,副硬盘接口分配的端口号是0x170~0x177。

  • 在 Intel 的系统中,只允许 65536 (十进制数)个端口存在,端口号从 0 到 65535 ( 0x0000~0xffff)

  • 因为是独立编址,所以,端口的访问不能使用类似于 mov 这样的指令,取而代之的是 in 和 out 指令。

  ; in 指令的目的操作数必须是寄存器 AL 或者 AX
  ; 当访问 8 位的端口时,使用寄存器 AL;访问 16 位的端口时,使用 AX
  ; in 指令的源操作数应当是寄存器 DX
  ; 这种指令形式的操作数部分只允许一字节,故只能访问0~255(0x00~0xff)号端口,不允许访问大于 255 的端口号

  in al,dx
  in ax,dx

  ; out 指令正好和 in 指令相反,目的操作数可以是 8 位立即数或者寄存器 DX,源操作数必须是寄存器 AL 或者 AX。
  out 0x37,al ;写0x37号端口(这是一个8位端口)
  out 0xf5,ax ;写0xf5号端口(这是一个16位端口)
  out dx,al ;这是一个8位端口,端口号在寄存器DX中
  out dx,ax ;这是一个16位端口,端口号在寄存器DX中
  • in和out不影响任何标志位

用硬盘控制器端口读扇区数据

  • 硬盘读写的基本单位是扇区。就是说,要读就至少读一个扇区,要写就至少写一个扇区,不可能仅读写一个扇区中的几个字节。这样一来,就使得主机和硬盘之间的数据交换是成块的,所以硬盘是典型的块设备。

  • 从硬盘读写数据,最经典的方式是向硬盘控制器分别发送磁头号、柱面号和扇区号(扇区在某个柱面上的编号),这称为 CHS 模式。这种方法最原始,最自然,也最容易理解。

  • 实际上,在很多时候,我们并不关心扇区的物理位置,所以希望所有的扇区都能统一编址。
  • 最早的逻辑扇区编址方法是LBA28,使用 28 个比特来表示逻辑扇区号,从逻辑扇区 0x0000000 到 0xFFFFFFF,共可以表示 2^28=268435456 个扇区。每个扇区有 512 字节,所以 LBA28 可以管理 128 GB 的硬盘
  • LBA28 已经落后了。在这种情况下,业界又共同推出了 LBA48,采用 48 个比特来表示逻辑扇区号。如此一来,就可以管理131072 TB 的硬盘容量了

  • LBA28访问硬盘示例

  • 第 1 步,设置要读取的扇区数量。

  ; 个人计算机上的主硬盘控制器被分配了 8 位端口,端口号从 0x1f0 到 0x1f7
  ; 设置要读取的扇区数量。这个数值要写入 0x1f2 端口。这是个 8 位端口,因此每次只能读写 255 个扇区
  mov dx,0x1f2 ;读取扇区数量写入0x1f2端口,如果写入的值为0,则表示要读取256个扇区。每读一个扇区,这个数值就减一。因此,如果在读写过程中发生错误,该端口包含着尚未读取的扇区数。
  mov al,0x01 ;1个扇区
  out dx,al 
  • 第 2 步,设置起始 LBA 扇区号。
  ; 28 位的扇区号太长,需要将其分成 4 段
  ; 0x1f3 号端口存放的是 0~7 位;0x1f4 号端口存放的是 8~15 位;0x1f5 号端口存放的是 16~23 位,最后 4 位在 0x1f6 号端口。
  mov dx,0x1f3
  mov al,0x02
  out dx,al  ;LBA 地址 7~0
  inc dx   ;0x1f4
  mov al,0x00
  out dx,al ;LBA 地址 15~8
  inc dx   ;0x1f5
  out dx,al  ;LBA 地址 23~16
  inc dx ;0x1f6
  mov al,0xe0  ;LBA 模式,主硬盘,以及 LBA 地址 27~24
  out dx,al
  • 0x1f6 端口

    作用
    0 ~ 3 逻辑扇区号27~24位
    4 0: 主硬盘 1: 从硬盘
    5 1
    6 0: CHS 1: LBA
    7 1
  • 第 3 步,向端口 0x1f7 写入 0x20,请求硬盘读。

    mov dx,0x1f7
    mov al,0x20 ;读命令
    out dx,al
  • 第 4 步,等待读写操作完成

    0x1f7作用

    作用
    0 ERR 1: 前一个命令执行错误,具体原因访问0x1f1
    1
    2
    3 DRQ 1: 硬盘准备好和主机交换数据
    4
    5
    6
    7 BSY 1: 硬盘忙
        mov dx,0x1f7
    .waits:
        in al,dx
        and al,0x88  ;0x88(10001000),如果寄存器 AL 中的二进制数是00001000(0x08),那就说明可以退出等待状态,继续往下操作,否则继续等待
        cmp al,0x08
        jnz .waits  ;不忙,且硬盘已准备好数据传输
  • 第 5 步,连续取出数据
    ; 连续取出数据。0x1f0 是硬盘接口的数据端口,而且还是一个 16 位端口。一旦硬盘控制器空闲,且准备就绪,就可以连续从这个端口写入或者读取数据。下面的代码假定是从硬盘读一
    个扇区(512 字节,或者 256 字节),读取的数据存放到由段寄存器 DS 指定的数据段,偏移地址由寄存器 BX 指定
        mov cx,256 ;总共要读取的字数
        mov dx,0x1f0
    .readw:
        in ax,dx
        mov [bx],ax
        add bx,2
        loop .readw
  • 0x1f1 端口是错误寄存器,包含硬盘驱动器最后一次执行命令后的状态(错误原因)

中断

外部硬件中断

非屏蔽中断(Non Maskable Interrupt,NMI)
  • 外部硬件中断是通过两个信号线引入处理器内部的,这两根线的名字就叫 NMI 和 INTR
  • 中断信号的来源,或者说,产生中断的设备,称为中断源
  • 实模式下,NMI 被赋予了统一的中断号 2,不再进行细分
可屏蔽中断
  • 数量很多,可以被屏蔽
  • 处理器每次只能处理一个中断,所以需要中断控制器,比如8259芯片
  • Intel 处理器允许 256 个中断,中断号的范围是 0~255,8259 负责提供其中的 15 个,但中断号并不固定、
  • 8259芯片有自己的端口号,可以像访问其他外部设备一样用 in 和 out 指令来改变它的状态,包括各引脚的中断号。正是因为这样,它又叫可编程中断控制器(Programmable Interrupt Controller,PIC)
  • 8259 的主片引脚 0(IR0)接的是系统定时器/计数器芯片;从片的引脚 0(IR0)接的是实时时钟芯片 RTC
  • 8259 芯片内部,有中断屏蔽寄存器(Interrupt Mask Register,IMR),这是个 8 位寄存器
  • 标志寄存器IF为0时,INTR引脚中断信号都被忽略,为1时,处理器可以接受响应中断
  • IF 标志位可以通过两条指令 cli 和 sti 来改变这两条指令都没有操作数, cli (CLear Interrupt flag)用于清除 IF 标志位,sti(SeT Interrupt flag)用于置位 IF 标志
  • 反正每片 8259 只有 8 个中断输入引脚,而在个人计算机上使用它,需要两块。如图 9-2 所示,第一块 8259 芯片的代理输出 INT 直接送到处理器的 INTR 引脚,这是主片(Master);第二块 8259 芯片的 INT 输出送到第一块的引脚 2 上,是从片(Slave),两块芯片之间形成级联(Cascade)关系。
  • 总体上来说,中断的优先级和引脚是相关的,主片的 IR0 引脚优先级最高,IR7引脚最低,从片也是如此。当然,还要考虑到从片是级联在主片的 IR2 引脚上。
实模式下的中断向量表
  • 在实模式下,处理器要求将它们的入口点集中存放到内存中从物理地址 0x00000 开始,到 0x003ff 结束,共 1KB的空间内,这就是所谓的中断向量表(Interrupt Vector Table,IVT)
  • 在 8259 芯片那里,每个引脚都赋予了一个中断号。而且,这些中断号是可以改变的,可以对8259 编程来灵活设置,但不能单独进行,只能以芯片为单位进行。比如,可以指定主片的中断号从0x08 开始,那么它每个引脚 IR0~IR7 所对应的中断号分别是 0x08~0x0e
  • 中断发生后处理器需要
  • 保护断点的现场
  • 执行中断处理程序
  • 返回到断点接着执行
  • 中断向量表的建立和初始化工作是由 BIOS 在计算机启动时负责完成的BIOS 为每个中断号填写入口地址,因为它不知道多数中断处理程序的位置,所以,一律将它们指向一个相同的入口地址,在那里,只有一条指令:iret。也就是说,当这些中断发生时,只做一件事,那就是立即返回。当计算机启动后,操作系统和用户程序再根据自己的需要,来修改某些中断的入口地址,使它指向自己的代码

内部中断

  • 内部中断发生在处理器内部,是由执行的指令引起的
  • 软中断是由 int 指令引起的中断处理。BIOS 中断也是软中断

MASM

参考王元珍的《80x86汇编语言程序设计 》 书中附录

NASM

80386

EFLAGS标志寄存器

标志名 作用
CF 0 进位标志
1
PF 2 恢复标志
3
AF 4 辅助进位
5
ZF 6 零标志
SF 7 负号标志
TF 8 Trap Flag,跟踪标志。当设置该位时可为调试操作启动单步执行方式;复位时则禁止单步执行。在单步执行方式下,处理器会在每个指令执行之后产生一个调试异常,这样我们就可以观察执行程序在执行每条指令后的状态。如果程序使用POPF、POPFD或IRET指令设置了TF标志,那么在随后指令之后处理器就会产生一个调试异常。
IF 9 中断允许标志
DF 10 方向标志
OF 11 溢出标志
IOPL 12,13 I/O Privilege Level,I/O特权级。该字段指明当前运行程序或任务的 I/O 特权级IOPL。当前运行程序或任务的 CPL 必须小于等于这个 IOPL 才能访问 I/O 地址空间。只有当 CPL为特权级 0 时,程序才可以使用 POPF 或 IRET 指令修改这个字段。IOPL 也是控制对 IF 标志修改的机制之一。
NT 14 Nested Task,嵌套任务标志。它控制着被中断任务和调用任务之间的链接关系。在使用CALL 指令、中断或异常执行任务调用时,处理器会设置该标志。在通过使用 IRET 指令从一个任务返回时,处理器会检查并修改这个 NT 标志。使用 POPF/POPFD 指令也可以修改这个标志,但是在应用程序中改变这个标志的状态会产生不可意料的异常。
RF 16 Resume Flag,恢复标志。该标志用于控制处理器对断点指令的响应。当设置时,这个标志会临时禁止断点指令产生的调试异常;当该标志复位时,则断点指令将会产生异常。RF 标志的主要功能是允许在调试异常之后重新执行一条指令。当调试软件使用 IRETD 指令返回被中断程序之前,需要设置堆栈上 EFLAGS 内容中的 RF 标志,以防止指令断点造成另一个异常。处理器会在指令返回之后自动地清除该标志,从而再次允许指令断点异常。
VM 17 Virtual-8086 Mode,虚拟8086方式。当设置该标志时,就开启虚拟-8086 方式;当复位该标志时,则回到保护模式。
18 保留

内存管理寄存器

GDTR

寄存器 作用
GDTR 全局描述符表寄存器 32位线性基地址,16位表长度 基地址指定GDT 表中字节 0 在线性地址空间中的地址,表长度指明 GDT 表的字节长度值。指令 LGDT 和 SGDT 分别用于加载和保存 GDTR 寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为 0,而长度值被设置成 0xFFFF。在保护模式初始化过程中必须给 GDTR 加载一个新值。
#### IDTR
寄存器 作用
------------------------------------ ----------------------------------------- --------------------------------------------
IDTR 中断描述符表寄存器 32位线性基地址,16位表长度 指令 LIDT 和 SIDT 分别用于加载和保存 IDTR 寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为 0,而长度值被设置成 0xFFFF。
#### TR
寄存器 作用
------------------------------------ ----------------------------------------- --------------------------------------------
TR 任务寄存器 16位段选择符,32位线性基地址,16位段长度和描述符属性值 指令LLDT 和 SLDT 分别用于加载和保存 LDTR 寄存器的段描述符部分。包含 LDT 表的段必须在 GDT 表中有一个段描述符项。当使用 LLDT 指令把含有 LDT 表段的选择符加载进 LDTR 时,LDT 段描述符的段基地址、段限长度以及描述符属性会被自动地加载到 LDTR 中。当进行任务切换时,处理器会把新任务 LDT的段选择符和段描述符自动地加载进 LDTR 中。在机器加电或处理器复位后,段选择符和基地址被默认地设置为 0,而段长度被设置成 0xFFFF。
#### LDTR
寄存器 作用
------------------------------------ ----------------------------------------- --------------------------------------------
LDTR 局部描述符表寄存器 16位段选择符,32位线性基地址,16位段长度和描述符属性值 它引用 GDT 表中的一个 TSS 类型的描述符。指令 LTR 和 STR 分别用于加载和保存 TR 寄存器的段选择符部分。当使用 LTR 指令把选择符加载进任务寄存器时,TSS 描述符中的段基地址、段限长度以及描述符属性会被自动地加载到任务寄存器中。当执行任务切换时,处理器会把新任务的 TSS 的段选择符和段描述符自动地加载进任务寄存器 TR 中。

CR0寄存器

含有控制处理器操作模式和状态的系统控制标志

标志名 作用
PE 0 Protection Enable,保护标志。当设置该位时即开启了保护模式;当复位时即进入实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么PE 和 PG 标志都要置位。
MP 1 Monitor Coprocessor,监控协处理器。用于控制 WAIT/FWAIT指令与 TS 标志的交互作用。如果 MP=1、TS=1,那么执行 WAIT 指令将产生一个设备不存在异常;如果 MP=0,则 TS 标志不会影响 WAIT 的执行。
EM 2 EMulation,仿真标志。当该位设置时,表示处理器没有内部或外部协处理器,执行协处理器指令时会引起设备不存在异常;当清除时,表示系统有协处理器。设置这个标志可以迫使所有浮点指令使用软件来模拟。
TS 3 Task Switched,任务已切换。该标志用于推迟保存任务切换时的协处理器内容,直到新任务开始实际执行协处理器指令。处理器在每次任务切换时都会设置该标志,并且在执行协处理器指令时测试该标志。
ET 4 Extension Type,扩展类型。当该标志为 1 时,表示指明系统有 80387 协处理器存在,并使用 32 位协处理器协议。ET=0 指明使用 80287 协处理器。如果仿真位 EM=1,则该位将被忽略。在处理器复位操作时,ET 位会被初始化指明系统中使用的协处理器类型。如果系统中有 80387,则 ET 被设置成 1,否则若有一个 80287 或者没有协处理器,则 ET 被设置成 0。
NE 5 Numeric Error,对于 Intel 80486 或以上的 CPU,CR0 的位 5 是协处理器错误标志。当设置该标<志时,就启用了 X87 协处理器错误的内部报告机制;若复位该标志,那么就使用 PC 机形式的X87 协处理器错误报告机制。当 NE 为复位状态并且 CPU 的 IGNNE 输入引脚有信号时,那么数学协处理器 X87 错误将被忽略。当 NE 为复位状态并且 CPU 的 IGNNE 输入引脚无信号时,那么非屏蔽的数学协处理器 X87 错误将导致处理器通过 FERR 引脚在外部产生一个中断,并且在执行下一个等待形式浮点指令或 WAIT/FWAIT 指令之前立刻停止指令执行。CPU 的 FERR 引脚用于仿真外部协处理器 80387 的 ERROR 引脚,因此通常连接到中断控制器输入请求引脚上。NE 标志、IGNNE 引脚和 FERR 引脚用于利用外部逻辑来实现 PC 机形式的外部错误报告机制。
WP 16 Write Proctect,对于 Intel 80486 或以上的 CPU,CR0 的位 16 是写保护标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级 0 的程序)向用户级只读页面执行写操作;当该位复位时则反之。该标志有利于 UNIX 类操作系统在创建进程时实现写时复制(Copy on Write)技术。
AM 18
PG 31 Paging,分页标志。当设置该位时即开启了分页机制;当复位时则禁止分页机制,此时所有线性地址等同于物理地址。在开启这个标志之前必须已经或者同时开启 PE 标志。即若要启用分页机制,那么 PE 和 PG 标志都要置位。

CR1寄存器

保留不用

CR2寄存器

名称 作用
Page-Fault Linear Address 页错误线性地址 32位 CR2 用于出现页异常时报告出错信息。在报告页异常时,处理器会把引起异常的线性地址存放在 CR2中。因此操作系统中的页异常处理程序可以通过检查 CR2 的内容来确定线性地址空间中哪一个页面引发了异常。

CR3寄存器

名称 作用
Page-Directory Base 页目录表基地址 高 20 位 CR3 含有存放页目录表页面的物理地址,因此 CR3 也被称为 PDBR。因为页目录表页面是页对齐的,所以该寄存器只有高 20 位是有效的。而低 12 位保留供更高级处理器使用,因此在往 CR3 中加载一个新值时低 12 位必须设置为 0。使用 MOV 指令加载 CR3 时具有让页高速缓冲无效的副作用。为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的页高速缓冲器件中,该缓冲器件被称为转换查找缓冲区TLB(Translation Lookaside Buffer)。只有当 TLB 中不包含要求的页表项时才会使用额外的总线周期从内存中读取页表项。

保护模式内存管理

内存寻址

名称 说明
段(Segment) 把内存空间分成一个或多个称为段的线性区域,从而对内存中一个数据对象的寻址就需要使用一个段的起始地址(即段地址)和一个段内偏移地址两部分构成
逻辑地址(虚拟地址) 程序中由 16 位的段和 32 位的偏移构成的 48 位地址或长指针称为一个
线性地址空间 分段机制把程序的逻辑地址变换成处理器可寻址内存空间

分段机制

  • 分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存块单元

  • 80386 虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成

  • 段是虚拟地址到线性地址转换机制的基础

  • 段的参数:段基地址(Base address)段限长(limit)段属性(Attributes)

  • 多个段映射到线性地址中的范围可以部分重叠或覆盖,甚至完全重叠

  • 逻辑地址由 16 位的段选择符和 32 位的偏移量组成

  • 逻辑地址转换成一个线性地址

  • 使用段选择符中的偏移值(段索引)在 GDT 或 LDT 表中定位相应的段描述符。(仅当一个新的段选择符加载到段寄存器中时才需要这一步。)

  • 利用段描述符检验段的访问权限和范围,以确保该段是可访问的并且偏移量位于段界限内
  • 把段描述符中取得的段基地址加到偏移量上,最后形成一个线性地址
  • 如果没开启分页,处理器直接把线性地址映射到物理地址。如果开启了分页,那么就会使用二级地址转换把线性地址转换成物理地址
段描述符表
1. 段描述符的一个数组
2. 表长度可变,最多包含8192个8字节描述符
3. 两种描述符表:GDT和LDT
4. 描述符表存储在由操作系统维护着的特殊数据结构中,并且由处理器的内存管理硬件来引用
5. 虚拟地址空间被分割成大小相等的两半。一半由 GDT 来映射变换到线性地址,另一半则由 LDT 来映射
6. 整个虚拟地址空间共含有2^14^个段:一半空间(即 2^13^个段)是由 GDT 映射的全局虚拟地址空间,另一半是由 LDT 映射的局部虚拟地址空间。通过指定一个描述符表(GDT 或 LDT)以及表中描述符号,我们就可以定位一个描述符 
7. 每个系统必须定义一个 GDT,并可用于系统中所有程序或任务。可选定义一个或多个 LDT
段选择符(逻辑地址由 16 位的段选择符和 32 位的偏移量组成)
  1. 段选择符(或称段选择子)是段的一个 16 位标识符

  2. 指向段描述符表中定义段的段描述符

  3. 15~3 2 1~0
    描述符索引 Index 表指示标志(TI) Table Index 请求特权级 (RPL) Requested Privilege Level
  4. Index给出了描述符在 GDT 或 LDT 表中的索引项号

  5. TI = 0是 GDT 。TI = 1是 LDT。

  6. 对应用程序来说段选择符是作为指针变量的一部分而可见,但选择符的值通常是由链接编辑器或链接加载程序进行设置或修改,而非应用程序。

  7. 加载段寄存器,提供了两类加载指令:

    1) 象 MOV、POP、LDS、LES、LSS、LGS 以及 LFS 指令。这些指令显式地直接引用段寄存器

    2) 隐式加载指令,例如使用长指针的 CALL、JMP 和 RET 指令、IRET、INTn、INTO 和 INT3 等指令。这些指令在操作过程中会附带改变 CS 寄存器(和某些其他段寄存器)的内容

段描述符
  1. 段描述符是 GDT 和 LDT 表中的一个数据结构项

  2. 每个段描述符长度是 8 字节,64位 高32位

31~24 23 22 21 20 19~16 15 14~13 12 11~8 7~0
基地址31~24 G D/B O AVL 段限长19~16 P DPL S TYPE 基地址23~16
低32位
31~16 15~0
---------- ----------
基地址15~0 段限长15~0
详细说明:
名称 作用
------------------------------------------------------------ ------------------------------------------------------------
段限长字段 LIMIT(Segment limit field) 20 位的值。G=0段长度 Limit 范围可从 1 字节到 1MB 字节,单位是字节。G=1段长度 Limit 范围可从 4KB 到 4GB,单位是4KB
基地址字段 BASE(Base address field) 32 位的值。4GB 线性地址空间中一个段字节 0 所处的位置。可以对齐 16 字节边界,让程序具有最佳性能
段类型字段 TYPE(Type field) 类型字段指定段或门(Gate)的类型、说明段的访问种类以及段的扩展方向。该字段的解释依赖于描述符类型标志 S 指明是一个应用(代码或数据)描述符还是一个系统描述符
描述符类型标志 S(Descriptor type flag) 指明一个段描述符是系统段描述符(当S=0)还是代码或数据段描述符(当S=1)
描述符特权级字段 DPL(Descriptor privilege level) 特权级范围从 0 到 3。0 级特权级最高,3 级最低。DPL 用于控制对段的访问。
段存在标志 P(Segment present) P=1 内存中,P=0 不在内存中
D/B(默认操作大小/默认栈指针大小和/或上界限)标志(Default operation size/default stack pointer size and/or upper bound) 根据段描述符描述的是一个可执行代码段、下扩数据段还是一个堆栈段,这个标志具有不同的功能。(对于 32 位代码和数据段,这个标志应该总是设置为 1;对于 16 位代码和数据段,这个标志被设置为 0。) 可执行代码段。此时这个标志称为 D 标志并用于指出该段中的指令引用有效地址和操作数的默认长度。如果该标志置位,则默认值是 32 位地址和 32 位或 8 位的操作数;如果该标志为0,则默认值是 16 位地址和 16 位或 8 位的操作数。指令前缀 0x66 可以用来选择非默认值的操作数大小;前缀 0x67 可用来选择非默认值的地址大小。 栈段(由 SS 寄存器指向的数据段)。此时该标志称为 B(Big)标志,用于指明隐含堆栈操作(例如 PUSH、POP 或 CALL)时的栈指针大小。如果该标志置位,则使用 32 位栈指针并存放在 ESP 寄存器中;如果该标志为 0,则使用 16 位栈指针并存放在 SP 寄存器中。如果堆栈段被设置成一个下扩数据段,这个 B 标志也同时指定了堆栈段的上界限。 下扩数据段。此时该标志称为 B 标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是 0xFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是 0xFFFF(64KB)。
颗粒度标志 G(Granularity) 该字段用于确定段限长字段 Limit 值的单位。如果颗粒度标志为 0,则段限长值的单位是字节;如果设置了颗粒度标志,则段限长值使用 4KB 单位。(这个标志不影响段基地址的颗粒度,基地址的颗粒度总是字节单位。)若设置了 G 标志,那么当使用段限长来检查偏移值时,并不会去检查偏移值的 12 位最低有效位。例如,当 G=1 时,段限长为 0 表明有效偏移值为 0 到 4095。
可用和保留比特位(Available and reserved bits) 段描述符第 2 个双字的位 20 可供系统软件使用;位 21 是保留位并应该总是设置为 0。
十进制 位 11 位 10 位 9 位 8 描述符类型 说明
扩展方向 E 可写 W 已访问 A
0 0 0 0 0 数据 只读
1 0 0 0 1 数据 只读,已访问
2 0 0 1 0 数据 可读/写
3 0 0 1 1 数据 可读/写,已访问
4 0 1 0 0 数据 向下扩展,只读
5 0 1 0 1 数据 向下扩展,只读,已访问
6 0 1 1 0 数据 向下扩展,可读/写
0 1 1 1 数据 向下扩展,可读/写,已访问
一致的 C 可读 R 已访问 A
8 1 0 0 0 代码 仅执行
9 1 0 0 1 代码 仅执行,已访问
10 1 0 1 0 代码 执行/可读
11 1 0 1 1 代码 执行/可读,已访问
12 1 1 0 0 代码 一致性段,仅执行
13 1 1 0 1 代码 一致性段,仅执行,已访问
14 1 1 1 0 代码 一致性段,执行/可读
15 1 1 1 1 代码 一致性段,执行/可读,已访问

1) 堆栈段必须是可读/写的数据段

2) 每当处理器把一个段的段选择符加载进段寄存器,它就会设置已访问 A(Accessed)位。该位需要明确地清除,否则一直保持置位状态。该位可用于虚拟内存管理和调试

3) 对于代码段,R=0 只能执行,R=1 可执行/可读。在保护模式下,代码段是不可写的

4) 代码段可以是一致性的或非一致性的。向更高特权级一致性代码段的执行控制转移,允许程序以当前特权级继续执行。向一个不同特权级的非一致性代码段的转移将导致一般保护异常,除非使用了一个调用门或任务门

5) 所有数据段都是非一致性的,即意味着它们不能被低特权级的程序或过程访问。然而,与代码段不同,数据段可以被更高特权级的程序或过程访问,而无须使用特殊的访问门。

S=0的时候,该描述符是一个系统描述符。处理器能够识别以下一些类型的系统段描述符

 局部描述符表(LDT)的段描述符;  任务状态段(TSS)描述符;  调用门描述符;  中断门描述符;  陷阱门描述符;  任务门描述符。

说明
十进制 位 11 位 10 位 9 位 8
0 0 0 0 0 Reserved 保留
1 0 0 0 1 16-Bit TSS (Available) 16 位 TSS(可用)
2 0 0 1 0 LDT LDT
3 0 0 1 1 16-Bit TSS (Busy) 16 位 TSS(忙)
4 0 1 0 0 16-Bit Call Gate 16 位调用门
5 0 1 0 1 Task Gate 任务门
6 0 1 1 0 16-Bit Interrupt Gate 16 位中断门
7 0 1 1 1 16-Bit Trap Gate 16 位陷阱门
8 1 0 0 0 Reserved 保留
9 1 0 1 32-Bit TSS (Available) 32 位 TSS(可用)
10 1 0 1 0 Reserved 保留
11 1 0 1 1 32-Bit TSS (Busy) 32 位 TSS(忙)
12 1 1 0 0 32-Bit Call gate 32 位调用门
13 1 1 0 1 Reserved 保留
14 1 1 1 0 32-Bit Interrupt Gate 32 位中断门
15 1 1 1 1 32-Bit Trap Gate 32 位陷阱门

分页机制

  • intel处理器访问内存的基本策略是分段,任何时候都无法关闭。也就是说,即使启用页管理功能,分段机制依然起作用。
  • 16位模式下,段的起始位置必须对齐在16字节边界上,且段的长度最大位64KB。进入32位保护模式后,进一步强化了分段功能,并提供保护机制,段的长度可以扩展到处理器的最大寻址边界。
  • 在32位保护模式下,对段的访问本着“先登记,后访问”的原则进行。登记在gdt或ldt中登记段的描述符,规定了段的地址和边界,以及访问权限。任何非法访问都会触发异常中断。
  • 在分段机制的基础上完成虚拟(逻辑)地址到物理地址转换的过程
  • 分页可以用于任何一种分段模型
  • 在分段之后,操作系统的任务是把段拆开,并分别映射到物理页
  • 处理器分页机制会把线性地址空间(段已映射到其中)划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上
  • 分页机制几种页面级保护措施,可和分段机制保护机制合用或替代分段机制的保护措施。例如,在基于页面的基础上可以加强读/写保护。另外,在页面单元上,分页机制还提供了用户 - 超级用户两级保护。
  • CR0 的 PG 位启用分页操作。PG=1,启用分页操作。PG=0,则禁用分页机制,此时分段机制产生的线性地址被直接用作物理地址
  • 与分段机制不同,分页机制对固定大小的内存块(称为页面)进行操作
  • 分页机制把线性和物理地址空间都划分成页面。线性地址空间中的任何页面可以被映射到物理地址空间的任何页面上
  • 分也把4GB内存分成相同大小的页,每个页4KB,4096byte,十六进制表示为0x1000。第一个物理地址0x00000000,第二个物理地址0x00001000,最后一个物理地址0xfffff000
  • 假设已经成功找到并分配一个段空间,基地址为0x00200000,长度8200字节,页最小尺寸4096字节,因此需要占用3个页面,其中最后一个页面只用了8个字节,其余都浪费,但这无关紧要,如果允许页共享,多个段或多个程序可以用同一个页来存放各自数据
  • 80X86 使用 4K(2^12^ )字节固定大小的页面。每个页面均是 4KB,并且对齐于 4K 地址边界处。这表示分页机制把 2^32^ 字节(4GB)的线性地址空间划分成 2^20^ (1M = 1048576)个页面。这些页面可以映射到物理内存中和/或磁盘存储空间中
  • 线性到物理地址的转换功能被扩展成允许一个线性地址被标注为无效的: ①操作系统不支持的线性地址;产生无效地址的程序必须被终止。②对应在虚拟内存系统中的页面在磁盘上而非在物理内存中,该无效地址实际上是请求操作系统虚拟内存管理器把对应页面从磁盘上加载到物理内存中
  • 映射线性地址空间的方法被称为虚拟存储或者需求页(Demand-paged)虚拟存储
  • 为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的缓冲器件中,该缓冲器件被称为转换查找缓冲区 TLB(Translation Lookaside Buffer)
  • 分页转换功能由驻留在内存中的表来描述,该表称为页表(page table),存放在物理地址空间中。页表可以看作是简单的具有 2^20^ 个项的数组。线性到物理地址的映射功能可以简单地看作是进行数组查找
  • 第一级表称为页目录(page directory)它被存放在 1 页 4K 页面中,具有 2^10^ (1K)个 4 字节长度的表项。线性地址的最高 10 位(位 31--22)用作一级表(页目录)中的索引值来选择 2 10 个二级表之一
  • 第二级表称为页表(page table)它的长度也是 1 个页面,最多含有 1K 个 4 字节的表项。每个 4 字节表项含有相关页面的 20 位物理基地址。二级页表使用线性地址中间 10 位(位 21--12)作为表项索引值,以获取含有页面 20 位物理基地址的表项
  • CR3 寄存器指定页目录表的基地址。
31 ~ 22 21 ~ 12 11 ~ 0
页目录表索引 二级页表索引 页内偏移,理地址低 12 位
         1) **CR3寄存器**取得**页目录表基地址**
         2) **线性地址高10**位用于**索引页目录表**,从中取得**二级页表的基地址**
         3) **线性地址中间10位**索引二级页表,获得**物理地址的高 20 位**
         4) **物理地址的高20位** 与 **线性地址低12位** 组成完整的**32位物理地址**
  • 二级表结构允许页表被分散在内存各个页面中,而不需要保存在连续的 4MB 内存块中

  • 目录表页面必须总是存在于物理内存中,但是二级页表可以在需要时再分配。这使得页表结构的大小对应于实际使用的线性地址空间大小

  • 页表中每个页表项大小为 32 位。由于只需要其中的 20 位来存放页面的物理基地址,因此剩下的 12位可用于存放诸如页面是否存在等的属性信息。如果线性地址索引的页表项被标注为存在的,则表示该项即有效,我们可以从中取得页面的物理地址。如果项中表明不存在,那么当访问对应物理页面时就会产生一个异常

  • 二级表结构允许页表被分散在内存各个页面中,而不需要保存在连续的 4MB 内存块中

  • 表项格式

    31 ~ 12 11~9 8~7 6 5 4~3 2 1 0
    页帧地址(Page Fram Address) AVL 0 0 D A 0 0 U/S R/W P
    名称 说明
    --------------------------------------- ----- ------------------------------------------------------------
    31~12
    AVL 11~9 该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。
    8~7
    D页面已被修改标志(Dirty) 6 当处理器对一个页面执行写操作时,就会设置对应页表表项的 D 标志。处理器并不会修改页目录项中的 D 标志
    A 已访问标志(Accessed) 5 当处理器访问页表项映射的页面时,页表表项的这个标志就会被置为 1。当处理器访问页目录表项映射的任何页面时,页目录表项的这个标志就会被置为 1。处理器只负责设置该标志,操作系统可通过定期地复位该标志来统计页面的使用情况。
    4~3
    U/S用户/超级用户标志(User/Supervisor) 2 如果为 1,那么运行在任何特权级上的程序都可以访问该页面。如果为 0,那么页面只能被运行在超级用户特权级(0、1 或 2)上的程序访问
    R/W读/写标志(Read/Write) 1 如果等于 1,表示页面可以被读、写或执行。如果为 0,表示页面只读或可执行。当处理器运行在超级用户特权级(级别 0、1 或 2)时,则 R/W 位不起作用。
    P 存在标志(Present) 0 用于指明表项对地址转换是否有效。P=1 表示有效;P=0 表示无效,会导致一个异常,其余比特位可供程序自由使用

    不存在的页目录和页表的表项格式

31~1 0
程序可用 P
  • 果程序访问物理内存中不存在的页面,处理器就会产生一个缺页异常。此时操作系统就可以利用这个异常处理过程把缺少的页面从磁盘上调入物理内存中,并把相应物理地址存放在表项中。最后在返回程序重新执行引起异常的指令之前设置标志 P=1

  • 已访问标志 A 和已修改标志 D 可以用于有效地实现虚拟存储技术。通过周期性地检查和复位所有 A标志,操作系统能够确定哪些页面最近没有访问过。这些页面可以成为移出到磁盘上的候选者。假设当一页面从磁盘上读入内存时,其脏标志 D=0,那么当页面再次被移出到磁盘上时,若 D 标志还是为 0,则该页面就无需被写入磁盘中。若此时 D=1,则说明页面内容已被修改过,于是就必须将该页面写到磁盘上

保护
  • 保护机制是可靠的多任务运行环境所必须的
  • 保护机制可以被用于分段和分页机制
段级保护
  • 段限长 Limit 检查

  • 段描述符的段限长(或称段界限)字段用于防止程序或过程寻址到段外内存位置

  • 段限长的有效值依赖于颗粒度 G 标志的设置状态
  • 对于数据段,段限长还与标志 E(扩展方向)和标志 B(默认栈指针大小和/或上界限)有关
  • 当 G 标志清零时(字节颗粒度),有效的段长度是 20 位的段描述符中段限长字段 Limit 的值。在这种情况下,Limit 的范围从 0 到 0xFFFFF(1MB)。当 G 标志置位时(4KB 页颗粒度),处理器把 Limit 字段的值乘上一个因子 4K。在这种情况下,有效的 Limit 范围是从 0xFFF 到 0xFFFFFFFF(4GB)。请注意,当设置了 G 标志时,段偏移(地址)的低 12 位不会与 Limit 进行对照检查。例如,当段限长 Limit 等于 0时,偏移值 0 到 0xFFF 仍然是有效的
  • 除了下扩段以外的所有段类型,有效 Limit 的值是段中允许被访问的最后一个地址,它要比段长度小1 个字节
  • 任何超出段限长字段指定的有效地址范围都将导致产生一个一般保护异常
  • 对于下扩数据段,段限长具有同样的功能,但其含义不同。这里,段限长指定了段中最后一个不允许访问的地址,因此在设置了 B 标志的情况下,有效偏移范围是从(有效段偏移+1)到 0xFFFF FFFF;当 B清零时,有效偏移值范围是从(有效段偏移+1)到 0xFFFF。当下扩段的段限长为 0 时,段会有最大长度。
  • 除了对段限长进行检查,处理器也会检查描述符表的长度。GDTR、IDTR 和 LDTR 寄存器中包含有16 位的限长值,处理器用它来防止程序在描述符表的外面选择描述符。描述符表的限长值指明了表中最后一个有效字节。因为每个描述符是 8 字节长,因此含有 N 个描述符项的表应该具有限长值 8N-1。
  • 选择符可以具有 0 值。这样的选择符指向 GDT 表中的第一个不用的描述符项。尽管这个空选择符可以被加载进一个段寄存器中,但是任何使用这种描述符引用内存的企图都将产生一个一般保护性异常。

  • 段类型 TYPE 检查

  • 除了应用程序代码和数据段有描述符以外,处理器还有系统段和门两种描述符类型。这些数据结构用于管理任务以及异常和中断

  • 并非所有的描述符都定义一个段,门描述符中存放有指向一个过程入口点的指针

  • 段描述符在两个地方含有类型信息,即描述符中的 S 标志和类型字段 TYPE

  • S 标志用于指出一个描述符是系统类型的还是代码或数据类型的。TYPE 字段另外提供了 4 个比特位用于定义代码、数据和系统描述符的各种类型

  • 当一个描述符的选择符加载进一个段寄存器中。此时某些段寄存器只能存放特定类型的描述符

    ​ 1) CS 寄存器中只能被加载进一个可执行段的选择符 ​ 2) 不可读可执行段的选择符不能被加载进数据段寄存器中 ​ 3) 只有可写数据段的选择符才能被加载进 SS 寄存器中

  • 当指令访问一个段,而该段的描述符已经加载进段寄存器中。指令只能使用某些预定义的方法来访问某些段

    ​ 1) 任何指令不能写一个可执行段; ​ 2) 任何指令不能写一个可写位没有置位的数据段; ​ 3) 任何指令不能读一个可执行段,除非可执行段设置了可读标志

  • 特权级

  • 处理器的段保护机制可以识别 4 个特权级(或特权层),0 级到 3 级。数值越大,特权越小

  • 处理器利用特权级来防止运行在较低特权级的程序或任务访问具有较高特权级的一个段,除非是在受控的条件下。当处理器检测到一个违反特权级的操作时,它就会产生一个一般保护性异常。

  • 当前特权级 CPL(Current Privilege Level)CPL 是当前正在执行程序或任务的特权级。它存放在CS 和 SS 段寄存器的位 0 和位 1 中

  • 当程序把控制转移到另一个具有不同特权级的代码段中时,处理器就会改变 CPL

  • 当访问一个一致性(conforming)代码段时,则处理器对 CPL 的设置有些不同。特权级值高于(即低特权级)或等于一致代码段DPL 的任何段都可以访问一致代码段

  • 当处理器访问一个特权级不同于 CPL 的一致代码段时,CPL 并不会被修改成一致代码段的 DPL

  • 描述符特权级 DPL(Descriptor Privilege Level)。DPL 是一个段或门的特权级。它存放在段或门描述符的 DPL 字段中

  • 在当前执行代码段试图访问一个段或门时,段或门的 DPL 会用来与 CPL 以及段或门选择符中的 RPL(见下面说明)作比较。根据被访问的段或门的类型不同,DPL 也有不同的含义:

     数据段(Data Segment)。其 DPL 指出允许访问本数据段的程序或任务应具有的最大特权级 数值。例如,如果数据段的特权级 DPL 是 1,那么只有运行在 CPL 为 0 或 1 的程序可以访 问这个段。  非一致代码段(Nonconforming code segment)(不使用调用门)。其 DPL 指出程序或任务访 问该段必须具有的特权级。例如,如果某个非一致代码段的 DPL 是 0,那么只有运行在 CPL 为 0 的程序能够访问这个段。  调用门(Call Gate)。其 DPL 指出访问调用门的当前执行程序或任务可处于的最大特权级数 值。(这与数据段的访问规则相同。)  一致和非一致代码段(通过调用门访问)。其 DPL 指出允许访问本代码段的程序或任务应具 有的最小特权级数值。例如,如果一致代码段的 DPL 是 2,那么运行在 CPL 为 0 的程序就 不能访问这个代码段。  任务状态段 TSS。其 DPL指出访问 TSS 的当前执行程序或任务可处于的最大特权级数值。 (这 与数据段的访问规则相同。)

  • 请求特权级 RPL(Request Privilege Level)。RPL 是一种赋予段选择符的超越特权级,它存放在选择符的位 0 和位 1 中

  • 处理器会同时检查 RPL 和 CPL,以确定是否允许访问一个段。即使程序或任务具有足够的特权级(CPL)来访问一个段,但是如果提供的 RPL 特权级不足的话访问也将被拒绝

  • 也即如果段选择符的 RPL 其数值大于 CPL,那么 RPL 将覆盖 CPL(而使用 RPL 作为检查比较的特权级),反之也然。即始终取 RPL 和 CPL 中数值最大的特权级作为访问段时的比较对象。因此,RPL 可用来确保高特权级的代码不会代表应用程序去访问一个段,除非应用程序自己具有访问这个段的权限

  • 访问数据段时的特权级检查

CS寄存器:CPL

数据段选择符:RPL

数据段描述符:DPL

三个一起进行特权级检查

  1. 为了访问数据段中的操作数,数据段的段选择符必须被加载进数据段寄存器(DS、ES、FS 或 GS)或堆栈段寄存器(SS)中。(可以使用指令 MOV、POP、LDS、LES、LFS、LGS 和 LSS 来加载段寄存器)

  2. 当前运行程序或任务的 CPL、段选择符的 RPL 和段描述符的 DPL 进行比较。只有当段的 DPL 数值大于或等于 CPL和 RPL 两者时,处理器才会把选择符加载进段寄存器中。否则就会产生一个一般保护异常,并且不加载段选择符。

  3. 一个程序或任务可寻址的区域随着其 CPL 改变而变化

  4. 当 CPL 是 0 时,此时所有特权级上的数据段都可被访问;当 CPL 是 1 时,只有在特权级 1 到 3 的数据段可被访问;当 CPL 是 3 时,只有处于特权级 3 的数据段可被访问

  5. 另外,有可能会把数据保存在代码段中。例如,当代码和数据是在 ROM 中时。因此,有些时候我们会需要访问代码段中的数据。此时可以使用以下方法来访问代码段中的数据:

    1) 把非一致可读代码段的选择符加载进一个数据段寄存器中。

    2) 把一致可读代码段的选择符加载进一个数据段寄存器中。

    3使用代码段覆盖前缀(CS)来读取一个选择符已经在 CS 寄存器中的可读代码段

  6. 访问数据段的相同规则也适用方法1)

  7. 方法 2) 则是总是有效的,因为一致代码段的特权级等同于 CPL,而不管代码段的 DPL

  8. 方法 3) 也总是有效的,因为 CS 寄存器选择的代码段的 DPL 与 CPL 相同

  9. 当使用堆栈段选择符加载 SS 段寄存器时也会执行特权级检查。这里与堆栈段相关的所有特权级必须与 CPL 匹配。也即,CPL、堆栈段选择符的 RPL 以及堆栈段描述符的 DPL 都必须相同。如果 RPL 或 DPL与 CPL 不同,处理器就会产生一个一般保护性异常。

  10. 代码段之间转移控制时的特权级检查

  11. 对于将程序控制权从一个代码段转移到另一个代码段,目标代码段的段选择符必须加载进代码段寄存器(CS)中。作为这个加载过程的一部分,处理器会检测目标代码段的段描述符并执行各种限长、类型和特权级检查。如果这些检查都通过了,则目标代码段选择符就会加载进 CS 寄存器,于是程序的控制权就被转移到新代码段中,程序将从 EIP 寄存器指向的指令处开始执行

  12. 程序的控制转移使用指令 JMP、RET、INT 和 IRET 以及异常和中断机制来实现。

  13. 直接调用或跳转到代码段

  14. JMP、CALL 和 RET 指令的近转移形式只是在当前代码段中执行程序控制转移,因此不会执行特权级检查

  15. JMP、CALL 或 RET 指令的远转移形式会把控制转移到另外一个代码段中,因此处理器一定会执行特权级检查

  16. 当不通过调用门把程序控制权转移到另一个代码段时,处理器会验证4种特权级和类型信息

    1) 当前特权级 CPL。(这里,CPL 是执行调用的代码段的特权级,即含有执行调用或跳转程序的代\码段的 CPL。)

    2) 含有被调用过程的目的代码段段描述符中的描述符特权级 DPL

    3) 目的代码段的段选择符中的请求特权级 RPL

    4) 目的代码段描述符中的一致性标志 C。它确定了一个代码段是非一致代码段还是一致代码段

  17. 处理器检查 CPL、RPL 和 DPL 的规则依赖于一致标志 C 的设置状态

    1) C=0,非一致代码段,调用者(程序)的 CPL 必须等于目的代码段的 DPL,否则将会产生一般保护异常。指向非一致代码段的段选择符的 RPL 对检查所起的作用有限。RPL 在数值上必须小于或等于调用者的 CPL 才能使得控制转移成功完成。当非一致代码段的段选择符被加载进 CS 寄存器中时,特权级字段不会改变,即它仍然是调用者的 CPL。即使段选择符的 RPL 与 CPL 不同,这也是正确的

    2) C=1,一致代码段,调用者的 CPL 可以在数值上大于或等于目的代码段的 DPL。仅当 CPL < DPL 时,处理器才会产生一般保护异常。对于访问一致代码段,处理器忽略对 RPL 的检查。对于一致代码段,DPL 表示调用者对代码段进行成功调用可以处于的最低数值特权级。

  18. 大多数代码段都是非一致代码段。对于这些段,程序的控制权只能转移到具有相同特权级的代码段中,除非转移是通过一个调用门进行

  19. 门描述符

31~16 15 14~13 12 11~8 7~5 4~0
段中偏移值 31~16 Offset in Segment 31..16 P DPL 0 TYPE 000 参数个数Param Count
31~16 15~0
段选择符 Segment Selector 段中偏移值 Offset in Segment 15..0
  1. 为了对具有不同特权级的代码段提供受控的访问,处理器提供了称为门描述符的特殊描述符集

  2. 共有4 种门描述符:

    1) 调用门(Call Gate),类型 TYPE=12 2) 陷阱门(Trap Gate),类型 TYPE=15,调用门的特殊类,专门用于调用异常和 3) 中断门(Interrupt Gate),类型 TYPE=14,调用门的特殊类,专门用于中断的处理程序 4) 任务门(Task Gate),类型 TYPE=5,用于任务切换

  3. 调用门用于在不同特权级之间实现受控的程序控制转移

  4. 它们通常仅用于使用特权级保护机制的操作系统中

  5. 一个调用门主要具有一下几个功能:

     指定要访问的代码段;  在指定代码段中定义过程(程序)的一个入口点;  指定访问过程的调用者需具备的特权级;  若会发生堆栈切换,它会指定在堆栈之间需要复制的可选参数个数;  指明调用门描述符是否有效。

  6. 调用门中的段选择符字段指定要访问的代码段。偏移值字段指定段中入口点。这个入口点通常是指定过程的第一条指令

  7. DPL 字段指定调用门的特权级,从而指定通过调用门访问特定过程所要求的特权级。 标志 P 指明调用门描述符是否有效

  8. 参数个数字段(Param Count)指明在发生堆栈切换时从调用者堆栈复制到新堆栈中的参数个数

  9. 通过调用门访问代码段

  10. 为了访问调用门,我们需要为 CALL 或 JMP 指令的操作数提供一个远指针。该指针中的段选择符用于指定调用门,而指针的偏移值虽然需要但 CPU 并不会用它。该偏移值可以设置为任意值

  11. 当处理器访问调用门时,它会使用调用门中的段选择符来定位目的代码段的段描述符。然后 CPU 会把代码段描述符的基地址与调用门中的偏移值进行组合,形成代码段中指定程序入口点的线性地址

  12. 通过调用门进行程序控制转移时,CPU 会对 4 中不同的特权级进行检查,以确定控制转移的有效性:

     当前特权级 CPL;  调用门选择符中的请求特权级 RPL;  调用门描述符中的描述符特权级 DPL;  目的代码段描述符中的 DPL;

  13. 使用 CALL 指令和 JMP 指令分别具有不同的特权级检测规则

  14. 调用门描述符的 DPL字段指明了调用程序能够访问调用门的数值最大的特权级(最小特权级),即为了访问调用门,调用者程序的特权级 CPL 必须小于或等于调用门的 DPL。调用门段选择符的 RPL 也需同调用这的 CPL 遵守同样的规则,即 RPL 也必须小于或等于调用门的 DPL。

    指令 特权级检查规则
    CALL CPL<=调用门的 DPL;RPL<=调用门的 DPL。
    对于一致性和非一致性代码段都只要求 DPL<=CPL
    JMP CPL<=调用门的 DPL;RPL<=调用门的 DPL。
    对于一致性代码段要求 DPL<=CPL;对于非一致性代码段只要求 DPL=CPL
  15. 如果调用者与调用门之间的特权级检查成功通过,CPU 就会接着把调用者的 CPL 与代码段描述符的DPL 进行比较检查。在这方面,CALL 指令和 JMP 指令的检查规则就不同了。只有 CALL 指令可以通过调用门把程序控制转移到特权级更高的非一致性代码段中,即可以转移到 DPL 小于 CPL 的非一致性代码段中去执行。而 JMP 指令只能通过调用门把控制转移到 DPL 等于 CPL 的非一致性代码段中。但 CALL 指令和 JMP 指令都可以把控制转移到更高特权级的一致性代码段中,即转移到 DPL 小于或等于 CPL 的一致 性代码段中。

  16. 如果一个调用把控制转移到了更高特权级的非一致性代码段中,那么 CPL 就会被设置为目的代码段的DPL 值,并且会引起堆栈切换。但是如果一个调用或跳转把控制转移到更高级别的一致性代码段上,那么CPL 并不会改变,并且也不会引起堆栈切换。

  17. 调用门可以让一个代码段中的过程被不同特权级的程序访问。例如,位于一个代码段中的操作系统代码可能含有操作系统自身和应用软件都允许访问的代码(比如处理字符 I/O 的代码)。因此可以为这些过程设置一个所有特权级代码都能访问的调用门。另外可以专门为仅用于操作系统的代码设置一些更高特权级的调用门。

  18. 堆栈切换

  19. 每当调用门用于把程序控制转移到一个更高级别的非一致性代码段时,CPU 会自动切换到目的代码段特权级的堆栈去。执行栈切换操作的目的是为了防止高特权级程序由于栈空间不足而引起崩溃,同时也为了防止低特权级程序通过共享的堆栈有意或无意地干扰高特权级的程序。

  20. 每个任务必须定义最多 4 个栈。一个用于运行在特权级 3 的应用程序代码,其他分别用于用到的特权级 2、1 和 0。如果一个系统中只使用了 3 和 0 两个特权级,那么每个任务就只需设置两个栈。每个栈都位于不同的段中,并且使用段选择符和段中偏移值指定。

  21. 当特权级 3 的程序在执行时,特权级 3 的堆栈的段选择符和栈指针会被分别存放在 SS 和 ESP 中,并且在发生堆栈切换时被保存在被调用过程的堆栈上

  22. 特权级 0、1 和 2 的堆栈的初始指针值都存放在当前运行任务的 TSS 段中。TSS 段中这些指针都是只读值。在任务运行时 CPU 并不会修改它们。当调用更高特权级程序时,CPU 才用它们来建立新堆栈。当从调用过程返回时,相应栈就不存在了。下一次再调用该过程时,就又会再次使用 TSS 中的初始指针值建立一个新栈。

  23. 操作系统需要负责为所有用到的特权级建立堆栈和堆栈段描述符,并且在任务的 TSS 中设置初始指针值。每个栈必须可读可写,并且具有足够的空间来存放以下一些信息:

     调用过程的 SS、ESP、CS 和 EIP 寄存器内容;  被调用过程的参数和临时变量所需使用的空间。  当隐含调用一个异常或中断过程时标志寄存器 EFLAGS 和出错码使用的空间

  24. 当通过调用门执行一个过程调用而造成特权级改变时,CPU 就会执行以下步骤切换堆栈并开始在新的特权级上执行被调用过程

    1) 使用目的代码段的 DPL(即新的 CPL)从 TSS 中选择新栈的指针。从当前 TSS 中读取新栈的段选择符和栈指针。在读取栈段选择符、栈指针或栈段描述符过程中,任何违反段界限的错误都将导致产生一个无效 TSS 异常

    2) 检查栈段描述符特权级和类型是否有效,若无效者同样产生一个无效 TSS 异常。

    3) 临时保存 SS 和 ESP 寄存器的当前值,把新栈的段选择符和栈指针加载到 SS 和 ESP 中。然后把临时保存的 SS 和 ESP 内容压入新栈中。

    4) 把调用门描述符中指定参数个数的参数从调用过程栈复制到新栈中。调用门中参数个数值最大为31,如果个数为 0,则表示无参数,不需复制。

    5) 把返回指令指针(即当前 CS 和 EIP 内容)压入新栈。把新(目的)代码段选择符加载到 CS 中,同时把调用门中偏移值(新指令指针)加载到 EIP 中。最后开始执行被调用过程。

  25. 从被调用过程返回

  26. 指令 RET 用于执行近返回(near return)、同特权级远返回(far return)和不同特权级的远返回。该指令用于从使用 CALL 指令调用的过程中返回。近返回仅在当前代码段中转移程序控制权,因此 CPU 仅进行界限检查。对于相同特权级的远返回,CPU 同时从堆栈中弹出返回代码段的选择符和返回指令指针。由于通常情况下这两个指针是 CALL 指令压入栈中的,因此它们因该是有效的。但是 CPU 还是会执行特权级检查以应付当前过程可能修改指针值或者堆栈出现问题时的情况。

  27. 会发生特权级改变的远返回仅允许返回到低特权级程序中,即返回到的代码段 DPL 在数值上要大于CPL。CPU 会使用 CS 寄存器中选择符的 RPL 字段来确定是否要求返回到低特权级。如果 RPL 的数值要比 CPL 大,就会执行特权级之间的返回操作。当执行远返回到一个调用过程时,CPU 会执行以下步骤: 1) 检查保存的 CS 寄存器中 RPL 字段值,以确定在返回时特权级是否需要改变。 2) 弹出并使用被调用过程堆栈上的值加载 CS 和 EIP 寄存器。在此过程中会对代码段描述符和代码 段选择符的 RPL 进行特权级与类型检查。 3) 如果 RET 指令包含一个参数个数操作数并且返回操作会改变特权级,那么就在弹出栈中 CS 和 EIP 值之后把参数个数值加到 ESP 寄存器值中,以跳过(丢弃)被调用者栈上的参数。此时 ESP 寄存 器指向原来保存的调用者堆栈的指针 SS 和 ESP。 4) 把保存的 SS 和 ESP 值加载到 SS 和 ESP 寄存器中,从而切换回调用者的堆栈。而此时被调用者堆栈的 SS 和 ESP 值被抛弃。 5) 如果 RET 指令包含一个参数个数操作数,则把参数个数值加到 ESP 寄存器值中,以跳过(丢弃)调用者栈上的参数。 6) 检查段寄存器 DS、ES、FS 和 GS 的内容。如果其中有指向 DPL 小于新 CPL 的段(一致代码段除外),那么 CPU 就会用 NULL 选择符加载加载这个段寄存器。
页级保护
  • 页目录和页表表项中的读写标志R/W和用户/超级用户标志U/S提供了分段机制保护属性的一个子集

  • 页机制只识别两级权限。特权级 0、1 和 2 被归类为超级用户级,而特权级 3 被作为普通用户级

  • 超级用户级的页面对于超级用户来讲总是可读/可写/可执行的,但普通用户不可访问

  • 对于分段机制,在最外层用户级执行的程序只能访问用户级的页面,但是在任何超级用户层(0、1、2)执行的程序不仅可以访问用户层的页面,也可以访问超级用户层的页面

  • 与分段机制不同的是,在内层超级用户级执行的程序对任何页面都具有可读/可写/可执行权限,包括那些在用户级标注为只读/可执行的页面

U/S R/W 用户允许的访问 超级用户允许的访问
0 0 读/写/执行
0 1 读/写/执行
1 0 读/执行 读/写/执行
1 1 读/写/执行 读/写/执行
  • 页级保护是在分段机制提供的保护措施之后发挥作用

  • 为了避免每次内存应用都要访问驻留内存的页表,从而加快速度,最近使用的线性到物理地址的转换信息被保存在处理器内的页转换高速缓冲中

  • 处理器在访问内存中的页表之前会首先利用缓冲中的信息。只有当必要的转换信息不在高速缓冲中时,处理器才会搜寻内存中的页目录和页表

  • 页转换高速缓冲作用类似于前面描述的加速段转换的段寄存器的影子描述符寄存器。页转换高速缓冲的另一个术语称为转换查找缓冲 TLB(Translation Lookaside Buffer)

  • 80X86 处理器并没有维护页转换高速缓冲和页表中数据的相关性,但是需要操作系统软件来确保它们一致。即处理器并不知道什么时候页表被软件修改过了。因此操作系统必须在改动过页表之后刷新高速缓冲以确保两者一致。通过简单地重新加载寄存器 CR3,我们就可以完成对高速缓冲的刷新操作

  • 有一种特殊情况,在这种情况下修改页表项不需要刷新页转换高速缓冲。也即当不存在页面的表项被修改时,即使是把 P 标志从 0 改成 1 来标注表项对页转换有效,也不需要刷新高速缓冲。因为无效的表项不会被存入高速缓冲中。所以在把一个页面从磁盘调入内存以使页面存在时,我们不需要刷新页转换高速缓冲

中断和异常处理
  • 中断(Interrupt)和异常(Exception)是指明系统、处理器或当前执行程序(或任务)的某处出现一个事件,该事件需要处理器进行处理

  • 这种事件会导致执行控制被强迫从当前运行程序转移到被称为中断处理程序(interrupt handler)或异常处理程序(exception handler)的特殊软件函数或任务中。处理器响应中断或异常所采取的行动被称为中断/异常服务(处理)。

  • 中断发生在程序执行的随机时刻,以响应硬件发出的信号,软件也能通过执行 INT n 指令产生中断

  • 对应用程序和操作系统来说,80X86 的中断和异常处理机制可以透明地处理发生的中断和异常事件。当收到一个中断或检测到一个异常时,处理器会自动地把当前正在执行的程序或任务挂起,并开始运行中断或异常处理程序。当处理程序执行完毕,处理器就会恢复并继续执行被中断的程序或任务。被中断程序的恢复过程并不会失去程序执行的连贯性,除非从异常中恢复是不可能的或者中断导致当前运行程序被终止。

  • 为了有助于处理异常和中断,每个需要被处理器进行特殊处理的处理器定义的异常和中断条件都被赋予了一个标识号,称为向量(vector)

  • 处理器把赋予异常或中断的向量用作中断描述符表 IDT(Interrupt Descriptor Table)中的一个索引号,来定位一个异常或中断的处理程序入口点位置

  • 允许的向量号范围是 0 到 255

  • 范围在 32 到 255 的向量号用于用户定义的中断

  • 中断源

处理器从两种地方接收中断:  外部(硬件产生)的中断;  软件产生的中断。

外部中断通过处理器芯片上两个引脚(INTR 和 NMI)接收。当引脚 INTR 接收到外部发生的中断信号时,处理器就会从系统总线上读取外部中段控制器(例如 8259A)提供的中断向量号。当引脚 NMI 接收到信号时,就产生一个非屏蔽中断。它使用固定的中断向量号 2。任何通过处理器 INTR 引脚接收的外部中断都被称为可屏蔽硬件中断,包括中断向量号 0 到 255。标志寄存器 EFLAGS 中的 IF 标志可用来屏蔽所有这些硬件中断。

通过在指令操作数中提供中断向量号,INT n 指令可用于从软件中产生中断。

EFLAGS 中的 IF 标志不能够屏蔽使用 INT 指令从软件中产生的中断

  • 异常源

处理器接收的异常也有两个来源:  处理器检测到的程序错误异常;  软件产生的异常。

在应用程序或操作系统执行期间,如果处理器检测到程序错误,就会产生一个或多个异常。80X86 处理器为其检测到的每个异常定义了一个向量。异常可以被细分为故障(faults)、陷阱(traps)和中止(aborts)

Fault 是一种通常可以被纠正的异常,并且一旦被纠正程序就可以继续运行

Trap 是一个引起陷阱的指令被执行后立刻会报告的异常

Abort 是一种不会总是报告导致异常的指令的精确位置的异常,并且不允许导致异常的程序重新继续执行。Abort 用于报告严重错误,例如硬件错误以及系统表中存在不一致性或非法值。

  • 为了让程序或任务在一个异常或中断处理完之后能重新恢复执行,除了中止(Abort)之外的所有异常都能报告精确的指令位置,并且所有中断保证是在指令边界上发生

  • 标志寄存器 EFLAGS 的中断允许标志 IF(Interrupt enable Flag)能够禁止为处理器 INTR 引脚上收到的可屏蔽硬件中断提供服务。当 IF=0 时,处理器禁止发送到 INTR 引脚的中断;当 IF=1 时,则发送到 INTR引脚的中断信号会被处理器进行处理。

  • IF 标志并不影响发送到 NMI 引脚的非屏蔽中断,也不影响处理器产生的异常。如同 EFLAGS 中的其他标志一样,处理器在响应硬件复位操作时会清除 IF 标志(IF=0)。

  • IF 标志可以使用指令 STI 和 CLI 来设置或清除。只有当程序的 CPL<=IOPL 时才可执行这两条指令,否则将引发一般保护性异常。IF 标志也会受一下操作影响

 PUSHF 指令会把 EFLAGS 内容存入堆栈中,并且可以在那里被修改。而 POPF 指令可用于把已被修改过的标志内容放入 EFLAGS 寄存器中。  任务切换、POPF 和 IRET 指令会加载 EFLAGS 寄存器。因此,它们可用来修改 IF 标志。  当通过中断门处理一个中断时,IF 标志会被自动清除(复位),从而会禁止可屏蔽硬件中断。但如果是通过陷阱门来处理一个中断,则 IF 标志不会被复位

  • 如果在一条指令边界有多个异常或中断等待处理时,处理器会按规定的次序对它们进行处理。
优先级 说明
1 最高 硬件复位:RESET
2 任务切换陷阱:TSS 中设置了 T 标志
3 外部硬件介入
4 前一指令陷阱:断点、调试陷阱异常
5 外部中断:NMI 中断、可屏蔽硬件中断
6 代码断点错误
7 取下一条指令错误:违反代码段限长、代码页错误
8 下一条指令译码错误:指令长度>15 字节、无效操作码、协处理器不存在
9 最低 执行指令错误:溢出、边界检查、无效 TSS、段不存在、堆栈错、一般保护、数据页、对齐检查、浮点异常
  • 中断描述符表

  • 中断描述符表 IDT(Interrupt Descriptor Table)将每个异常或中断向量分别与它们的处理过程联系起来

  • IDT 也是由 8 字节长描述符组成的一个数组

  • 表中第 1 项可以包含描述符

  • 为了构成 IDT 表中的一个索引值,处理器把异常或中断的向量号*8

  • 为最多只有 256 个中断或异常向量,所以 IDT 无需包含多于 256 个描述符

  • 所有空描述符项应该设置其存在位(标志)为 0

  • IDT 表可以驻留在线性地址空间的任何地方,处理器使用 IDTR 寄存器来定位 IDT 表的位置。这个寄存器中含有 IDT 表 32 位的基地址和 16 位的长度(限长)值

    47~16 15~0
    IDT 基地址 IDT 限长值
  • IDT 表中可以存放三种类型的门描述符

     中断门(Interrupt gate)描述符  陷阱门(Trap gate)描述符  任务门(Task gate)描述符

  • 处理器对异常和中断处理过程的调用操作方法与使用 CALL 指令调用程序过程和任务的方法类似

  • 如果索引值指向中断门或陷阱门,则处理器使用与 CALL 指令操作调用门类似的方法调用异常或中断处理过程

  • 如果索引值指向任务门,则处理器使用与 CALL 指令操作任务门类似的方法进行任务切换,执行异常或中断的处理任务

  • 当处理器执行异常或中断处理过程调用时会进行以下操作:

     如果处理过程将在高特权级(例如 0 级)上执行时就会发生堆栈切换操作。堆栈切换过程如下:  处理器从当前执行任务的 TSS 段中得到中断或异常处理过程使用的堆栈的段选择符和栈指针(例如 tss.ss0、tss.esp0)。然后处理器会把被中断程序(或任务)的栈选择符和栈指针压入新栈中,见图 4-29 所示。  接着处理器会把 EFLAGS、CS 和 EIP 寄存器的当前值也压入新栈中。  如果异常会产生一个错误号,那么该错误号也会被最后压入新栈中。

    如果处理过程将在被中断任务同一个特权级上运行,那么:

    ​ 处理器把 EFLAGS、CS 和 EIP 寄存器的当前值保存在当前堆栈上。

    ​ 如果异常会产生一个错误号,那么该错误号也会被最后压入新栈中。

  • 为了从中断处理过程中返回,处理过程必须使用 IRET 指令。IRET 指令与 RET 指令类似,但 IRET 还会把保存的寄存器内容恢复到 EFLAGS 中。不过只有当 CPL 是 0 时才会恢复 EFLAGS 中的 IOPL 字段,并且只有当 CPL<=IOPL 时,IF 标志才会被改变。 如果当调用中断处理过程时发生了堆栈切换,那么在返回时 IRET 指令会切换回到原来的堆栈。

  • 异常和中断处理过程的特权级保护机制与通过调用门调用普通过程类似。处理器不允许把控制转移到比 CPL 更低特权级代码段的中断处理过程中,否则将产生一个一般保护性异常。

    ​ 因为中断和异常向量没有 RPL,因此在隐式调用异常和中断处理过程时不会检查 RPL。

    ​ 只有当一个异常或中断是利用使用 INT n、INT 3 或 INTO 指令产生时,处理器才会检查中断或陷阱门中的 DPL。此时 CPL 必须小于等于门的 DPL

  • 因为异常和中断通常不会定期发生,因此这些有关特权级的规则有效地增强了异常和中断处理过程能 够运行的特权级限制。我们可以利用以下技术之一来避免违反特权级保护:  异常或中断处理程序可以存放在一个一致性代码段中。这个技术可以用于只需访问堆栈上数据的 处理过程(例如,除出错异常)。如果处理程序需要数据段中的数据,那么特权级 3 必须能够访 问这个数据段。但这样一来就没有保护可言了。  处理过程可以放在具有特权级 0 的非一致代码段中。这种处理过程总是可以执行的,而不管被中 断程序或任务的当前特权级 CPL

  • 异常或中断处理过程的标志使用方式

    当通过中断门或陷阱门访问一个异常或中断处理过程时,处理器会在把 EFLAGS 寄存器内容保存到堆栈上之后清除 EFLAGS 中的 TF 标志。清除 TF 标志可以防止指令跟踪影响中断响应。而随后的 IRET 指令会用堆栈上的内容恢复 EFLAGS 的原 TF 标志。 中断门与陷阱门唯一的区别在于处理器操作 EFLAGS 寄存器 IF 标志的方法。当通过中断门访问一个异常或中断处理过程时,处理器会复位 IF 标志以防止其他中断干扰当前中断处理过程。随后的 IRET 指令则会用保存在堆栈上的内容恢复 EFLAGS 寄存器的 IF 标志。而通过陷阱门访问处理过程并不会影响 IF 标 志

  • 执行中断处理过程的任务

    当通过 IDT 表中任务门访问异常或中断处理过程时,就会导致任务切换。从而可以在一个专用任务中执行中断或异常处理过程。IDT 表中的任务门引用 GDT 中的 TSS 描述符。切换到处理过程任务的方法与普通任务切换一样。由于本书讨论的 Linux 操作系统没有使用这种中断处理方式,因此这里不再赘述

  • 中断处理任务

  • 当通过 IDT 中任务门来访问异常或中断处理过程时就会导致任务切换

     被中断程序或任务的完整上下文会被自动保存;  在处理异常或中断时,新的 TSS 可以允许处理过程使用新特权级 0 的堆栈。在当前特权级 0 的堆栈已毁坏时如果发生了一个异常或中断,那么在为中断过程提供一个新特权级 0 的堆栈条件下,通过任务门访问中断处理过程能够防止系统崩溃;  通过使用单独的 LDT 给中断或异常处理任务独立的地址空间,可以把它与其他任务隔离开来。

  • 使用独立任务处理异常或中断的不足之处是:在任务切换时必须对大量机器状态进行保存,使得它比使用中断门的响应速度要慢,导致中断延时增加

  • 错误码

  • 当异常条件与一个特定的段相关时,处理器会把一个错误码压入异常处理过程的堆栈上

    31~16 15~3 2 1 0
    保留 段选择符索引 TI IDT EXT
    名称 作用
    EXT(External event)标志 当置位时,表示执行程序以外的事件造成了异常
    描述符位置 IDT(Descriptor location)标志 当该位置位时,表示错误码的索引部分指向IDT 中的一个门描述符。当该位复位时,表示索引部分指向 GDT 或 LDT 中的一个段描述符
    GDT/LDT 表选择标志 TI 只有当位 1 的 IDT=0 才有用。当该 TI=1 时,表示错误码的索引部分指向 LDT 中的一个描述符。当 TI=0 时,说明错误码中的索引部分指向 GDT 表中的一个描述符
  • 页故障(Page-fault)异常的错误码格式

    31~3 2 1 0
    保留 U/S R/W P
    名称 作用
    P 异常是由于页面不存在或违反访问特权而引发。P=0,表示页不存在;P=1 表示违反页级保护权限
    W/R 异常是由于内存读或写操作引起。W/R=0,表示由读操作引起;W/R=1,表示由写操作引起
    U/S 发生异常时 CPU 执行的代码级别。U/S=0,表示 CPU 正在执行超级用户代码;U/S=1,表示 CPU 正在执行一般用户代码

任务管理

  • 任务(Task)是处理器可以分配调度、执行和挂起的一个工作单元。它可用于执行程序、任务或进程、操作系统服务、中断或异常处理过程和内核代码

  • 80X86 提供了一种机制,这种机制可用来保存任务的状态、分派任务执行以及从一个任务切换到另一个任务

  • 当工作在保护模式下,处理器所有运行都在任务中。即使是简单系统也必须起码定义一个任务

  • 80X86 提供了多任务的硬件支持

  • 通过中断、异常、跳转或调用,我们可以执行一个任务

  • 描述符表中与任务相关的描述符有两类:任务状态段描述符和任务门

  • 当执行权传给这任何一类描述符时,都会造成任务切换

  • 任务切换很象过程调用,但任务切换会保存更多的处理器状态信息。

  • 这种转移操作要求保存处理器中几乎所有寄存器的当前内容,包括标志寄存器 EFLAGS 和所有段寄存器

  • 与过程不过,任务不可重入。任务切换不会把任何信息压入堆栈中,处理器的状态信息都被保存在内存中称为任务状态段(Task state segment)的数据结构中。

  • 一个任务由两部分构成:任务执行空间和任务状态段 TSS(Task-state segment)

任务执行空间包括代码段、堆栈段和一个或多个数据段

  • TSS 指定了构成任务执行空间的各个段,并且为任务状态信息提供存储空间

  • 所有这些调度任务执行的方法都会使用一个指向任务门或任务 TSS 段的选择符来确定一个任务。当使 用 CALL 或 JMP 指令调度一个任务时,指令中的选择符既可以直接选择任务的 TSS,也可以选择存放有 TSS 选择符的任务门。当调度一个任务来处理一个中断或异常时,那么 IDT 中该中断或异常表项必须是一 个任务门,并且其中含有中断或异常处理任务的 TSS 选择符。 当调度一个任务执行时,当前正在运行任务和调度任务之间会自动地发生任务切换操作。在任务切换 期间,当前运行任务的执行环境(称为任务的状态或上下文)会被保存到它的 TSS 中并且暂停该任务的执 行。此后新调度任务的上下文会被加载进处理器中,并且从加载的 EIP 指向的指令处开始执行新任务。 如果当前执行任务(调用者)调用了被调度的新任务(被调用者),那么调用者的 TSS 段选择符会被 保存在被调用者 TSS 中,从而提供了一个返回调用者的链接。对于所有 80X86 处理器,任务是不可递归 调用的,即任务不能调用或跳转到自己。 中断或异常可以通过切换到一个任务来进行处理。在这种情况下,处理器不仅能够执行任务切换来处 理中断或异常,而且也会在中断或异常处理任务返回时自动地切换回被中断的任务中去。这种操作方式可 以处理在中断任务执行时发生的中断。 作为任务切换操作的一部份,处理器也会切换到另一个 LDT,从而允许每个任务对基于 LDT 的段具 有不同逻辑到物理地址的映射。同时,页目录寄存器 CR3 也会在切换时被重新加载,因此每个任务可以有 自己的一套页表。这些保护措施能够用来隔绝各个任务并且防止它们相互干扰。 使用处理器的任务管理功能来处理多任务应用是任选的。我们也可以使用软件来实现多任务,使得每 个软件定义的任务在一个 80X86 体系结构的任务上下文中执行。TSS 也为任务之间的链接提供了处理方法

  • 一个任务使用指向其 TSS 的段选择符来指定。 当一个任务被加载进处理器中执行时,那么该任务的段选择符、基地址、段限长以及 TSS 段描述符属性就会被加载进任务寄存器 TR(Task Register)中

  • 如果使用了分页机制,那么任务使用的页目录表基地址就会被加载进控制寄存器 CR3 中

  • 当前执行任务的状态由处理器所有以下一些内容组成:

 所有通用寄存器和段寄存器信息;  标志寄存器 EFLAGS、程序指针 EIP、控制寄存器 CR3、任务寄存器和 LDTR 寄存器;  段寄存器指定的任务当前执行空间;  I/O 映射位图基地址和 I/O 位图信息(在 TSS 中);  特权级 0、1 和 2 的堆栈指针(在 TSS 中);  链接至前一个任务的链指针(在 TSS 中)

  • 软件或处理器可以使用以下方法之一来调度执行一个任务

 使用 CALL 指令明确地调用一个任务;  使用 JMP 指令明确地跳转到一个任务(Linux 内核使用的方式);  (由处理器)隐含地调用一个中断句柄处理任务;  隐含地调用一个异常句柄处理任务

所有这些调度任务执行的方法都会使用一个指向任务门或任务 TSS 段的选择符来确定一个任务。当使 用 CALL 或 JMP 指令调度一个任务时,指令中的选择符既可以直接选择任务的 TSS,也可以选择存放有 TSS 选择符的任务门。当调度一个任务来处理一个中断或异常时,那么 IDT 中该中断或异常表项必须是一 个任务门,并且其中含有中断或异常处理任务的 TSS 选择符。 当调度一个任务执行时,当前正在运行任务和调度任务之间会自动地发生任务切换操作。在任务切换 期间,当前运行任务的执行环境(称为任务的状态或上下文)会被保存到它的 TSS 中并且暂停该任务的执 行。此后新调度任务的上下文会被加载进处理器中,并且从加载的 EIP 指向的指令处开始执行新任务。 如果当前执行任务(调用者)调用了被调度的新任务(被调用者),那么调用者的 TSS 段选择符会被 保存在被调用者 TSS 中,从而提供了一个返回调用者的链接。对于所有 80X86 处理器,任务是不可递归 调用的,即任务不能调用或跳转到自己。 中断或异常可以通过切换到一个任务来进行处理。在这种情况下,处理器不仅能够执行任务切换来处 理中断或异常,而且也会在中断或异常处理任务返回时自动地切换回被中断的任务中去。这种操作方式可 以处理在中断任务执行时发生的中断。 作为任务切换操作的一部份,处理器也会切换到另一个 LDT,从而允许每个任务对基于 LDT 的段具 有不同逻辑到物理地址的映射。同时,页目录寄存器 CR3 也会在切换时被重新加载,因此每个任务可以有 自己的一套页表。这些保护措施能够用来隔绝各个任务并且防止它们相互干扰。 使用处理器的任务管理功能来处理多任务应用是任选的。我们也可以使用软件来实现多任务,使得每 个软件定义的任务在一个 80X86 体系结构的任务上下文中执行。

  • 任务管理数据结构

 任务状态段 TSS;  TSS 描述符;  任务寄存器 TR;  任务门描述符;  标志寄存器 EFLAGS 中的 NT 标志。

  1. 任务状态段

    用于恢复一个任务执行的处理器状态信息被保存在称为任务状态段 TSS(Task state segment)的段中

    . 动态字段。当任务切换而被挂起时,处理器会更新动态字段的内容。这些字段包括:  通用寄存器字段。用于保存 EAX、ECX、EDX、EBX、ESP、EBP、ESI 和 EDI 寄存器的内容。  段选择符字段。用于保存 ES、CS、SS、DS、FS 和 GS 段寄存器的内容。  标志寄存器 EFLAGS 字段。在切换之前保存 EFLAGS。  指令指针 EIP 字段。在切换之前保存 EIP 寄存器内容。  先前任务连接字段。含有前一个任务 TSS 段选择符(在调用、中断或异常激发的任务切换时更新)。 该字段(通常也称为后连接字段(Back link field))允许任务使用 IRET 指令切换到前一个任务

    静态字段。处理器会读取静态字段的内容,但通常不会改变它们。这些字段内容是在任务被创建时设 置的。这些字段有:

     LDT 段选择符字段。含有任务的 LDT 段的选择符。  CR3 控制寄存器字段。含有任务使用的页目录物理基地址。控制寄存器 CR3 通常也被称为页目 录基地址寄存器 PDBR(Page directory base register)。  特权级 0、1 和 2 的堆栈指针字段。这些堆栈指针由堆栈段选择符(SS0、SS1 和 SS2)和栈中偏 移量指针(ESP0、ESP1 和 ESP2)组成。注意,对于指定的一个任务,这些字段的值是不变的。 因此,如果任务中发生堆栈切换,寄存器 SS 和 ESP 的内容将会改变。  调试陷阱(Debug Trap)T 标志字段。该字段位于字节 0x64 比特 0 处。当设置了该位时,处理器 切换到该任务的操作将产生一个调试异常。  I/O 位图基地址字段。该字段含有从 TSS 段开始处到 I/O 许可位图处的 16 位偏移值。

    如果使用了分页机制,那么在任务切换期间应该避免处理器操作的 TSS 段中(前 104 字节中)含有内 存页边界

  2. TSS 描述符

  3. 任务寄存器

  4. 任务门描述符

  5. 任务切换

  6. 任务链

  7. 任务地址空间

    把任务映射到线性和物理地址空间

    任务逻辑地址空间

Debug用法

命令 作用
R 改变寄存器内容
D 查看内存中内容
E 改写内存中内容
U 翻译内存中机器指令成汇编指令
T 执行CS:IP指向的一条机器指令
A 以汇编指令格式在内存中写入一条机器指令

存储数据的尺寸

  • byte: 字节,8bit组成
  • word: 字,两个字节组成,分为高位和低位

地址加法器

物理地址 = 段地址 * 16 + 偏移地址

修改CS,IP的指令

  • jmp CS段地址:IP偏移地址
  • jmp 寄存器 (只修改IP寄存器内容)

DS寄存器

  • [n] 表示内存单元,n表示偏移地址

  • mov al, [n] 表示 ds * 16 + n 位置的数据读到al中

jmp

  • jmp short 标号 跳转8位位移,范围 -128 ~ 127

  • jmp near ptr 标号 跳转16位位移,范围 -32768 ~ 32767

  • jmp far ptr 标号 段间转移,远转移,修改CS和IP

  • jmp 16位寄存器 修改IP中的值

  • jmp word ptr 内存单元地址(段内转移) 从内存单元地址处读取一个字偏移地址

  • jmp dword ptr 内存单元地址(段间转移) 从内存单元地址处开始读取两个字,高地址处是目的段地址,低地址处是转移目的偏移地址

loop

  • 循环指令都是短转移,对IP修改范围-128 ~ 127

ret, retf

  • ret 用栈中数据,修改IP内容,近转移
  # ret指令执行相当于
  (IP)=((ss)*16+(sp))
  (sp)=(sp)+2
  cpu执行ret指令时,相当于进行
  pop IP
  • retf 用栈中数据,修改CS和IP内容,实现远转移
  # retf指令执行相当于
  (IP)=((ss)*16+(sp))
  (sp)=(sp)+2
  (CS)=((ss)*16+(sp))
  (sp)=(sp)+2
  cpu执行retf指令时,相当于进行
  pop IP
  pop CS

x86/x64

机械硬盘

  • 一个或者多个盘片串在同一个轴上,由电动机带着一起高速旋转
  • 每个盘片都有两个磁头(Head),上面一个,下面一个,经常用磁头指代盘面。磁头有编号,第1个盘片,上面为0,下面为1,第2个盘片,上面磁头编号为2,下面磁头编号为3,以此类推
  • 每个磁头不是单独移动,相反,都是固定在同一个支架上,所有盘片都是同进同退。
  • 盘片在高速旋转时,磁头都会画一个看不见的圆,这就是磁道(Track)。
  • 磁道时数据记录的轨迹,所有磁头都是联动的,故每个盘面上的同一条磁道又可以形成一个虚拟的圆柱,称为柱面(Cylinder)。
  • 磁道,或者柱面,也要编号。编号从盘面最边缘那条开始,向着圆心方向,从0开始编号。
  • 柱面是优化数据读写的概念。为了加速数据在硬盘上的读写,最好的办法就是尽量不移动磁头。这样,当 0 面的磁道不足以容纳要写入的数据时,应当把剩余的部分写在 1 面的同一磁道上。如果还写不下,那就继续把剩余的部分写在 2 面的同一磁道上。换句话说,在硬盘上,数据的访问是以柱面来组织的。
  • 磁道还要进一步划分为扇区(Sector),每条磁道能够划分为几个扇区,取决于磁盘的制造者,但通常为 63 个。扇区编号从1开始。
  • 扇区与扇区之间以间隙(空白)间隔开来,每个扇区以扇区头开始,然后是512字节的数据区。扇区头包含了每个扇区自己的信息,主要有本扇区的磁道号、磁头号和扇区号,用来供硬盘定位机构使用。现代的硬盘还会在扇区头部包括一个指示扇区是否健康的标志,以及用来替换该扇区的扇区地址。用于替换扇区的,是一些保留和隐藏的磁道
  • 硬盘的第一个扇区是 0 面 0 道 1 扇区,或者说是 0 头 0 柱 1 扇区,这个扇区称为主引导扇区。
  • bios读取硬盘主引导扇区内容,将他加载到内存0x0000:0x7c00处,然后jmp跳转到那里接着执行