章节目录
上一节讲解了bootsect,由bootsect加载setup进入内存,最后jmpi 0,SETUPSEG
跳转到setup程序处。
INITSEG = 0x9000 ! we move boot here - out of the way
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020 ! this is the current segment
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx ! it from 0x90000.
先把ds设置成 0x9000,这个段地址在加载bootsect时候讲过,就是bootsect从0x07c00地址复制到 0x90000地址,
bios 10号中断,功能ah=0x03表示读取光标,
输入参数是bh,
返回参数ch=扫描开始线,cl=扫描结束线
dh=行号,dl=列号
mov [0],dx
如同mov ds:[0],dx
,保存了行号和列号,也就是说0x90000处保存了行号和列号。
mov ah,#0x88
int 0x15
mov [2],ax
bios 15号中断,功能号ah=0x88表示获取扩展内存的大小(KB)
返回参数:ax=从0x100000(1MB)处开始的扩展内存大小(KB)
如果出错则cf=1,ax=出错码
mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width
bios 10号中断,功能号ah=0x0f表示获取显卡显示模式
返回参数:ah=字符列数,al=显示模式,hb=当前显示页
0x9000:0x0004存放当前显示页
0x9000:0x0006存放显示模式
0x9000:0x0007存放字符列数
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx
检查显示方式(EGA/VGA)并获取参数
bios 10号中断
ah=0x12 ,bl = 0x10
返回参数:
bh=显示状态, 0x00-彩色模式,I/O端口=0x3dX,0x01-单色模式,I/O端口=0x3bX
bl = 安装的显示内存,0x00-64K,0x01-128K,0x02-192K,0x03-256K
cx=显卡特性参数
程序执行后
0x9000:0x000A = 安装的显示内存
0x9000:0x000B = 显示状态
0x9000:0x000C = 显卡特性参数
! Get hd0 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41]
mov ax,#INITSEG
mov es,ax
mov di,#0x0080
mov cx,#0x10
rep
movsb
获取第一个硬盘的信息,这里用到41号中断。
bios中断向量表中的每个中断向量大小是4字节。这4字节描述了一个中断处理例程(程序)的段基址和段内偏移地址。因为中断向量表的长度为1024字节,故该表最多容纳256个中断向量处理程序。计算机启动之初,中断向量表中的中断例程是由BIOS建立的,它从物理内存地址0x0000处初始化并在中断向量表中添加各种处理例程。
[4*0x41]
这个就表示41号中断的物理偏移地址,段地址就是ds。
lds si,[4*0x41]
取4个字节,前2个字节放在si寄存器中,后2个字节放在ds寄存器中。
rep movsb
在前一节已经讲过类似的,这里不再详说。
movsb的功能是将ds:si指向的内存单元中的字节送入es:di中,注意这里是字节,这样就是把ds:si地址的数据复制到0x9000:0x0080地址,复制10个字节数据。
! Check that there IS a hd1 :-)
mov ax,#0x01500
mov dl,#0x81
int 0x13
jc no_disk1
cmp ah,#3
je is_disk1
no_disk1:
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
检查系统是否存在第2个硬盘,如果不存在则第2个表清0。
利用13号中断
功能号:ah = 0x15
输入:dl = 驱动号(0x8X是硬盘:0x80指第1个硬盘,0x81第2个硬盘)
输出:ah=类型码,00-没有这个盘,CF置位;01-软驱,没有change-line支持,03-软驱(或者其他可移动设备),有change-line支持;03-硬盘。
is_disk1:
! now we want to move to protected mode ...
cli ! no interrupts allowed !
! first we move the system to it's rightful place
mov ax,#0x0000
cld ! 'direction'=0, movs moves forward
do_move:
mov es,ax ! destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax ! source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
jmp do_move
执行到标号is_disk1处,就要进入保护模式了。
cli
关中断,此时不再响应中断信号,因为要把system复制到0x0000位置,会覆盖bios中断。
cld
cld用来操作方向标志位DF(Direction Flag)。cld使DF 复位,即是让DF=0,std使DF置位,即DF=1.这两个指令用于串操作指令中。通过执行cld或std指令可以控制方向标志DF,决定内存地址是增大(DF=0,向高地址增加)还是减小(DF=1,向地地址减小)。
在实模式下,寻址一个内存地址主要是使用段和偏移值,段值被存放在段寄存器中,并且段的最大长度被固定为64KB,段内偏移地址存放在任意一个可用寻址的寄存器中,因此根据段寄存器和偏移寄存器中的值,就可以算出实际内存的地址。
在保护模式下,段寄存器中存放的不再是寻址段的基地址,而是一个一个索引,称为段选择符,由段选择符从全局描述符表或者局部描述符表中找到8个字节长的段描述符,从而确定关于这个段的全部描述信息。
RPL(RequestedPrivilege Level): 请求特权级,表示将要访问的特权级,取值范围0~3。
TI(TableIndicator): TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符。
index: 索引,指出要访问描述符在段描述符表中的顺序号,index占13位,因为顺序号的范围是0~8191,每个段描述符表中最多有8192个描述符。
有一个特殊的选择子称为空(Null)选择子,它的Index=0,TI=0,而RPL字段可以为任意值。空选择子有特定的用途,当用空选择子进行存储访问时会引起异常。空选择子是特别定义的,它不对应于全局描述符表GDT中的第0个描述符,因此处理器中的第0个描述符总不被处理器访问,一般把它置成全0。但当TI=1时,Index为0的选择子不是空选择子,它指定了当前任务局部描述符表LDT中的第0个描述符。
因此。在进入保护模式之前,必须首先设置好将要用到的段描述符,然后使用指令lgdt把描述符表的基地址告知CPU(gdt表的基地址存入gdtr寄存器),再将机器状态字的保护模式标志置位即可进入32位保护运行模式。
end_move:
mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax
!idt刚被加载,一条数据也没有
lidt idt_48 ! load idt with 0,0
lgdt gdt_48 ! load gdt with whatever appropriate
! that was painless, now we enable A20
call empty_8042
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042
mov al,#0xDF ! A20 on
out #0x60,al
call empty_8042
图片来源英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A 第196页
idt limit是描述符表的长度值(字节)。
idt base Address 是描述符表的32位线性基地址。
中断描述符表中的每一个表项(8字节)指出发生中断时需要调用的代码信息,与中断向量有些相似,但要包含更多的信息。
图片来源英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A 第103页
idt_48:
.word 0 ! idt limit=0
.word 0,0 ! idt base=0L
gdt_48:
.word 0x800 ! gdt limit=2048, 256 GDT entries
.word 512+gdt,0x9 ! gdt base = 0X9xxxx
此时此刻内核尚未真正运行起来,还没有进程,所以现在创建的GDT表的第一项为空,第二项为内核代码段描述符,第三项为内核数据段描述符,其余项皆为空。
idt表虽然已经设置,实为一张空表,原因是目前已经关中断,无需调用中断服务程序。
打开A20,意味着CPU可以进行32位寻址,最大寻址空间为4GB。
CPU 在保护模式下,int 0x00~0x1F 被 Intel 保留作为内部(不可屏蔽)中断和异常中断。如果不对 8259A 进行重新编程,int 0x00~0x1F 将被覆盖。例如,IRQ0 (时钟中断)为 8 号(int 0x08)中断,但在保护模式下此中断号是 Intel 保留的“Double Fault”(双重故障)。因此,必须对 8259A 编程将原来的 IRQ0x00~IRQ0x0F 对应的中断号重新分布,即在保护模式下,IRQ0x00~IRQ0x0F 的中断号是 int 0x20~int 0x2F。
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2
out #0xA0,al ! and to 8259A-2
.word 0x00eb,0x00eb
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x01 ! 8086 mode for both
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.
mov ax,#0x0001 ! protected mode (PE) bit
lmsw ax ! This is it!
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
这个时候,正式进入保护模式,CR0控制寄存器的PE置位。
段寄存器cs的值是8,不过此时不再是基地址,而是保护模式下的段选择符,段选择符是16位的数据,8用二进制表示就是0b0000 0000 0000 1000,第0到1位表示特权级别,第2位的0表示使用全局描述符表,第3位的1表示索引,所以jmpi 0,8
就是请求特权级0,使用全局描述符表中的第一项,由于上面我们设置过gdt,这个基地址就是0,也就是跳转去执行system中的代码,system的头部就是head,下一节继续讲解head.s
图片来源英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A 第76页