Linux内核将setup放到bootsect之后,使用BIOS中断将setup加载进内存。先来修改bootsect,加载setup模块,然后再跳转进setup模块执行,加载setup模块的具体实现如代码清单2-1所示。
1 SETUPLEN=4
2
3 BOOTSEG=0x7c0
4
5 INITSEG=0x9000
6
7 SETUPSEG=0x9020
8
9 SYSSEG=0x1000
10
11 ENDSEG=SYSSEG+SYSSIZE
12
13 ROOT_DEV=0x000
14
15 .code16
16 .text
17
18 .global_start
19 _start:
20
21 jmpl$BOOTSEG,$start2
22
23 start2:
24 movw $BOOTSEG,%ax
25 movw %ax,%ds
26 movw $INITSEG,%ax
27 movw %ax,%es
28 movw $256,%cx
29 subw%si,%si
30 subw%di,%di
31
32 rep
33 movsw
34
35 jmpl$INITSEG,$go
36
37 go:
38 movw %cs,%ax
39 movw %ax,%ds
40 movw %ax,%es
41 movw %ax,%ss
42 movw $0xFF00,%sp
43
44 load_setup:
45 movw $0x0000,%dx
46 movw $0x0002,%cx
47 movw $0x0200,%bx
48 movb $SETUPLEN,%al
49 movb $0x02,%ah
50 int $0x13
51 jncok_load_setup
52 movw $0x0000,%dx
53 movw $0x0000,%ax
54 int $0x13
55 jmpload_setup
56
57 ok_load_setup:
58
59 movw $msg,%ax
60 movw %ax,%bp
61 movw $0x01301,%ax
62 movw $0x0c,%bx
63 movw $21,%cx
64 movb $0,%dl
65 int $0x010
66
67 jmpl$SETUPSEG,$0
68
69 msg:
70 .ascii"Setup has been loaded"
71
72 .org 508
73 root_dev:
74 .word ROOT_DEV
75 boot_flag:
76 .word 0xaa55
在上述代码中:第24行到第33行将bootsect搬移到0x90000(INITSEG左移4位)。这是因为0x7c00是一个比较低的地址,这一块内存将来会被覆盖,所以bootsect一开始就把自己搬到一块更高的地址。
通过第35行的跳转指令进入新地址里的bootsect继续执行,也就是第38行。注意,第35行的跳转会使cs寄存器变成0x9000(即INITSEG),所以第38行到第42行的代码就是为了把其他段寄存器也设置成0x9000,顺便把栈指针寄存器sp也设置了。在接下来的很长一段时间内,内核都不会使用到栈,但让sp寄存器指向合理的位置是一个好的编程习惯。
接下来的代码使用了BIOS的0x13号中断,从软盘中将setup模块加载进内存。0x13号中断的入参通过各个寄存器传递。
1)ah寄存器是功能号,其值为02,代表读磁盘扇区到内存。
2)al寄存器的值代表了需要读出的扇区数量。
3)ch代表柱面号的低8位。
4)cl的0~5位代表开始扇区号,6~7位代表磁道号的高2位。
5)dh代表磁头号。
6)dl代表驱动器号,0代表软盘,7代表硬盘,es:bx指向数据缓冲区。如果读取出错,则CF标志置位。
综上,第45行至第50行代码的作用就是从0号柱面、0号磁头的第2个扇区开始,连续读取4个扇区数据进内存,数据会被存储在0x90200(INITSEG:0x200)。如果成功,则CF标志不会置位,且第51行的条件跳转语句就会跳到ok_load_setup处继续执行;如果不成功,则使用0x13号中断重置磁盘(ah=0),然后再跳转回ok_load_setup处循环执行。
第59行至第65行使用了0x010(对应的十六进制为0x10)号中断向屏幕上打印“Setup has been loaded”。最后通过第67行的跳转进入setup模块执行。
接下来的代码便是setup模块(见代码清单2-2)的入口。这段代码的功能非常简单,就是在屏幕上打印提示信息,表示setup模块已经开始真正执行了。
1 .code16
2 .text
3 .globl_start_setup
4
5 _start_setup:
6 movw %cs,%ax
7 movw %ax,%ds
8 movw %ax,%es
9 10movw $setup_msg,%ax
11 movw %ax,%bp
12 movw $0x01301,%ax
13 movw $0x0c,%bx
14 movw $16,%cx
15 movb $3,%dh
16 movb $0,%dl
17 int $0x010
18 setup_msg:
19 .ascii"setup is running"
0x10号中断已经出现过很多次了,它只是简单地向屏幕上打印一行信息。这段代码就不再过多解释了。增加了一个新的文件setup.S,makefile文件也要做相应的修改,将这个文件编译进内核镜像,如代码清单2-3所示。
1 AS:=as
2 LD:=ld-m elf_x86_64
3
4 LDFLAG:=-Ttext 0x0-s--oformat binary
5
6 image:linux.img
7
8 linux.img:tools/build bootsect setup
9 ./tools/build bootsect setup>$@
10
11 tools/build:tools/build.c
12 gcc-o$@ $<
13
14 bootsect:bootsect.o
15 $(LD) $(LDFLAG)-o$@ $<
16
17 bootsect.o:bootsect.S
18 $(AS)-o$@ $<
19
20 setup:setup.o
21 $(LD) $(LDFLAG)-e_start_setup-o$@ $<
22
23 setup.o:setup.S
24 $(AS)-o$@ $<
25 clean:
26 rm-f *.o
27 rm-f bootsect
28 rm-f setup
29 rm-f tools/build
30 rm-f linux.img
上面的构建文件里使用了一个名为build的工具,它的作用是将bootsect和setup模块拼接在一起,并且保证bootsect的长度为512B,而setup的长度是2KB,所以经过build工具的拼接,生成的linux.img文件的大小一定是准确的2560B,一共是5个扇区。因为build.c文件代码比较长,逻辑又非常简单,所以这里就不再引录了。读者可以在代码仓库中找到相应版本的build.c文件。
构建并运行以后,可以看到屏幕上正确地显示了两行文字。