有实模式、保护模式、长模式
真实分为两个方面:
总结来说就是,这个模式下直接往物理层写,而不是我们上一章说过的硬件抽象层
数据和指令都是放在内存中,如何寻址将数据装入寄存器?
所有内存地址得来的方式都是:段寄存器左移4位,再加上一个通用寄存器中的值形成地址。由这个地址去访问内存,着就是分段内存管理模型
注意:
代码段:由CS(段寄存器)和IP(程序指针)确定
栈段:由SS(段寄存器)和SP(栈针)确定
由此我们知道,不同程序执行,只需要分配不同的四个寄存器CS IP SS SP,就可以分配不同运行地址。
要点:
data SEGMENT ;定义一个数据段存放Hello World!
hello DB 'Hello World!$' ;注意要以$结束
data ENDS
code SEGMENT ;定义一个代码段存放程序指令
ASSUME CS:CODE,DS:DATA ;告诉汇编程序,DS指向数据段,CS指向代码段
start:
MOV AX,data ;将data段首地址赋值给AX
MOV DS,AX ;将AX赋值给DS,使DS指向data段
LEA DX,hello ;使DX指向hello首地址
MOV AH,09h ;给AH设置参数09H,AH是AX高8位,AL是AX低8位,其它类似
INT 21h ;执行DOS中断输出DS指向的DX指向的字符串hello
MOV AX,4C00h ;给AX设置参数4C00h
INT 21h ;调用4C00h号功能,结束程序
code ENDS
END start
中断是中止当前程序,立即跳转到另一个特定的地址上,以运行特定代码。
实模式下,它的实现过程:先保存CS和IP寄存器,然后装载新的CS和IP
注:IP只和CS一起使用,只能由处理器才能直接改变其值。开始一段代码的时候,CS指向代码段起始地址,IP段指向段内偏移。这样CS+IP就能形成逻辑地址,由总线接口变换成物理地址从而取得指令。处理器也会自动根据当前指令长度改变IP值,从而执行下一条指令。
中断如何产生?
中断如何实现?
在内存中存放一个中断向量表,这个表由CPU特定寄存器IDTR指向。在实模式下,中断向量表条目由代码段地址和段内偏移组成。
有了中断信号,CPU根据IDTR寄存器中信息,计算出中断行向量中的条目。然后装载CS和IP(即把代码段基地址和段内偏移装入),最终响应中断。
寻址问题是内存不断扩大最重要的问题。假设寄存器16位,那么最多只能标识2^16个地址。
我们说了实模式下CPU对指令不区分的执行,且对访问内存的地址不加限制。
鉴于上述两点,CPU还实现了保护模式。
相比于实模式,保护模式增加了很多32位的通用寄存器,这些寄存器可以单独使用低16位甚至低8位。
为了区分指令(如INT OUT CLI指令)和资源(寄存器,IO,内存地址)
保护模式下CPU实现了特权级
分为四级R0_R3。R0可以执行所有指令,数字越大,级别越低,执行的指令越少。即可认为R0拥有最大权力,可访问资源也最多。R0可以访问低级别的资源,反之不行。
16位的段寄存器肯定无法放入32位的段基址以及段内偏移。
所以把描述一个段的信息封装成了如下形式:
段描述符有64位,8B、
多个段描述符在内存中形成全局段描述符。该表的基地址和长度由CPU的GDTR寄存器指示。
段寄存器中不再存放基地址,而是具体段描述符的索引。
访问内存时,段寄存器中索引结合GDTR寄存器先找到内存中的段描述符,再根据这个描述符中的信息判断能否访问成功。
在保护模式中,段寄存器中存放的并不仅仅只有描述符索引,低4位
影子寄存器用于缓存具体段描述符的数据,是靠硬件来操作的,对系统程序员不可见,是硬件为了减少性能损耗而设计的一个段描述符的高速缓存,不然每次内存访问都要去内存中查表,那性能损失是巨大的,影子寄存器也正好是 64 位,里面存放了 8 字节段描述符数据。
权限组成: 现代操作系统都使用分页模型,但是x86cpu并不能直接分页,而是在分段的前提下,根据需要决定是否开启分页。 在实模式下,中断是这样实现的: 而在保护模式下,中断要做权限检查还要切换特权级别,所以要扩展中断向量表的信息,每个中断用中断门描述符来表示。 最后,CPU加载中断门描述符中目标代码段选择子到CS寄存器中把目标代码段偏移加载到EIP中。 x86CPU第一次加电和每次重启,都会自动进入实模式,保护模式需要程序员手动切换。 第二步,加载设置 GDTR 寄存器,使之指向全局段描述符表。 第三步,设置 CR0 寄存器,开启保护模式。 第四步,进行长跳转,加载 CS 段寄存器,即段选择子。 注:为什么要进行长跳转: 接下来,CPU 发现了 CRO 寄存器第 0 位的值是 1,就会按 GDTR 的指示找到全局描述符表,然后根据索引值 8,把新的段描述符信息加载到 CS 影子寄存器,当然这里的前提是进行一系列合法的检查。到此为止,CPU 真正进入了保护模式,CPU 也有了 32 位的处理能力。 长模式又名 AMD64,因为这个标准是 AMD 公司最早定义的,它使 CPU 在现有的基础上有了 64 位的处理能力,既能完成 64 位的数据运算,也能寻址 64 位的地址空间。这在大型计算机上犹为重要,因为它们的物理内存通常有几百 GB。 相比于保护模式,增加了一些通用寄存器,并扩展通用寄存器的位宽,所有的通用寄存器都是 64 位,还可以单独使用低 32 位。这个低 32 位可以拆分成一个低 16 位寄存器,低 16 位又可以拆分成两个 8 位寄存器,如下表。 然具备保护模式绝大多数特性,如特权级和权限检查。 当描述符中的 L=1,D/B=0 时,就是 64 位代码段,DPL 还是 0~3 的特权级。然后有多个段描述在内存中形成一个全局段描述符表,同样由 CPU 的 GDTR 寄存器指向。 段长度和段基址都是无效的填充为 0,CPU 不做检查。但是上面段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问。若是数据段的话,G、D/B、L 位都是无效的。 保护模式下为了实现对中断进行权限检查,实现了中断门描述符,在中断门描述符中存放了对应的段选择子和其段内偏移,还有 DPL 权限,如果权限检查通过,则用对应的段选择子和其段内偏移装载 CS:EIP 寄存器。 第一步,准备长模式全局段描述符表。 第二步,准备长模式下的 MMU 页表,这个是为了开启分页模式,切换到长模式必须要开启分页,想想看,长模式下已经不对段基址和段长度进行检查了,那么内存地址空间就得不到保护了。 加载 GDTR 寄存器,使之指向全局段描述表: 开启长模式,要同时开启保护模式和分页模式,在实现长模式时定义了 MSR 寄存器,需要用专用的指令 rdmsr、wrmsr 进行读写,IA32_EFER 寄存器的地址为 0xC0000080,它的第 8 位决定了是否开启长模式。 进行跳转,加载 CS 段寄存器,刷新其影子寄存器。 如果看的云里雾里,了解以下内容即可:
CS和SS中的RPL(Request Privilege Level )组成的当前权限级别(CPL),通常情况下RPL=CPL。进而 CPL 就表示发起访问者要以什么权限去访问目标段。
如果CPL大于目标段的DPL,CPU禁止访问,我们刚才说了,数字越大级别越低,权限越低。因此只有CPL保护模式平坦模型
保护模式的平坦模型,主要让分段变成“虚设”
因为CPU的32位的寄存器最多只能产生4GB大小的地址,所有段的段基址都设置为0,段的长度为0xFFFFF,每段4KB。这样所有段都指向同一个0-4GB的地址空间中。保护模式中断
在内存中存放一个中断向量表,这个表由CPU特定寄存器IDTR指向。在实模式下,中断向量表条目由代码段地址和段内偏移组成。
有了中断信号,CPU根据IDTR寄存器中信息,计算出中断行向量中的条目。然后装载CS和IP(即把代码段基地址和段内偏移装入),最终响应中断。(即保存当前CS和IP,运行新的CS IP。)
同实模式一样,保护模式中断在内存中也有中断向量表,同样是用IDTR寄存器指向。只不过中断向量表中的条目变成了中断门描述符。(实模式下中断向量表存放的是基址和偏移地址)
产生中断的时候,CPU 首先检查中断信号是否大于最后一个中断门描述符。然后检查描述符类型,以及是否在内存中。然后检查中断门描述符的段选择子 (就是段寄存器方的东西,指示段描述符索引通过和CIDR结合生成段描述符)指向的描述符。最后做权限检查,如果CPL小于DPL并大于中断门的段选择子就指向段描述符的DPL。
如果CPL等于中断门中的段选择子指向段描述符的DPL,则为同级权限,不进行栈切换。
否则进行栈切换:还要加载具体权限的SS ESP,也要筛查SS中段选择子指向的段描述符。切换到保护模式
第一步,准备全局段描述符表,代码如下
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1
GDTBASE dd GDT_START
lgdt [GDT_PTR]
;开启 PE
mov eax, cr0
bts eax, 0 ; CR0.PE =1
mov cr0, eax
jmp dword 0x8 :_32bits_mode ;_32bits_mode为32位代码标号即段偏移
因为不能直接mov数值到CS寄存器(汇编语言如此限制),所以不能直接给段寄存器赋值。长模式(AMD64)
长模式寄存器
长模式段描述符
长模式下CPU不再检查段基址和段长度,而只检查DPL。
长模式下的段描述符表,加深一下理解,如下所示.
ex64_GDT:
null_dsc: dq 0
;第一个段描述符CPU硬件规定必须为0
c64_dsc:dq 0x0020980000000000 ;64位代码段
;无效位填0
;D/B=0,L=1,AVL=0
;P=1,DPL=0,S=1
;T=1,C=0,R=0,A=0
d64_dsc:dq 0x0000920000000000 ;64位数据段
;无效位填0
;P=1,DPL=0,S=1
;T=0,C/E=0,R/W=1,A=0
eGdtLen equ $ - null_dsc ;GDT长度
eGdtPtr:dw eGdtLen - 1 ;GDT界限
dq ex64_GDT
长模式中断
中断门描述符,就会发现其中的段内偏移只有 32 位,但是长模式支持 64 位内存寻址,所以要对中断门描述符进行修改和扩展,下面我们就来看看长模式下的中断门描述符的格式,如下图所示。
首先为了支持 64 位寻址中断门描述符在原有基础上增加 8 字节,用于存放目标段偏移的高 32 位值。
其次,目标代码段选择子对应的代码段描述符必须是 64 位的代码段。
长模式也同样在内存中有一个中断门描述符表,只不过表中的条目(如上图所示)是 16 字节大小,最多支持 256 个中断源,对中断的响应和相关权限的检查和保护模式一样,这里不再赘述。切换到长模式
ex64_GDT:
null_dsc: dq 0
;第一个段描述符CPU硬件规定必须为0
c64_dsc:dq 0x0020980000000000 ;64位代码段
d64_dsc:dq 0x0000920000000000 ;64位数据段
eGdtLen equ $ - null_dsc ;GDT长度
eGdtPtr:dw eGdtLen - 1 ;GDT界限
dq ex64_GDT
mov eax, cr4
bts eax, 5 ;CR4.PAE = 1
mov cr4, eax ;开启 PAE
mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax
lgdt [eGdtPtr]
;开启 64位长模式
mov ecx, IA32_EFER
rdmsr
bts eax, 8 ;IA32_EFER.LME =1
wrmsr
;开启 保护模式和分页模式
mov eax, cr0
bts eax, 0 ;CR0.PE =1
bts eax, 31
mov cr0, eax
jmp 08:entry64 ;entry64为程序标号即64位偏移地址
总结