在实模式下,使用BIOS中断可以获取很多硬件信息。本节将会使用BIOS中断查看硬件信息,然后将结果存储在内存的特定位置。这样一来,操作系统内核就可以直接使用这些参数,从而简化编程。获取硬件信息的具体实现如代码清单2-4所示。
1 _start_setup:
2 ...
3 movw $INITSEG,%ax
4 movw %ax,%ds
5 movb $0x03,%ah
6 xor%bh,%bh
7 int $0x10
8 movw %dx,(0)
9 movb $0x88,%ah
10 int $0x15
11 movw %ax,(2)
12
13 movb $0x0f,%ah
14 int $0x10
15 movw %bx,(4)
16 movw %ax,(6)
17 movb $0x12,%ah
18 movb $0x10,%bl
19 int $0x10
20 movw %ax,(8)
21 movw %bx,(10)
22 movw %cx,(12)
23
24 movw $0x0000,%ax
25 movw %ax,%ds
26 ldsw(4 *0x41),%si
27 movw $INITSEG,%ax
28 movw %ax,%es
29 movw $0x0080,%di
30 movw $0x10,%cx
31 rep
32 movsb
33
34 /* 获取hd1数据 */
35 movw $0x0000,%ax
36 movw %ax,%ds
37 ldsw(4 *0x46),%si
38 movw $INITSEG,%ax
39 movw %ax,%es
40 movw $0x0090,%di
41 movw $0x10,%cx
42 rep
43 movsb
44
45 movw $0x1500,%ax
46 movb $0x81,%dl
47 int $0x13
48 jcno_disk1
49 cmpb$3,%ah
50 jeis_disk1
51 no_disk1:
52 movw $INITSEG,%ax
53 movw %ax,%es
54 movw $0x0090,%di
55 movw $0x10,%cx
56 movw $0x00,%ax
57 rep
58 stosb
59 is_disk1:
60 ...
上述代码首先将INITSEG(0x9000)保存在ds寄存器中,然后通过0x10号中断获取了光标信息,其输入功能号是0x03(第5~7行)。光标位置结果作为中断的输出,会被存放在dx寄存器中。第8行代码将dx寄存器中的值放到内存里,其内存地址以ds为段基址,位置的偏移为0,也就是0x9000<<4+0,或者直接写成0x90000。
这个位置存放着bootsect的代码,但是当CPU执行到setup的时候,bootsect就肯定不会再被执行了,所以这部分内存是可以被复用的。操作系统选择将硬件参数保存在这里。
第9~10行,利用中断号0x15的子功能号0x88来获取扩展内存的大小,获取内存大小的结果会保存在ax寄存器中,然后第11行将扩展内存保存在0x90002处。注意,这条指令是利用了段寻址方式,这与第8行的写法是一致的,所以扩展内存的大小就保存在0x90002处。
接下来就是获取显卡的显示模式相关信息,依然参见代码清单2-4。
第14行使用了中断号0x10,这也是一个和显示器相关的中断。ah寄存器赋值为0x0f,对应的子功能号为获取显示器的显示模式,将返回结果放入ax和bx两个寄存器中。其中ah(ax的高8位),存放屏幕字符的列数,al(ax的低8位)存放显示器的显示模式,bh(bx的高8位)存放显示器页数。第15和16行分别把获取到的显示模式信息放到0x90004和0x90006的位置。
在获取显示器的显示模式信息之后,紧接着还要获取显示器的其他信息。第19行,再次调用BIOS 0x10号中断,这仍然是与显示器相关的中断,第17行中的ah表示中断的功能号为0x12,第18行的bl表示中断子功能号为0x10。这个中断的作用是获取显卡的相关信息,返回结果分别存入ax、bx、cx三个寄存器中。
其中,ax作为返回寄存器的作用已经被废弃。bh存放视频状态,0x00h表示彩色模式,0x01h表示单色模式。bl存放已经安装的显存大小(00h=64KB,01h=128KB,02h=192KB,03h=256KB)。ch存放特性连接器位信息。cl存放的是显卡的一些开关设置。在之后的20~22行,将返回结果存入内存0x90008~0x90012的位置。
内核在初始化控制台的时候就会使用这些信息,这里先不详细解释这些数据的意义,等到实现控制台打印功能时再来仔细研究。更多有关显示卡编程的知识可以查看附录。
显卡的基本信息获取之后,接下来就是获取硬盘相关的信息。
与磁盘和内存不同,磁盘的信息是在BIOS引导阶段就被保存在了内存里,其中第一块硬盘的参数被存放在4 *0x41的位置,如果系统中还有第二块硬盘,那它的参数就被存放在4 *0x46的位置。
这两个位置本来是BIOS存放中断向量的地方,所以你有时会读到某些材料上说硬盘参数存储在0x41号中断处。这种说法是不准确的,因为BIOS占用了这一段内存,它已经不再是中断向量了。
第24和25行的作用是将ds寄存器初始化为0x0。也就是数据段基址此时被设置成了0。然后第26行使用ldsw指令将地址送入si寄存器,“(4 *0x41)”和前面提到的“0”是一样的,它的地址就是4 *0x41,所以这条指令最终的作用就是把值4 *0x41送入si寄存器。你也可以使用mov指令达成同样的效果,但ldsw指令更加清楚地指明了si寄存器里存放的是一个地址。
接下来,将es寄存器赋值为0x9000,即setup.S开头定义的INITSEG。之后将目的寄存器di赋值为0x80,并将计数寄存器cx赋值为0x10,然后开始进行复制操作。通过rep和movsb两条指令完成从(0x0000:0x41 *4)到(0x9000:0x80)的复制工作,重复16次,完成16个字节的复制工作。
紧接着继续复制第二块硬盘hd1的参数,hd1的参数位于4 *0x46处,它和前面复制hd0参数的代码几乎完全一样,差别仅仅是源地址和目标地址有所不同(第34~43行)。
一台机器可能有多块硬盘,所以接下来还要再通过中断来进行判断。第47行中断类型是0x13,表示磁盘相关的服务,第45行将0x1500赋值给ax,实际上是将功能号0x15赋值给ah(ax的高8位)。第46行dl表示的是驱动器编号,0x80表示第一块硬盘,0x81表示第二块硬盘。中断的返回值存放在ah寄存器中,当ah寄存器的值为0时,代表没有目标驱动器,同时CF会被置位。值为1或者2时代表软盘,值为3时代表硬盘。所以第49行进一步判断:如果ah=3,则表示存在第二块硬盘。
如果没有第二块硬盘,则会执行no_disk1这个lable的代码。首先还是将es寄存器初始化为INITSEG,然后将di寄存器赋值为0x90,这个目标地址是前面初始化hd1时的目标地址。寄存器ax的值是0,所以no_disk1的这段代码的作用就是将硬盘参数表的第二个表清零。
至此,在实模式下的工作就全部完成了,我们可以向保护模式“进发”了。