Assembly学习记录
¶实模式+8086阶段
¶实模式内存布局
起始 | 结束 | 大小 | 用途 |
---|---|---|---|
FFFF0 | FFFFF | 16B | BIOS入口地址,此地址也属于BIOS代码,同样属于顶部的640KB字节。只是为了强调其入口地址才单独i特出来。此处16字节的内容时跳转指令jmp f000:e05b |
F0000 | FFFEF | 64KB-16B | 系统BIOS范围是F0000~FFFFF共640KB,为说明入口地址,将最上面的16字节从此处去掉,所以此处终止地址是0xFFFEF |
C8000 | EFFFF | 160KB | 映射硬件适配器的ROM或内存映射式I/O |
C0000 | C7FFF | 32KB | 显示适配器BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB | 用于黑白显示适配器 |
A0000 | AFFFF | 64KB | 用于彩色显示适配器 |
9FC00 | 9FFFF | 1KB | EBDA(Extended BIOS Area)扩展BIOS数据区 |
07E00 | 9FBFF | 622080B约608KB | 可用区域 |
07C00 | 07DFF | 512B | MBR被BIOS加载到此处,共512字节 |
00500 | 07BFF | 30464B约30KB | 可用区域 |
00400 | 004FF | 256B | BIOS Data Area(BIOS数据区) |
00000 | 003FF | 1KB | Interrupt Vector Table(中断向量表) |
- 逻辑地址和物理地址
- 00000~FFFFF中最低位字节位0时,就可以作为段基地址
- 物理地址=段基地址>>4 + 偏移地址(4个字节)
- 每个段的大小最大位64K,即65536个字节(偏移地址4个字节表示最大值)
- 实际物理地址最大值:10FFEF(段地址:FFFF0+偏移地址:FFFF),当受限与20根地址线(超过了20个bit),最大只能访问到FFFFF
¶寄存器
¶通用寄存器
- AH&AL=AX(accumulator):累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据.
- BH&BL=BX(base):基址寄存器,常用于地址索引,用来提供数据访问,使用bx提供偏移地址时,段寄存器默认使用(指定使用需要加段超越前缀)ds提供段地址(bp默认访问栈段,bx默认访问数据段)
- CH&CL=CX(count):计数寄存器,常用于计数;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器.
- DH&DL=DX(data):数据寄存器,用来和外设之间进行数据传输
- BP(Base Pointer)基址寄存器,用来提供访问栈段,使用bx提供偏移地址时,段寄存器默认使用ss提供段地址(bp默认访问栈段,bx默认访问数据段)
- SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置;ss+sp=栈地址)【压栈操作:高地址(栈底)->低地址(栈顶)(8086每次压栈和出栈都是16字节)】
- SI(Source Index):源索引寄存器(或变址寄存器)(用法见movsb),si提供偏移地址时,段寄存器默认使用ds提供段地址
- DI(Destination Index):目标索引寄存器(或变址寄存器)(用法见movsb),di提供偏移地址时,段寄存器默认使用ds提供段地址
¶指针寄存器
- 指令指针IP(Instruction Pointer):指令指针IP是一个16位专用寄存器,它指向当前需要取出的指令字节,当BIU从内存中取出一个指令字节后,IP就自动加1,指向下一个指令字节。注意,IP指向的是指令地址的段内地址偏移量,又称偏移地址(Offset Address)或有效地址(EA,Effective Address)。
¶段寄存器
两个段寄存器之间不能直接传送数据,需要借助通用寄存器
- CS: 代码段寄存器(Code Segment Register) ,其值为代码段的段值;
- DS : 数据段寄存器(Data Segment Register) ,其值为数据段的段值;段内访问地址默认位ds寄存器(不指定的情况下)(段内地址位偏移地址)
- ES: 附加段寄存器(Extra Segment Register) ,其值为附加数据段的段值;
- SS: 堆栈段寄存器(Stack Segment Register) ,其值为堆栈段的段值;
¶标志寄存器
- FLAGS寄存器
- bit0(CF):进位标志,当一个算术在结果最高位产生进位或者借位时,标志为1,否则为0
- 若al = 1000 0000则
add al, al
CF = 1
- 若al = 1000 0000则
- bit2(PF):奇偶标志,当算术操作结果在低8位中有偶数个“1”时,此标志位1,反之则为0
- bit4(AF):辅助进位标志,当一个算术操作在结果的位3(bit3)产生进位或者借位时,此为1,反则0
- bit6(ZF):零标志,用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
- bit7(SF):符号位,用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。
- bit8(TF):跟踪标志。该标志可用于程序调试。TF标志没有专门的指令来设置或清楚。如果TF=1,则CPU处于单步执行指令的工作方式,此时每执行完一条指令,就显示CPU内各个寄存器的当前值及CPU将要执行的下一条指令。如果TF=0,则处于连续工作模式。
- bit9(IF):中断标志,中断允许标志IF位用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
- bit10(DF):方向标志,用来决定在串操作指令执行时有关指针寄存器发生调整的方向。
- bit11(OF):溢出标志,对任何一个算术,假定算术为有符号运算,结果超出目标位置所能容纳最大正数或者最小负数时,此为1,反则0
- bit0(CF):进位标志,当一个算术在结果最高位产生进位或者借位时,标志为1,否则为0
¶可以提供偏移地址的寄存器
- 可用BX,SI,DI,BP,其他都为非法
mov [ax], dl
非法指令mov [dx], bl
非法指令
- 基址变址组合
bx+si, bx+di, bp+si, bp+di
- 非法例子
bx+ax
ax+cs
¶文本模式颜色表(80 X 25)
属性:KRGB IRGB(前4为背景色,后4为前景色)
R | G | B | 背景色(K=0不闪烁,K=1闪烁) | 前景色I=0 | 前景色I=1 |
---|---|---|---|---|---|
0 | 0 | 0 | 黑 | 黑 | 灰 |
0 | 0 | 1 | 蓝 | 蓝 | 浅蓝 |
0 | 1 | 0 | 绿 | 绿 | 浅绿 |
0 | 1 | 1 | 青 | 青 | 浅青 |
1 | 0 | 0 | 红 | 红 | 浅红 |
1 | 0 | 1 | 品(洋)红 | 品(洋)红 | 浅品(洋)红 |
1 | 1 | 0 | 棕 | 棕 | 黄 |
1 | 1 | 1 | 白 | 白 | 亮白 |
¶指令
指令 | 使用 |
---|---|
$和$$ | nasm特有 $:当前行地址 $$:当前所在段起始地址 |
div(无符号除法) | 8位(寄存器或者8位操作数的内存地址) 除数在寄存器AX中 相除后:商在AL中,余数在AH中 示例: div bh div byte [0x2002] |
16位(寄存器或者16位操作数的内存地址) 除数是32位的,低16位在AX中,高16位在DX中 相除后:商在AX中,余数在DX中 示例: div bx div word [0x2002] |
|
32位(寄存器或者32位操作数的内存地址) 除数是64位的,低32位在EAX中,高32位在EDX中 相除后:商在EAX中,余数在EDX中示例: div ebx div dword [0x2002] |
|
64位(寄存器或者64位操作数的内存地址) 除数是128位的,低64位在EAX中,高64位在RDX中 相除后:商在RAX中,余数在RDX中 示例: div rbx div dword [0x2002] |
|
idiv(有符号除法) | 寄存器规则同div 正负:如果被除数和除数符号相同,商为正数,否则商为负则为负数 余数的符号始终和被除数相同 |
mul(无符号乘法) | 8位寄存器或者8位操作数的内存地址) 被乘数是8位的,在寄存器AL中 相乘后,乘积是16位的,在寄存器AX中 示例: mul bh mul byte [0x2002] |
16位(寄存器或者16位操作数的内存地址) 被乘数是16位的,在寄存器AX中 相除后:乘积是32位的,低16位在寄存器AX中,高16位在寄存器DX中 示例: mul bx mul word [0x2002] |
|
32位(寄存器或者32位操作数的内存地址) 被乘数是32位的,在寄存器AX中 相除后:乘积是64位的,低32位在寄存器EAX中,高32位在寄存器EDX中 示例: mul ebx mul dword [0x2002] |
|
64位(寄存器或者64位操作数的内存地址)被乘数是64位的,在寄存器RAX中 相除后:乘积是128位的,低64位在寄存器RAX中,高64位在寄存器RDX中 示例: mul rbx mul dword [0x2002] |
|
imul(无符号乘法) | 类似mul |
add, adc | add:相加 |
adc:进位相加,受进位标志CF影响 | |
or, xor | or:或操作 |
xor:异或指令 可用于寄存器清`xor ax, ax,等价于mov ax 0,用异或操作的都是寄存器,速度会更快 |
|
neg, not | neg:求补码(取反) neg dx=> dx * -1 等同-2=>2, 3=>-3 |
not:逐位求反 | |
shr,ror,shl,rol | shr:右移 |
ror:循环右移 | |
shl:左移 | |
rol:循环左移 | |
near,for | near:绝对间近跳转,寻址范围为64K |
jmp near table 近距离跳转 | |
dec, inc | dec:递减,i-- |
inc:递增,i++ | |
rep,loop重复指令 | rep次数由cx寄存器决定(为0则停止) rep movsw(cx会自己递减) |
loop机器码E2,8位相对偏移量,也就意味着,跳转位置不能太远,循环次数也是由cx确定,为0则中断循环(cx会自己递减) | |
movsb, movsw | 数据传输指令,只会传送一次,一般辅助循环指令使用 使用规则: DS:SI:原始数据串的段地址:偏移地址,si会自动改变值(根据方向递增或递减) ES:DI:目标位置的段地址:偏移地址,di会自动改变值(根据方向递增或递减) 设置传送方向:cld,std |
cld,std | cld:FLAGS寄存器方向位(bit10)清零,此时传送方向,低地址向高地址传送 |
sld:FLAGS寄存器方向位(bit10)置一,此时传送方向,高地址向低地址传送 | |
cli,sti | cli:FLAGS寄存器方向位(bit9:IF)清零 |
sti:FLAGS寄存器方向位(bit9:IF)置一 | |
SECTION,ALIGN,VSTART | section给程序分段,align执行段的内存对齐方式,在同一个程序中,由多个段,理论每个段中的第一个数据地址是0,但是实际上是相对程序开头的地址,所以需要实现某个段的首个数据地址是0时,需要使用VSTART=0(0为指定起始地址,如果为2则每个段其实数据地址为2) 示例: SECTION data1 ALIGN=16 VSTART=0 mydata dw 0xFACE SECTION data2 ALIGN=16 VSTART=0 string db 'hello' section code ALIGN=16 VSTART=0 mov bx,mydata mov si,string |
section.段名字.start | 计算一个段相对整个程序的起始位置,相当于这个段位于程序开头的字节数,也可以是理解为相对程序开头的偏移量(示例见用户程序头部信息) |
equ | 等同于。常量的声明,类似于#define。常量的声明不会占用汇编地址 |
resb,resw,resd | 在编译过程中保留指定大小的空间,不做初始化 resb 256 == resw 128 == resd 64 == times 256 db 0 |
in,out | 端口的输入输出不能直接使用mov指令,在intel中需要使用in,out;它们都不影响标志位 in:源操作数通常是dx(存放端口号),目标操作数根据数据大小使用al或ax,当端口号小于256时,源操作数可以使用立即数 out:目标操作数通常为dx或小于256的立即数,源操作数为al或ax(和in相反) |
call指令 | 调用程序(16位相对近调用,所以跳转时cs不用压栈保存数据)。被调用的子程序可以ret或retf指令结尾,过程返回,以此返回主程序继续执行。ip指针寄存器指向下一条指令的偏移地址,调用前ip压栈,返回时出栈。 call 标号或者目标处的汇编地址(16位相对近调用,前后两个字节的大小-32768到32767,可搭配ret) call 寄存器/偏移地址(段内,16位间接绝对近调用,可搭配ret) call cx:转移前先将ip压栈,然后用cx中的值填充ip,再进行跳转 call [0x3000]:转移前将ip压栈,然后ds值左移4位加上偏移地址0x3000取到物理地址,在地址中取出一个字(两个字节),填充到ip,再进行跳转 call [bx]:转移前将ip压栈,然后ds+bx得到物理地址,在地址中取出一个字(两个字节),填充到ip,再进行跳转 call 16位段地址:16位偏移地址(16位直接绝对远调用,可搭配reft) call 0x052e:0x003c:转移前将ip压栈,0x052e替换cs内容,0x003c替换ip内容 call far 目标地址(16位间接绝对远调用,可搭配reft) call far [0x2000]:转移前将cs,ip压栈,然后ds值左移4位加上偏移地址0x2000取到物理地址,在地址中取出两个个字(四个字节),前一个字填充到ip,后一个字填充到cs,再进行跳转 call far [bx]:转移前将cs,ip压栈,然后ds+bx得到物理地址,在地址中取出两个个字(四个字节),前一个字填充到ip,后一个字填充到cs,再进行跳转 |
ret,retf | ret 用栈中的数据修改IP(近转移) |
retf 用栈中数据修改cs:ip(远转移) | |
他们不一定要搭配call执行使用,也可搭配push使用,push先压缩段地址,在压缩偏移地址,然后调用retf,将从栈中pop出数据填充到cs和ip当中 | |
iret | 所有的中断程序结尾必须是以iret指令结尾 |
iret | 所有的中断程序结尾必须是以iret指令结尾 |
test | test 寄存器/地址, 寄存器/立即数 与and类似,不同于目标操作数的值不会被改变,但是FLAGS的值会改变 |
hlt | 停止指令,使CPU进入低功耗状态,直到用中断唤醒 |
int, int3,into | int:软中断 int 中断号 |
int3:陷阱中断(调式程序使用,断点的原理) int3 中断号 | |
into:溢出中段(OF是1时发生into中断) | |
int 0x70:这里的0x70中断等同于实时时钟发出的0x70中断 int3 != int(space) 3 |
|
条件跳转 | js:符号标志SF为1则转移,jns,符号标志为0则转移; jz:零标志ZF为1则转移,jnz:零标志ZF为0则转移; jo:溢出标志OF为1则转移,jno:溢出标志OF为0则转移; jc:进位标志CF为1则转移,jnc:进位标志CF为0则转移; jp:奇偶标志PF为1则转移,jnp:奇偶标志PF为0则转移; jcxz:当CX寄存器内容为0时则转移; cmp,比较两个数后,条件转移(大于等于小于等等) 助记: a:above高于 b:below低于 e:equal相等 n:not不 g:greater大于 l:小于 例子:jnge:不大于等于 |
无条件转移(jmp) | jmp 之后的指令如果跟了 ret,那就直接退出到上一个 call 对应指令的下一行;jmp跳转后会影响cs,ip的值,但不会影响ds,es的值 jmp short 标号/目标处的汇编地址(段内,相对短转移,前后一个字节的大小-128到127) jmp near 标号/目标处的汇编地址(段内,相对近转移,前后两个字节的大小-32768到32767) jmp 标号/目标处的汇编地址(编译器决定时short还是near) jmp 寄存器/内存地址(段内,16位间接绝对近转移) jmp 16位段地址:16位偏移地址 (段之间转移,16位直接绝对远转移) 示例: jmp 0x052e:0x003c:0x052e替换cs内容,0x003c替换ip内容 jmp far 内存地址(16位间接绝对远转移) 示例: jmp far [0x2002]:2002~2003替换ip内容,2004~2005替换cs内容 jmp far [bx]:ds+bx拿到物理地址中数据中的两个字,第一个字替换ip内容,第二个字替换cs内容 |
¶指令对flags寄存器的影响
- cbw / cwde / cdqe / cwd / cdq/ cqo: 不影响任何标志位。
- cld : DF=0,对CF、OF、ZF、SF、AF和PF的影响未定义。
- std : DF=1,不影响其他标志位。
- inc/dec: CF标志不受影响;对OF、SF、ZF、AF和PF的影响依计算结果而定。
- add / sub:OF、SF、ZF、AF、CF和PF的状态依计算结果而定。
- div / idiv:对CF、OF、SF、ZF、AF和PF的影响未定义。
- mov / movs:这类指令不影响任何标志位。
- neg:如果操作数为0,则CF=0,否则CF=1;对OF、SF、ZF、AF和PF的影响,计算结果而定。
- xor:OF=0,CF=O;对SF、ZF和PF依计算结果而定;对AF的影响未定义。
¶寻址方式
¶寄存器寻址
操作数时由寄存器引起的
- mov ax, cx
- add bx, 0xf000(目标操作数)
- inc dx
¶立即数寻址
操作数时由立即数直接指定的
- add bx, 0xf000(源操作数)
- mov dx, mydata
¶内存寻址
数据在内存当中(8086:段地址左移4位加上偏移地址形成20位的物理地)
段地址:由4个段寄存器之一提供(cs, ds,es,ss,默认ds),偏移地址:由指令来提供(内存寻址,实际即使寻找偏移地址)
- 直接内存寻址:
- mov ax, [0x5c0f]
- add word [0x0230], 0x5000(目标操作数)
- xor byte [es:mydata], 0x55(目标操作数)
- 变址寻址:内存寻址的一种
- 类似基址寻址,不同之处主要在于变址寻址是使用si和di索引寄存器
; 使用变址寻址的例子 |
¶基址寻址
在8086中由基址寄存器bx,bp提供,基址地址也称有效地址(effective address)
buffer dw 0x20, 0x100, 0x0f,0x300, 0xff00(buffer是标号) |
¶基址变址寻址:
基址变址寻址中使用了bx时,默认使用ds提供段地址,访问数据段;使用了bp时,默认使用ss提供段地址,访问栈段(使用示例见示例代码基址变址寻址的使用项中)
[bx + si] |
¶用户程序
- 用户程序头部必须包含:程序的长度,入口点,段重定位表项数,段重定位表
- 用户程序起始逻辑扇区号从100开始存放(协定)
¶设备交互
每个外部设备都有自己的ROM,里面有设备提供的调用例程代码。
ROM映射到C0000~E0000之间,bios把他们的中断程序写入到中断向量表中(IVT)。
外部设备接口(板卡)设计:
字节一 字节二 字节三 … 55 AA ROM中代码长度(以512为长度) 功能调用例程代码
¶硬盘(块设备)
- 扇区时硬盘的基本单位,和cpu交互数据时是按一个扇区一个扇区的交互
- 个人计算机上硬盘分配了8个端口0x1f0~0x1f7
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
固定值1 | 0:CHS模式; 1:LBA模式 |
固定值1 | 0:主硬盘; 1:从硬盘 |
逻辑扇区号位27 | 逻辑扇区号位26 | 逻辑扇区号位25 | 逻辑扇区号位24 |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
BSY:为1则表示硬盘忙 | DRQ:为1表明硬盘已准备好与主机交换数据 | ERR:为1则表明前一个命令执行错误。具体原因可访问端口0x1f1 |
; LBA28读数据 |
¶显卡
显卡提供两个端口
0x3d4
(索引端口),0x3d5
(数据端口),显卡内部寄存器都有自己的编号,其中两个8位光标寄存器为0x0e(高8位),0x0f(低8位).当我们把0x0f给索引端口时,数据端口会连接0x0f寄存器,我们就可以通过数据端口去读写数据。
put_char: ;显示一个字符 |
¶8259芯片
含有:中断屏蔽寄存器,中断服务寄存器
-
8259主片:
- INC:与处理器的INTR相连
- 引脚:0x08~0x0F
- 端口:0x20,0x21
- 0x08(bit0):与系统定时器/计数器相连
-
8259从片:
- INC:与主片的0x0A引脚相连
- 引脚:0x70~0x77
- 端口:0xA0,0xA1
- 0x70(bit0):与RTC相连,bit0是RTC的中断信号(0:不屏蔽;1:屏蔽中断)
-
中断程序结束前需要发送中断结束命令(EOI),由服务寄存器处理
¶实时时钟(Real Time Clock:RTC)
它结合CMOS RAM使用;
RTC与系统总线相连的端口有索引端口:0x70/0x74;数据端口:0x71/0x75
示例:示例代码中
RTC周期中断,显示时间
- CMOS RAM内容分布
偏移地址 | 内容 |
---|---|
0x00 | 秒 |
0x01 | 闹钟秒 |
0x02 | 分 |
0x03 | 闹钟分 |
0x04 | 时(bit7为0:12小时制;bit7为1:24小时制) |
0x05 | 闹钟时 |
0x06 | 星期 |
0x07 | 日 |
0x08 | 月 |
0x09 | 年 |
0x0A | 寄存器A |
0x0B | 寄存器B |
0x0C | 寄存器C |
0x0D | 寄存器D |
- BCD
CMOS RAM内容数值由二进制形式的十进制编码(Binary Coded Decimal)填写
示例:
25:二进制编码:0001 1001
BCD编码:0010 0101(拆解位2和5,拆解的数字不能大于9,否则视为无效)
¶中断信号
- 寄存器C是只读寄存器
寄存器 | 比特位 | 功能 |
---|---|---|
寄存器A | bit7 | UIP(Update In Process):更新过程指示。(0:更新周期至少在488us内不会启动,此时访问cmos中内容的时间时安全的,1:488us内启动,周期不会超过1984us) |
寄存器A | 6~4 | RTC时基选择。这3位控制外部输入频率的功能。系统将其初始化到010,为RTC选择一个32.768kHz的时钟频率。 |
寄存器A | 3~0 | RTC速率选择(Rate Select,RS)。选择分频电路的分节点。此处的选择决定了周期性中断信号发生的时间间隔。若选择0000,表示不产生周期性中断信号。注意:这是时基为32.768kHz的情况,使用其他时基的速率请参考相关资料MC146818.pdf。 0000:从不触发中断 0001:3.90625ms 0010:7.8125ms 0011:122.070us 0100:244.141us 0101:488.281us 0110:976.5625us 0111:1.953125ms 1000:3.90625ms 1001:7.8125ms 1010:5.625ms 1011:1.25ms 1100:62.5ms 1101:125ms 1110:250ms 1111:5000ms(最长一秒2次) |
寄存器B | bit0 | DSLSWS(Daylight Savings Legacy Software Support):老软件的夏令时支持(兼容老设备,现已不适用) |
寄存器B | bit1 | HF(Hour Format):小时模式(0:12小时制;1:24小时制) |
寄存器B | bit2 | DM(Data Mode):数据模式(0:BCD编码;1:二进制编码) |
寄存器B | bit3 | SQWE(Square Wave Enable):方波输出允许(兼容老芯片,现已不适用) |
寄存器B | bit4 | UIE(Update-ended Interrupt Enabele):更新周期结束中断允许(0:不产生更新周期中断信号,1反之) |
寄存器B | bit5 | AIE(Alarm Interrupt Enable):闹钟中断是否允许(0:不允许,1:允许) |
寄存器B | bit6 | PIE(Periodic Interrupt Enable):周期性中断是否允许发生(0:不允许;1:允许) 寄存器A选择0000时,PIE自动置0 |
寄存器B | bit7 | SET:更新周期禁止(0:更新周期,每秒都发生;1:终止更新周期,并且此后不再产生更新周期。) |
寄存器C | 0~3 | 保留位:始终为0 |
寄存器C | bit4 | UF(Update-enable Flag):更新结束标志(1:发生;0:没有发生) |
寄存器C | bit5 | AF(Alarm Flag):闹钟中断标志(1:发生;0:没有发生) |
寄存器C | bit6 | PF(Periodic Interrupt Flag):周期性中断标志(1:发生;0:没有发生) |
寄存器C | bit7 | IRQF(Interrupt Request Flag):中断请求标志(1:有中断发生,0:没有中断发生) 当为1时去检查bit4~6是那种中断发生,为IRQF都取会使IRQF清0 |
¶中断
NMI:不可屏蔽中断
INTR:可屏蔽中断
¶中断向量表(Interrupt Vector Table:IVT)
实模式下,cpu可以识别256个中断,共1KB大小(00000~003FF)
表示由基础输入输出系统系统(BIOS)启动时创建的,xp /512xh 0(0x7c00后执行可查看IVT)
格式:(中断号*4便可获得入口地址)
00000~00001:中断0的入口地址的偏移地址
00002~00003:中断0的入口地址的段地址
00004~00005:中断0的入口地址的偏移地址
00006~00007:中断0的入口地址的段地址
…
…
¶无法发生中断的时期
当有命令修改ss栈段寄存器时,这条指令执行及下条指令(所以此处一般后面应该修改sp寄存器)执行时不允许发生中断
¶中断类别
¶硬件中断
来自处理器外部的中断(键盘,实时时钟)
¶内部中断
来自处理器内部的中断,不受IF的影响,出现会立即执行
¶软中断
编写程序时用指令(int,int3,into)指定的中断,不收IF的影响
¶中断处理过程
- 第一步:保护断点的现场
将标志寄存器,断码段寄存器cs,指针寄存器ip及相关寄存器压栈
- 第二部:执行中断处理程序
此时把拿到的中断号*4拿到程序地址,继而执行
- 第三步:在遇到iret指令时,相关寄存器出栈,返回断点接着执行
外部中断-->8259A---->IF(可中断,发送中断响应,要求8259A发送一个中断号)-->8259A-->中断处理过程 |
¶BIOS中断调用
call调用指令必要要知道段地址和偏移地址,但是先调用偏移地址的功能时将拿不到地址(因为版本更新,地址也会一直变化),所以此时的设计就是用中断代替call去提供系统服务
- 示例一
mov ah,0 ;每个中段有很多功能,发送软中断前,将0号功能写入ah |
¶示例代码
¶1+2+3…+100
jmp start |
¶基址变址寻址的使用
; 使用基址变址寻址就地反转字母,而不适用栈 |
¶用户程序头部信息
; 包含代码段、数据段和栈段的用户程序 |
¶硬盘主引导扇区代码(加载程序)
;文件说明:硬盘主引导扇区代码(加载程序) |
¶RTC周期中断,显示时间
;=============================================================================== |
¶BIOS功能调用
;=============================================================================== |
¶保护模式+x86阶段
¶寄存器
¶通用寄存器
EAX,EBX,ECX,EDX,EDI,ESI,EBP,ESP
¶段选择器(还是16位寄存器,但作用发生了改变)
SS,DS,CS,ES,FS,GS,保护模式下段寄存器用来保存描述符的选择子,不再用来保存逻辑地址。
他们都拥有一个只有处理器能使用的描述符高速缓存器,用来存放段的线性基地址、界限和属性。段选择器的内容是段描述符的段选择子。段选择子的内容传递个段选择器后,段选择器立即根据段选择子去找到描述符表中的描述符,并取出它。然后将内容传奇个段选择器的描述符高速缓存器中,从此程序用描述符高速缓存器中的段基地址加上段内偏移地址访问数据。
32位系统,实模式同样是使用描述符高速缓存器的,他将寄存器中的值左移4位,变为32的基地址(低20有效),高12位全为0。
- FS: 附加段寄存器(Extra Segment Register) ,其值为附加数据段的段值;
- GS: 附加段寄存器(Extra Segment Register) ,其值为附加数据段的段值。
¶段选择子(16位)
简称选择子(Segment Select:SS)。用来在描述符表中选择一个描述符。
bit15~bit3:描述符索引。最大是8192(2的13次方),索引从0开始。
bit2:TI(Table Indicator)表指示器。为0时,代表描述符在全局描述符表(GDT)中;为1时,代表在描述符表LDT中。
bit1~bit0:RPL,请求特权级。本程序的特权级。
¶全局描述符表寄存器GDTR
48位寄存器。用于跟踪GDT表。保存了GDT的起始线性地址和GDT界限值。
起始地址:通常情况下等于物理地址,但不总是等于。
¶控制寄存器
- CR0
处理器内部寄存器,还有CR1,CR2等等寄存器。CR0是32位寄存器。CR0的bit0:PE,代表着实模式与保护模式的分解线。为0时是实模式,为1时时保护模式。
¶标志寄存器
- bit21(ID):判断CPU是否支持cpuid(为1支持,为0不支持)。
¶任务寄存器TR
用来指向当前任务的tss.寄存器结构类似于段寄存器(段选择器,也有描述符高速缓存器。)
ltr执行:加载ltr选择子到tss选择器,一旦ltr选择器部分发生改变,处理器立刻用ltr选择器中的选择子到gdt中查找对应的ltr描述符,将它传送到ltr的描述符高速缓存器中。
¶局部描述符表寄存器LDTR
用来指向当前任务的ldt.寄存器结构类似于段寄存器(段选择器,也有描述符高速缓存器。)
lldt执行:加载ldt选择子到ldt选择器,一旦ldt选择器部分发生改变,处理器立刻用ldt选择器中的选择子到gdt中查找对应的ldt描述符,将它传送到ldt的描述符高速缓存器中。
¶内存布局
¶高位内存区
实模式下,32处理器地址线不止20根了,可以访问到FFFFF~10FFEF的内存了。这段内存区就叫做高位内存区(High Memory Area:HMA)
¶寻址方式
¶内存寻址
- 32位处理器特有的内存寻址方式
寄存器 + 寄存器 X 比例因子+ 内存偏移 |
¶指令
¶指令集
指令 | 使用 |
---|---|
lgdt | 内存地址指向一个48为的内存地址(16为界限值+32位段基地址),它不影响任何标志位 |
jmp | 实模式:jmp 逻辑段地址:段内偏移地址 保护模式:jmp 描述符选择子:段内偏移地址 |
xchg | 数据交换。[xchg r/m,r/m] |
bits | bits 16/32:指定本条命令后面的代码的操作尺寸。使用bits时,可能会影响到处理器的流水线中的指令,会清除。实模式和保护模式默认都是16位尺寸的,进入保护模式后需要32位的话需要切换。bits 16 ;或[bits 16] mov ds,ax bits 32 ;或[bits 32] mov ds,ax; 此时编译器会给机器码多一个指令前缀66,会影响工作效率;正确的做法是:mov ds,eax。但nasm编译器无论16位还是32位都不会出现前缀66 |
bswap | 操作数只能时寄存器[bswap r].它用来反转操作数的字节序。若是32位的操作数:原来的0~7变为了24~31.原来的8~15变为了16~23.原来的16~23变为了8~15.原来的24~31变为了0~7. |
cpuid | CPU Indentification.获取CPU品牌信息。使用前使用eax(功能号)指定要返回的信息。 |
指令族vmov.. | cmov.. r,r/m(r只能是16位,32位,或者64位的,不能是8位) cmovnz al,bl;非法 cmovg ax,[0x2008] cmovge [0x2008],ecx cmp eax,edx cmovne eax,edx;如果不相等则,把edx值赋给eax |
sgdt | [sgdt m].获取gdt的信息保存到指定地址中,这个地址必须要有6个字节大小。 |
movzx | 带0扩展指令。扩充小的数到大的数中。(左边补零)movzx r16,r8/m8 movzx r32,r8/m8 movzx r64,r8/m8 movzx r32,r16/m16 movzx r64,r16/m16 |
串数据比较 | [cmpsb,cmpsw,cmpsd,cmpsq],他有两个比较串,都必须位于内存中,都有自己的长度。比较时是采用默认尺寸决定使用什么寄存器的。详细见【串比较】 |
pushad/popad | 压入/弹出所有的双字的寄存器到栈中。压栈顺序:eax-ecx-edx-ebx-esp-ebp-esi-edi出栈顺序:edi-esi-ebp-废弃-ebx-edx-ecx-eax |
xlat | 指令执行时,处理器访问指定的表格,用AL中的数字作为偏移量,从表中取出一个字节,传回AL寄存器。隐含操作数:段寄存器DS指向转换表所在段的选择子【类似于数组】。默认尺寸是16位时,使用BX指定转换表的段内偏移。默认尺寸是32位时,使用EBX指定转换表的段内偏移。AL=数字。执行执行后,AL=从表中取出的字节数据。 |
push | 32位处理器支持push直接压入立即数。压入寄存器的实际数据大小需要根据实际判断。 |
ltr/lldt | ltr:加载tss到TR寄存器。lldt:加载ldt到LDTR寄存器。他们操作数或者操作基址都是选择子或者存储着选择子。(tss选择子,ldt选择子) |
¶串比较
默认操作尺寸 | 目的串 | 源串 | REP前缀使用的计数器 |
---|---|---|---|
16 | ES:DI | DS:SI | CX |
32 | ES:EDI | DS:ESI | ECX |
64 | ES:RDI | DS:RSI | RCX |
重复前缀 | 终止条件 |
---|---|
rep | CX/ECX/RCX=0 |
repe/repz | CX/ECX/RCX=0并且ZF=0 |
repne/repnz | CX/ECX/RCX=0并且ZF=1 |
他们只会从指定内存中拿出对应大小数据去比较一次。所以需要搭配rep执行使用。而我们又需要知道比较多少次,所以rep使用时需要一个前缀计数器。也因为如此,我们需要在比较前设定好指定的计数器大小(串总大小除于每次比较的大小)。因为串比较指令会影响标志寄存器,所以循环可使用其他衍生指令。比较方向由标志寄存器DF位决定(DF=0,从开头开始,DI和SI递增;DF=1,从结尾开始,DI和SI递减)。
- 具体使用细节
- 1.用CLD或者STD清除或设置方向位标志DF;
- 2.根据处理器的默认操作尺寸和方向标志,设置DS和SI/ESI/RSI指向源串;
- 3.根据处理器的默认操作尺寸和方向标志,设置ES和DI/EDI/RDI指向源串;
- 4.根据处理器的默认操作尺寸及选择串的比较指令,将比较次数传送到CX/ECX/RCX;
- 5.根据实际情况,选择以串比较指令冲的一个;
- 6.根据实际情况,为串比较指令指定一个前缀,可以是repz/repe/repnz/repne;
- 7.重复比较结束后,根据零标志ZF最后的状态是0还是1,判断两个串是否相同。
¶操作尺寸
指令的操作长度是指指令中操作数的长度及有效地址(偏移地址、偏移量)的长度。为了设计和扩展指令系统,32为处理器将操作尺寸定义为两种:16位操作尺寸和32位操作尺寸。
16位操作尺寸允许8位或者16位操作数,以及16位有效地址。
32位操作尺寸允许8位或者32位操作数(没有16位),以及32位有效地址。
¶默认操作尺寸
32位处理器虽然有两种尺寸,但是,在同一时刻,处理器只能按一种操作尺寸工作,要么选择16位操作尺寸,要么选择32位操作尺寸,着叫做处理器的默认操作尺寸。可从描述符中的属性D/B知道是多少位的。当指令有66/67前缀时,指令执行会多一个时钟周期,所以应该尽量减少前缀的添加。
- 当默认操作尺寸为16位时的指令前缀
66:反转操作数为32位;67:反转有效地址为32位。
汇编指令 | 机器指令 | 操作尺寸 |
---|---|---|
mov ad,dl | 0x88D0 | 8位数据 |
mov ax,dx | 0x89D0 | 16位数据 |
mov eax,edx | 0x6689D0 | 32位数据 |
mov [bx+di],dh | 0x8831 | 8位数据,16位数据 |
mov [bx+di],si | 0x8931 | 16位数据,16位数据 |
mov [bx+di],esi | 0x668931 | 32位数据,16位数据 |
mov [ecx],esi | 0x66678931 | 32位数据,32位数据 |
mov [ecx],si | 0x678931 | 16位数据,32位数据 |
movsb | 0xA4 | 8位数据 |
movsw | 0xA5 | 16位数据 |
movsd | 0x66A6 | 32位数据 |
- 当默认操作尺寸为32位时的指令前缀
66:反转操作数为16位;67:反转有效地址为16位。
汇编指令 | 机器指令 | 操作尺寸 |
---|---|---|
mov ad,dl | 0x88D0 | 8位数据 |
mov ax,dx | 0x6689D0 | 16位数据 |
mov eax,edx | 0x89D0 | 32位数据 |
mov [bx+di],dh | 0x8831 | 8位数据,16位数据 |
mov [bx+di],si | 0x66678931 | 16位数据,16位数据 |
mov [bx+di],esi | 0x678931 | 32位数据,16位数据 |
mov [ecx],esi | 0x8931 | 32位数据,32位数据 |
mov [ecx],si | 0x668931 | 16位数据,32位数据 |
movsb | 0xA4 | 8位数据 |
movsw | 0x66A5 | 16位数据 |
movsd | 0xA6 | 32位数据 |
¶push压入立即数
实际数据压栈尺寸决定着出栈的接受尺寸。
立即数大小 | 操作尺寸大小 | 实际压入的数据尺寸 | 栈指针的变化 |
---|---|---|---|
8 | 16 | 16:高8位的内容是操作数的符号扩展 | SP/ESP减2:由栈段描述符中的B位决定是SP还是ESP |
32 | 32:高24位的内容是操作数的符号扩展 | SP/ESP减4:由栈段描述符中的B位决定是SP还是ESP | |
16 | 16 | 16 | SP/ESP减2:由栈段描述符中的B位决定是SP还是ESP |
32 | 16 | SP/ESP减2:由栈段描述符中的B位决定是SP还是ESP | |
32 | 16 | 32 | SP/ESP减4:由栈段描述符中的B位决定是SP还是ESP |
32 | 32 | SP/ESP减4:由栈段描述符中的B位决定是SP还是ESP |
¶push压入寄存器
push cs/push ss/push ds/push es/push fs/push gs | ||
---|---|---|
操作尺寸大小 | 实际压入的数据尺寸 | 栈指针的变化 |
16 | 16 | SP/ESP减2:由栈段描述符中的B位决定是SP还是ESP |
32 | 32:用比特0扩展到高16位 | SP/ESP减4:由栈段描述符中的B位决定是SP还是ESP |
指令 | 执行的操作 | 说明 |
---|---|---|
ret imm | 从栈中弹出数据到指令指针寄存器栈指针寄存器=栈指针寄存器+imm | 指令执行时,如果处理器的默认操作尺寸是16位的,指令指针寄存器是IP,如果默认尺寸是32位的,指令指针寄存器是EIP;否则是RIP栈指针寄存器是SP还是ESP,取决于SS描述符高数缓存器的B位。 |
retf imm | 从栈中弹出数据到指令指针寄存器从栈中弹出数据到段寄存器CS;栈指针寄存器=栈指针寄存器+imm |
¶指令格式
ModR/M和SIB查表:见intel相关开发手册
指令前缀 | 操作码 | ModR/M | SIB | 偏移量 | 立即数 |
---|---|---|---|---|---|
如果存在为1个字节 | 1到3个字节 | 如果存在为1个字节 | 如果存在为1个字节 | 如果存在:1/2/4个字节 | 如果存在:1/2/4个字节 |
机器码 | 汇编语言指令 | 操作码 | ModR/M | SIB | 偏移量 | 立即数 | ||||
---|---|---|---|---|---|---|---|---|---|---|
mod | reg | r/m | ss | index | Base | |||||
0x031458 | add edx,[eax+ebx*2] | 0x03 | 00b | 010b | 100b | 00b | 011b | 000b | ||
0x14 | 0x58 | |||||||||
0x8184D8003C0000AA55 | add word [eax+ebx*8+0x3c00],0x55AA | 0x81 | 10b | 000b | 100b | 11b | 011b | 000b | 0x003C0000 | 0xAA55 |
0x84 | 0xD8 |
机器码 | 汇编指令 | 指令前缀 |
---|---|---|
0xA5 | movsw | |
0xF3A5 | req movsw | req |
¶设备交互
¶端口0x92
在32位系统中处理器有一个名为
A20M
(address 20 mask)的引脚,它连接着一个或门,或门连接着ICH(输入输出控制中心芯片,南桥芯片)的0x92端口的bit1和老式键盘的0x60
端口(用来控制A20线的有效与否);0x91-bit0:连接处理器的INIT引脚。复位处理器,从0变为1时,处理器将复位,计算机重启。
¶CPUID
功能号 | 返回 |
---|---|
0 | 返回CPU所支持的最大功能号。 返回信息: eax=最大支持的功能号 ebx=0x756E6547,是“Genu"的字符编码 ecx=0x49656E69,是”ineI“的字符编码 edx=0x6C65746E,是"ntel"的字符编码 以上三个寄存器的合成是”GenuineIntel",是供应商的信息。 |
1 | 返回cpu是否支持cmovcc指令族(edx的bit15:为0不支持,为1支持)。 |
¶段描述符
¶段描述符和描述符表
每个段有关的信息需要8个字节(64位)来描述,我们称之为段描述符。当段描述符集中存放时,集中存放的地方就是描述符表,主要的表就是GDT表。描述符索引从0开始,索引号乘于8就能拿到在描述符表中的偏移地址,偏移地址加上描述符表的基地址,就能拿到线性地址。
数据段寄存器只能加载数据段描述符,代码段寄存器只能加载代码段描述符,在加载描述符时会判断类型。但是,平时取指令或读写数据时,段的类型不做检查。
¶全局描述符表
Global Descriptor Table:GDT。它必须在进入保护模式之前定义,也就是定义于实模式下。
处理器规定:GDT的第一个描述符必须为空描述符(或亚描述符)。
¶描述符属性
-
描述符分类
-
存储器的段描述符:代码段,数据段
-
系统描述符
-
系统的段描述符:GDT,LDT,TSS。
-
门描述符
-
-
¶描述符的属性
描述符中的
bit44位
,也称S位(位于高双字中)。s=0:系统描述符,且TYPE字段用来指定系统段的类型或门的类型。s=1:存储器的段描述符,且TYPE字段用来区分代码段和数据段。系统拿到描述符后根据S位去确定处
bit44~bit40(bit43~bit40:TYPE)
之外的内存是什么数据。因为存储器的段描述符和系统的段描述符结构是不同的。
- 存储器段描述符格式(拆为高低双字节展示)
字节 | 比特位 | 属性 | |||||
---|---|---|---|---|---|---|---|
高双字 | bit31~bit24:段基地址 | 段基地址的31~24位 | |||||
bit23:G | 段界限粒度(Granularity)位。 G=0:表示界限粒度为字节; G=1:表示界限粒度为4K字节。 注意:界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 G=0, 实际使用的段界限=描述符中的界限值 G=1,实际使用的段界限=描述符中的界限值x0x1000+0xFFF |
||||||
bit22:D/B | 对于代码段来说,它是D位。操作尺寸/栈上部边界。 值为0时,段使用按16操作;值为1时,段使用32操作。也就决定段界限的大小(向下模式下,段大小是FFFF还是FFFFFFFF) |
对于数据段来说,它是B位。如果B=0,并且这个段是栈段时,使用的是sp寄存器。如果B=1,并且这个段是栈段时,使用的是esp寄存器。 | |||||
bit21:L | 长(Long)模式。64位代码段标志。32位系统下为0。 | ||||||
bit20:AVL | 软件可利用(Available)位。通常由操作系统使用,处理器不使用。 | ||||||
bit19~bit16:段界限 | 段界限的bit16~bit19位 | ||||||
bit15:P | 存在(Present)位。 P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。 |
||||||
bit14~bit13:DPL | 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 | ||||||
bit12:S | s=0:系统描述符,且TYPE字段用来指定系统段的类型或门的类型。 s=1:存储器的段描述符,且TYPE字段用来区分代码段和数据段 |
||||||
bit11~bit8:TYPE | bit11:X=0(executable)时,该描述符段不可执行,通常为数据段。 bit11:X=1时,该描述符段可执行,通常为代码段。 |
X=0时;bit10为E(expand),E=0时,段向上扩展(低地址->高地址:普通段);E=1时,段向下扩展(高地址->低地址:通常为栈段) | X=0时;bit9为W,W=0,段不可写入,W=1,段可以正常写入。 | bit8:A(Accessed):该段近期访问过或者使用过。段初始化时值为0,被访问过后置1,清0由用户软件和操作系统执行。因此通过检测A就可以知道该段的使用频率。 | |||
X=1时;bit10为C,C=0时,为非亦从代码段,表示特权级相同的程序才能转移到这个段执行;C=1时,为亦从代码段,特权级低的程序可以直接转移到这个段来执行。 | X=1时;bit9为R,R=0,代码段不可读出,R=1,代码段可读出。 | ||||||
bit7~bit0:段基地址 | 段基地址的23~15位 | ||||||
低双字 | bit31~bit16:段基地址 | 段基地址的15~0位 | |||||
bit15~bit0:段界限 | 段界限的bit0~bit15位。向上扩展的段,段界限值位0时,段的大小位1(0是最小偏移量);向下扩展的段,段界限值位0时,段的大小位sp或esp的能表示的最大值(FFFF或FFFFFFFF)。 |
¶存储器的描述符
- 第一步:通过选择子,用索引号*8+7<=边界,检查是否存在于gdt中,如果不存在,处理器中止处理,产生异常中断13。
- 第二部:检查描述符类型是否有效,检查描述符与要赋值的段寄存器是否匹配。
段寄存器 | 数据段(X=0) | 代码段(X=1) | ||
---|---|---|---|---|
只读(W=0) | 读写(W=1) | 只执行(R=0) | 执行、读(R=1) | |
CS | N | N | Y | Y |
DS | Y | Y | N | Y |
ES | Y | Y | N | Y |
FS | Y | Y | N | Y |
GS | Y | Y | N | Y |
SS | N | Y | N | N |
- 第三步:检查属性P位,查看描述符所对应的段是否在物理内存中,如果P=0,则不存在物理内存中,它将发出中断11,把对应段调入内存中,把P置1。注意:这里中断后将返回到引起中断的指令,这样将可重新加载描述符。
对于DS,ES,FS和GS的选择器,可以向其加载数值为0的选择子。对于cs和ss的选择器来说,不允许向其传送为0的选择子。
尽管在加载时不会有任何问题,但是访问内存时,会抛出异常。
mov eax,0 |
¶代码段执行的保护
G=0, 实际使用的段界限=描述符中的界限值
G=1,实际使用的段界限=描述符中的界限值x0x1000+0xFFF
- 但jmp 跳转到一个代码段时,cs,ip的值会被刷新。处理器执行下条指令时,将检查0<=EIP+指令长度-1<=实际使用的段界限,否则发出异常。
- **段与段之间是可以重叠的。但一个数据段为00000000~FFFFFFFF时,代码段在0x7c00~0x7c00+512KB时,很明显,两个段重叠了,虽然代码段的描述符不能写入,但是可以通过重叠段的数据段去修改代码段中的值。**如果某个段的内存已经存在,又位这段内存添加一个新的段,那么这个新段就可以是旧段的别名。(实际使用将示例代码:冒泡排序)
¶向上扩展的栈段的保护
向上的数据段也是可以作为栈段的,扩展方向并不影响段界限检查,栈的栈底栈顶方向和向下扩展的段时相同的。偏移量的变化范围也是sp/esp(使用esp还是sp,由描述符的B位来决定)的变化范围。(隐式操作push、pop、call、ret、iret都会用到esp)
向上扩展的栈段检查:操作数的有效地址+操作数的大小-1<=实际使用的段界限
;段基地址0x6c00,段界限0x7ff,粒度为1,向上扩展,数据段 |
¶GDT
为了让程序在内存中能自由的浮动而又不影响它的正常执行,处理器将内存划分成逻辑上的段,并在指令中使用段内偏移地址。在保护模式下,对内存的访问仍然使用段地址和偏移地址,但是,在每个段能够访问之前,必须先进行登记。用来存放这些信息的数据结构叫做描述符表。
等级信息有每个段的起始地址,段的界限等各种访问属性。这样,当你访问的偏移地址超出了段的界限时,处理器就会阻止这种访问,并产生一个叫做内部异常的中断。
GDT理论上可以位于内存中的任意位置,但是因为它需要在进入保护模式前定义,所以它通常位于00000~FFFFF之间。可在进入保护模式后重定位位置。gdt也有一个自己的描述符,他位于处理器当中。
- gdt的格式:(64K大小)
一个一个读描述符,包括不仅限于存储器段描述符,ldt描述符,tss描述符。
- GDTR
bit47~bit16
:全局描述符表段基地址;bit15~bit0
:全局描述符边界(表大小减一)。因为边界值是16位的,所以表最大65536字节,又因一个描述符占8个字节,所以最大在表中定义8192个描述符(段)。
¶LDT
段描述符表。分为三类:全局描述符表GDT,局部描述符表LDT和中断描述符表IDT。GDT和IDT在整个系统中只有一张,而每个任务都有自己私有的一张局部描述符表LDT,用于记录本任务中涉及的各个代码段、数据段和堆栈段以及本任务的使用的门描述符。GDT包含系统使用的代码段、数据段、堆栈段和特殊数据段描述符,以及所有任务局部描述符表LDT的描述符。
ldt也是一块内存区,最大也是64K,最多8192个描述符。ldt也有自己的一个描述符,每个描述符对应一个ldt,它安装在GDT表中。
当段描述符S位为0时,Type类型是2时(0010),这个描述符就是ldt描述符。此时D(bit22)位和L(bit21)位都为0.
- ldt:的格式:(64K大小)
包括不仅限于用户程序头不断的描述符,用户程序代码段的描述符,用户程序数据段的描述符,用户程序栈段的描述符等等的存储器段描述符。
¶TSS
任务状态段。在一个多任务环境中,当发生了任务切换,需保护现场,因此每个任务的应当用一个额外的内存区域保存相关信息,即任务状态段(TSS);TSS格式固定,104个字节,处理器固件能识别TSS中元素,并在任务切换时读取其中信息。tss也有自己的一个描述符,每个描述符对应一个tss,它安装在GDT表中。
当段描述符S位为0时,Type类型是10B1:B位为0时代表不忙,为1时代表忙,这一位由处理器固件管理(可以有效防止任务切换重加载,或者切换到自己),这个描述符就是ldt描述符。此时D(bit22)位和L(bit21)位都为0.
-
tss的格式:(104字节大小)
¶示例代码
¶GDT使用示例
;设置堆栈段和栈指针 |
¶冒泡排序
;冒泡排序 |
¶保护模式程序的动态加载和执行
¶主引导程序
core_base_address equ 0x00040000 ;常数,内核加载的起始内存地址 |
¶内核程序
;以下常量定义部分。内核的大部分内容都应当固定 |
¶用户程序
;=============================================================================== |
¶ldt tss的使用
;以下常量定义部分。内核的大部分内容都应当固定 |