目录
本文是笔者学习《操作系统真象还原》的读书笔记,部分图表来自《操作系统真象还原》,若有侵权,请联系删除。笔者也是处于学习阶段,所陈述的内容如有错误,欢迎指正!
一、MBR中涉及的知识点
①段寄存器的初始化,不能通过“mov 段寄存器 立即数”,只能通过“mov 段寄存器 通用寄存器”和“mov 段寄存器 内存”
②由于显存被映射到了处理器的地址空间,我们可以使用普通的传送指令“mov”来读写
③在计算机中,每个用来显示在屏幕上的字符,都是一个二进制代码。一个字符对应两个字节。第一个字节存储字符的ASCII码,第二个字节存储字符属性
④往显存中写入一个字符时,可以使用该字符的ASCII码,也可以使用单引号围起来的字符。命令如下:mov byte [es:0x00] 'L'或者mov byte [es:0x00] 0x4c。目的操作数是内存地址,需要用中括号扣起来。由于目的操作数和原操作数无法确定单次操作的数据宽度,所以使用关键字“byte”来表此数据宽度是8bits。
⑤mov指令。mov指令用于数据传送,目的操作数的作用就像一个“容器”。目的操作数必须是通用寄存器或者内存单元,源操作数必须是和目的操作数具有相同的数据宽度的通用寄存器和内存单元,也可以是立即数。但是,不允许目的操作数与源操作数同时为内存单元。
⑦在程序中声明并初始化数据。DB、DW、DD、DQ并不是处理器指令,而是伪指令,是编译器提供的汇编指令。DB是声明字节,跟在它后面的操作数都占一个字节的长度,要声明超过一个以上的数据,各个操作数之间必须以逗号隔开。DW是声明字数据。DD声明双字数据。DW声明四字数据。
⑧除法
I、用16位的二进制数除以8位的二进制数。被除数必须在寄存器AX中,需事先传送到寄存器AX。除数可以由8位通用寄存器或者内存单元提供。指令执行后,商在AL中,余数在AH中。例如:div cl 和div byte [0x0023]
在一个源程序中,当除数由内存单元提供时,我们通常不知道汇编地址的具体数值,可以使用标号。例如:
dividnd dw 0x3f0
divisor db 0x3f
……
mov ax,[dividnd] ;当表示内存单元地址时,需要使用中括号括住标号或者地址
div byte [divisor]
II、用32位的二进制数除以16位的二进制数。被除数的高16位在DX中,低16位在AX中。除数可以由16位寄存器或者内存单元提供。商在AX中,余数在DX中。例如:div cx 和 div word [0x0023]
注意:若除数由内存单元提供,应该使用伪指令“byte”或者“word”说明除数的数据宽度。
⑨异或运算(xor)。xor指令的目的操作数可以是通用寄存器和内存单元,源操作数可以是通用寄存器、内存单元和立即数。注意,不允许目的、源操作数均为内存单元。一般而言,两个操作数应当有相同的数据宽度。xor运算是在两个操作数相对应的比特之间单独运行的。
⑩使程序进入无限不循环状态。对于处理器来说,取指令、执行是永不停歇的。当我们的程序运行完了代码部分后,应该设置一个无限循环,让程序悬停在此。例如: infi: jmp near infi
一、保护模式
1.1实模式的缺点
①实模式下,操作系统和用户程序属于同一特权级
②程序可以自由修改段基址,可以访问所有内存(通过vstart指定段基址)
③用户程序所引用的地址是真实的物理地址
④访问超过64KB的内存区域,就需要切换段基址
⑤一次只能运行一个程序
⑥20根地址线,1MB内存太小
实模式不是32位的CPU变成了16位。实模式的CPU运行环境是16位,保护模式的CPU运行环境是32位。在32位CPU出现之后,需要兼容原来16位的运行模式,所以,实模式其实是32位CPU运行在16位模式下。
1.2初见保护模式
1.2.1保护模式之寄存器扩展
除段寄存器外,通用寄存器、指令寄存器、标志寄存器都由原来的16位扩展到了32位。寄存器要保持向下兼容,必须在原有的基础上扩展,在各个寄存器原有16位的基础上,再次向高位扩展16位,变成了32位。高16位无法单独使用,只有在用到32位寄存器时,才有机会使用它们。
此时,段寄存器中已经不再是段基址,而是段选择子,也就是全局描述符中的段描述符的索引。
1.2.2保护模式之寻址方式扩展
在实模式下,基址寄存器只能是bx、bp,变址寄存器只能是si、di,寻址中偏移量不能超过16位。
在保护模式下,基址寄存器可以是所有32位的通用寄存器,变址寄存器可以是除了esp以外的所有通用寄存器,偏移量变成了32位。
1.2.3保护模式之运行方式反转
指令格式如下:
前缀 | 操作码 | 寻址方式、操作数类型 | 立即数 | 偏移量 |
我们使用的CPU运行模式有实模式和保护模式两种。实模式相当于8086的加强版,可以使用32位下的资源。但是,对于同一句汇编代码,它总该隶属于某种模式之下。编译器提供了伪指令bits,bits指令的格式是“[bits 16]”和“[bits 32]”,前者告诉编译器,下面的代码编译成16位的机器码,后者告诉编译器,下面的代码编译成32位的机器码。若未使用bits指令,默认是[bits 16]。
在指令格式不变的情况下,为了兼容,可以重新定义各寻址方式、寄存器的编码。由于保护模式中的寻址方式和操作数类型同实模式下完全不同,故对应的编码也不同。
在两种模式下,可能出现同样的操作码和操作数编码,却有着不同的解释,为了让CPU第一时间了解到是哪种模式下的指令,编译器在机器码的最前面加入了反转前缀,用来率先告诉CPU应该用哪种模式。
操作数反转前缀0x66、寻址方式反转前缀0x67。示例如下:
bits 16 ;告知编译器,以下代码编译成16位机器码
mov word [bx], 0x1234
mov word [eax], 0x1234 ;寻址方式反转,使用了保护模式下的eax作为基址寄存器
mov dword [eax], 0x1234 ;寻址方式反转,操作数反转,dword表示32bits,保护模式下操作数为32位
1.2.4保护模式之指令扩展
①add&&sub
add al, cl ;支持8位操作数
add ax, cx ;支持16位
add eax, ecx ;支持32位
sub al, cl ;支持8位操作数
sub ax, cx ;支持16位
sub eax, ecx ;支持32位
②inc&&dec
同时支持8、16、32位操作数
③mul&&div
;mul指令的被乘数是al(8 bits)或者ax(16 bits)或者eax(32 bits)。乘数在寄存器或者内存中
mul cl
mul byte [position_1] ;结果存入ax
mul cx
mul word [position_2] ;结果存入dx:ax
mul ecx
mul dword [position_3] ;结果存入edx:eax
;div指令的被除数是ax(16 bits)或者dx:ax(32 bits)或者ebx:eax(64 bits)。除数数在寄存器或者内存中
div cl
div byte [position_1] ;商在al中,余数在ah中
div cx
div word [position_2] ;商在ax中,余数在dx中
div ecx
div dword [position_3] ;商在eax中,余数在edx中
;以上的position_1、position_2、position_3是标号,由于只是用法说明,所以没在程序中写出来
④push
当压入的是立即数时:在实模式下,压入8、16位数,sp-2,压入32位数,sp-4。在保护模式下,压入16位数,sp-2,压入8、32位数,sp-4。
当压入的是寄存器时:对于段寄存器,按当前模式的默认操作数大小压入栈中。对于通用寄存器,无论哪种模式,压入16位数据,sp-2,压入32位数据,sp-4
当压入的是内存时:无论哪种模式,压入16位数据,sp-2,压入32位数据,sp-4(由伪指令确定)
二、全局描述符表
在保护模式中,内存段不再是用段寄存器加载一下段基址就能用了,段的信息增加了很多,需要提前定义好放入段描述符中,然后将段描述符放入全局描述符表中。全局描述符表是保护模式下内存段的登记表。
2.1段描述符
段描述符存放在内存中,占用8字节。其内部结构如下:
①TYPE字段
用来表示描述符的类型,需要配合S字段(决定当前段是否为系统段),此处,我们看非系统段
- A表示Accessed,由CPU设置,该段被CPU访问过,就会被CPU置为1,新段此位应为0
- C表示一致性代码段,是指自己是转移的目标段,并且自己是一致性代码段,自己的特权级一定高于当前特权级,转移后的特权级与转移前的低特权级一致。C=1表示该段为一致性代码段
- B表示是否可读,B=1为可读(是否可读是相对程序而言,并非CPU,只要给CPU地址,CPU就可以访问所有内存)
- X表示该段是否可执行,X=1表示可执行
- E标识段扩展的方向,E=0向上扩展,如代码段、数据段,E=1向下扩展,如栈段
- W标识段是否可写,W=1表示可写
②S字段
S=0表示系统段,S=1表示非系统段
③DPL字段
特权级字段,系统总共有四个特权级,0~3,0级特权最高。特权级是保护模式的产物
④P字段
表示该段是否在内存中,当内存不够时,可能将该段暂时先放在硬盘。P=1表示在内存中
⑤AVL字段
表示是否可用,是对用户来说的,操作系统可以随便用此位
⑥L字段
L=1表示64位代码段,L=0表示32位代码段。我们目前是在32位环境下编程
⑦D/B字段
用来指明有效地址(段内偏移地址)和操作数大小的。
对于代码段来说,此位是D位,D=0时,指令有效地址用IP寄存器,否则用EIP寄存器
对于栈段来说,此位是B位,B=0,使用sp,否则使用esp
⑧G字段
G=0,表示段界限的粒度为1字节;G=1,表示段界限的粒度为4KB
2.2全局描述符、选择子
“全局”体现在多个程序都可以在里面定义自己的段描述符,全局描述符表是公用的。全局描述符表在内存中,由专门的寄存器——gdtr指向它。gdtr的结构如下:
16~47 | 0~15 |
CDT内存起始地址 | GDT界限 |
需要访问gdtr,不能使用mov指令,而是要用lgdt。指令格式为“lgdt 48位内存数据”
段寄存器cs、ds、es……,在保护模式下,它们变身成为选择子,选择子内部结构如下:
3~15 | 2 | 0~1 |
描述符索引值 | TI(TI=0表示在DGT中) | RPL(请求特权级) |
即使在保护模式下,访问内存依然是“段寄存器:段内偏移地址”的形式,不过需要将段寄存器索引的段描述符表中的段基址与段内偏移地址相加,才能得到要访问的物理地址。
GDT中的第0个描述符是不可用的,因为定义在GDT中的段描述符是依靠选择子来访问的,如果选择子忘记初始化,选择子的值便会是0。为避免忘记初始化而选择到第0个描述符的情况,GDT中的第0个描述符不可用。
2.3进入保护模式
2.3.1打开A20地址线
在实模式下,访问内存是以“段基址:段内偏移地址”的形式访问的,当段基址*16与段内偏移地址之和,大于20位地址线时,会发生回绕,因为没有第20根地址线,产生的进位相当于丢失了。
我们现在的CPU有两种工作模式,现在的CPU有32位地址线,在实模式下只能访问20根地址线所包含的地址,是因为没有打开A20,在进入保护模式之前,需要打开A20地址线
;打开A20地址线如下
in al, 0x92 ;将端口0x92的内容读取到al中
or al 0000_0010b ;通过做“或”运算,将第二位置1
out 0x92, al ;将修改过的值写回端口0x92
2.3.1CR0寄存器的PE位
CR*是控制寄存器。控制寄存器是CPU的窗口,既可以用来展示CPU的内部状态,也可以用于控制CPU的运行机制。
本实验,需要用到的是CR0寄存器的第0位,即PE位。PE=1时,在实模式下运行,否则,运行于保护模式。
;将CR0寄存器的第0位设置为1
mov eax, cr0
or eax, 0x00000001
mov ro0, eax
三、用代码实现进入保护模式
前置知识
- 了解段描述符的内部结构
- 了解选择子的内部结构
- 了解gdtr的内部结构
- 了解文本模式显示适配器所在的内存空间
- 了解进入保护模式的流程
- 懂得在实模式在通过BIOS中断实现清屏和输出字符
- 懂得在保护模式通过直接操作显卡输出字符
小小疑问:为什么在loader.s中,在进入保护模式之后,要执行指令“jmp jmp dword SELECTOR_CODE:pro_mode_start”呢?读者可以参考《操作系统真相还原》P172页
实验内容:本实验,实现了从mbr到loader的权利交接,在mbr中实现输出“Real MBR”,在loader中,构造全局描述符表,实现了从实模式进入保护模式,在进入保护模式前输出“loader in real”,在进入保护模式后输出“P”。
本实验有三个文件,boot.inc、mbr.s和loader.s
实验源码:
boot.inc
;-------------------loader&&kernel-------------------
Loader_Base_Addr equ 0x900
Loader_Start_Sector equ 0x2
Loader_Sector_Cnt equ 0x4
;----------------提前设置好gdt的相关属性-----------------
DESC_G_4K equ 0x800000 ;段界限粒度为4K
DESC_D_32 equ 0x400000 ;代码段,指令中的有效地址及其操作数大小为32位
DESC_L equ 0x000000 ;32位代码段,置为0
DESC_AVL equ 0x000000 ;是否可用,置为0
DESC_CODE_LIMIT_16TO19 equ 0xf0000 ;代码段段界限的16~19位
DESC_DATA_LIMIT_16TO19 equ 0xf0000 ;数据段段界限的16~19位
DESC_VIDEO_LIMIT_16TO19 equ 0x00000 ;显存段段界限的16~19位
DESC_P equ 0x8000 ;存在位,置为1
DESC_DPL0 equ 0x0000 ;优先级为0
DESC_DPL1 equ 0x2000 ;优先级为1
DESC_DPL2 equ 0x4000 ;优先级为2
DESC_DPL3 equ 0x6000 ;优先级为3
DESC_S_NOTSYM equ 0x1000 ;当前描述符不是系统段
DESC_S_SYM equ 0x0000 ;当前描述副是系统段
DESC_CODE_TYPE equ 0x800 ;代码段TYPE字段描述
DESC_DATA_TYPE equ 0x200 ;数据段TYPE字段描述
MID_WEIGHT equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_P + DESC_DPL0 + DESC_S_NOTSYM
DESC_CODE_HIGH4 equ MID_WEIGHT + DESC_CODE_LIMIT_16TO19 + DESC_CODE_TYPE + 0x00
DESC_DATA_HIGH4 equ MID_WEIGHT + DESC_DATA_LIMIT_16TO19 + DESC_DATA_TYPE + 0x00
DESC_VIDEO_HIGH4 equ MID_WEIGHT + DESC_VIDEO_LIMIT_16TO19 + DESC_DATA_TYPE + 0x0b
;--------------------选择子属性----------------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
mbr.s
%include "boot.inc"
section mbr vstart=0x7c00
;initialize sreg
mov ax, cs
mov ds, ax
mov es, ax
mov fs, ax
mov sp, 0x7c00
;let gs point to video memory
mov ax, 0xb800
mov gs, ax
;;;;;;;;;;clear screen;;;;;;;;;;
;ah = function number:0x06-->ah = 0x06
;al = number of rows to roll(0 means all)
;bh = scroll line properties
;(cl,ch) = the (x,y) position in the bottom left corner of the window
;(dl,dh) = the (x,y) position in the top right corner of the window
mov ax, 0x600;
mov bx, 0x700
mov cx, 0
mov dx, 0x184f ;the default screen has 25row*80column
int 0x10
;output strings with red letters on green background through the graphics card
mov byte [gs:0x00], 'R'
mov byte [gs:0x01], 0xA4
mov byte [gs:0x02], 'e'
mov byte [gs:0x03], 0xA4
mov byte [gs:0x04], 'a'
mov byte [gs:0x05], 0xA4
mov byte [gs:0x06], 'l'
mov byte [gs:0x07], 0xA4
mov byte [gs:0x08], ' '
mov byte [gs:0x09], 0xA4
mov byte [gs:0x0a], 'M'
mov byte [gs:0x0b], 0xA4
mov byte [gs:0x0c], 'B'
mov byte [gs:0x0d], 0xA4
mov byte [gs:0x0e], 'R'
mov byte [gs:0x0f], 0xA4
mov eax, Loader_Start_Sector
mov bx, Loader_Base_Addr
mov cx, Loader_Sector_Cnt
call rd_disk
jmp Loader_Base_Addr ;let the program go to Loader
rd_disk:
;first: set the number of read sectors
mov dx, 0x1f2
mov di, ax
mov al, cl
out dx, al
mov ax, di
;second: set LBA's address
;write the 0 to 7 bits of the LBA's address through port 0x1f3
mov dx, 0x1f3
out dx, al
;write the 8 to 15 bits of the LBA's address through port 0x1f4
mov cl, 0x08
shr eax, cl
mov dx, 0x1f4
out dx, al
;write the 16 to 23 bits of the LBA's address through port 0x1f5
shr eax, cl
mov dx, 0x1f5
out dx, al
;write device through port 0x1f6
shr eax, cl
mov dx, 0x1f6
and al, 0x0f
or al, 0xe0
out dx, al
;third: the read command, 0x20, is written to port 0x1f7
mov dx, 0x1f7
mov al, 0x20
out dx, al
;fourth: check the status of the hard disk
.not_ready:
nop
in al, dx
and al, 0x88 ;only the 3rd and 7th positions will be focused here
cmp al, 0x08
jnz .not_ready
;fifth: read data from port 0x1f0
;count reads times, cx get read times
mov ax, Loader_Sector_Cnt
mov dx, 256
mul dx
mov cx, ax
mov dx, 0x1f0
.read:
in ax, dx
mov [bx], ax
add bx, 2
loop .read
ret ;return to "jmp Loader_Base_Addr"
times 510-($-$$) db 0 ;fill the program to 510 bytes size
dw 0xaa55 ;fill in the magic number
loader.s
%include"boot.inc"
section loader vstart=Loader_Base_Addr
Loader_Stack_Top equ Loader_Base_Addr
jmp loader_start
;构造DGT及其内部相关的段描述符表
;第0个段描述符(不可用)
GDT_BASE: dd 0x00000000
dd 0x00000000
;第1个段描述符
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
;第2个段描述符
DATA_EDSC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
;第3个段描述符
VIDEO_EDSC: dd 0x80000007 ;文本模式显示适配器的起始位置为0xb8000,空间大小为32KB,又因为段界限粒度为4K,所以,段界限为7
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
gdt_ptr dw GDT_LIMIT ;先准备好lgdt寄存器的值
dd GDT_BASE
times 60 dq 0 ;dq表示8个字节,此处预留了60个段描述符的空间
;准备好三个段的选择子
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
loadermsg db "loader in real"
loader_start:
;;;;;;;;;;print string;;;;;;;;;;
;ah = function number;0x13-->ah = 0x13
;al = the way string are written
;cx = string's length
;bh = the page number of the page used to display string(0)
;bl = string's properties
;(dh,dl) = position(row,column)
mov sp, Loader_Base_Addr
mov bp, loadermsg ;es:bp is the string's address
mov ax, 0x1301
mov cx, 14
mov bx, 0x001f
mov dx, 0x0100
int 0x10
;--------------准备进入保护模式-----------------
;打开A20地址线
in al, 0x92
or al, 0x02
out 0x92, al
;加载GDT
lgdt [gdt_ptr]
;cr0第0位置1
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
;刷新流水线并更新段描述符缓冲寄存器的内容
jmp dword SELECTOR_CODE:pro_mode_start
[bits 32]
pro_mode_start:
;初始化数据段、附加数据段es、栈段寄存器
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
;初始化栈顶指针
mov esp, Loader_Stack_Top
;初始化附加数据段gs
mov ax, SELECTOR_VIDEO
mov gs, ax
;在保护模式下,在显示屏第三行打下字符‘P’
mov byte [gs:320], 'P'
jmp $
编译阶段:
nasm -I include/ -o mbr.bin mbr.s
nasm -I include/ -o loader.bin loader.s
写入磁盘:
dd if=/home/oskiller/bochs/mylab/lab3/mbr.bin of=/home/oskiller/bochs/mylab/lab3/hd60M.img bs=512 count=1 conv=notrunc
dd if=/home/oskiller/bochs/mylab/lab3/loader.bin of=/home/oskiller/bochs/mylab/lab2/hd60M.img bs=512 count=4 seek=2 conv=notrunc
运行:
/home/oskiller/bochs/bin/bochs -f bochsrc.disk
运行结果:
分析结果:
以上第1、2张截图,是在mbr.bin和loader.bin执行之前,执行“info gdt”和“creg”两条命令的结果。gdt中没有信息,而cr0中的PE位是“0”。在执行程序之后,再次执行上述两条命令,可以看到全局描述符中只有四个段描述符,第一个段描述符因为没有使用,所以代码中并没有给它的相关属性赋值,余下的三个段描述符与boot.inc和loader.s中描述的一样,此时cr0的PE为“1”
四、处理器微架构
4.1流水线
假设CPU的指令执行过程分为n个步骤,那么一个时钟周期内CPU干了n件事,但是需要明确的是,这n件事不属于同一个指令,而是隶属于不同指令。
当CPU执行到一条无条件跳转指令时,此时已经有若干条指令进入流水线了,但是这若干条指令并不会用到了,所以,遇到无条件跳转指令,会清空流水线。
将一条指令的执行过程拆分成粒度更小的微操作,将会大大提高CPU的执行效率
4.2乱序执行
乱序执行,是指在CPU中运行的指令并不按照代码中的顺序执行,而是按照一定策略打乱顺序执行,也许后面的指令先执行,当然,需要保证指令之间不具备相关性。
例如:mov eax, [0x1234]
add eax, ebx
这两条指令并不能够乱序执行,因为第二条指令中的eax需要等第一条指令把eax的值更新,否则,将会发生错误([0x1234]处的内容与eax中的内容相同除外)
以下例子可以乱序执行:
mov eax, [0x1234]
push eax
call function
第一步中访问内存速度较慢,可以在寻址等待过程中做其他事情
第二步的push可以拆分为“sub esp, 4”和“mov [esp], eax”
第三步的call函数调用,需要在栈中压入返回地址,所以说call指令需要栈指针。由于第二步中执行了“sub esp, 4”,可以让CPU知道esp的最新值,不用等“mov [esp], eax”执行,就可以执行第3步了
4.3缓存
缓存的原理是用一些存取速度较快的存储设备作为数据缓冲区,避免频繁访问速度较慢的低速存储设备。可以解决速度不匹配问题,也可以解决数据粒度不一致问题。
CPU中有一级、二级甚至三级缓存,它们都是SRAM,是速度最快的存储器了,与寄存器一样,都是触发器做的。
我们可以根据程序的局部性原理采取缓存策略。一方面是时间局部性原理,即最近访问过的指令和数据,在将来一段时间内依然经常被访问;另一方面是空间局部性原理,即靠近当前访问内存空间的内存地址,在将来一段时间也会被访问。
4.4分支预测
在高级语言中的if、switch、for、while等语言结构,编译器将其转化为汇编代码后,在汇编一级,这些结构都是用跳转指令来实现的。
如果程序一直是顺序执行,那么无论多少条指令都可以提前放到流水线上,不会出现错误,但是程序中不乏分支的存在。
预测是针对有条件跳转的,最简单的方法是2位预测法,具体方法,读者有兴趣可以自行查阅资料。在分支预测中,难免会出现预测错误的情况,此时只需将流水线清空再把正确分支上的指令加入到流水线上即可。
五、保护模式之内存段的保护
5.1向段寄存器加载选择子时的保护
处理器先检查TI的值,因为要确定与GDT还是LDT做比较,再根据选择子的值验证段描述符是否超越界限。检查完选择子之后,没有越界则继续检查type字段,具备可执行的代码段才能加载到CS,代码段只能加载到CS,具备可写属性的数据段才能加载到SS,至少具备可读属性的段才能加载到DS、ES、FS、GS中。检查完type后,检查段是否存在,若存在,即可将选择子加载到段寄存器了,同时段描述符缓冲寄存器也会被更新,随后处理器将A置为1。
5.2代码段和数据段的保护
代码段与数据段是向上扩展的,在进行地址检查时,检查方式是“当前EIP + 数据/指令长度 < 实际的段界限”
5.3栈段的保护
栈段是向下扩展的,栈底的地址一定高于或者等于栈顶的地址,但是注意,栈的界限并不是负数,我们可以认为是以栈界限为基准,在进行地址检查时,检查方式为“实际界限值 < ss:esp <= 栈底”