
1.3 第一个内核程序
学习一门编程语言往往会从打印Hello World开始,开发操作系统也同样如此。读者在本节将会看到一个最简单的操作系统雏形,它的唯一作用就是在屏幕上显示“Hello World!”。
1.3.1 打印Hello World
首先创建文件bootsect.S,这是一个基于汇编语言的源文件。系统的引导就是从这个文件开始的,使用汇编语言与硬件打交道是最方便的。本书选择的汇编编译器是GNU的as,采用的是AT & T语法。编辑文件内容,如代码清单1-1所示。
代码清单1-1 bootsect.S源代码
1 BOOTSEG=0x7c0
2
3 .code16
4 .text
5
6 .global_start
7 _start:
8 jmpl$BOOTSEG,$start2
9
10 start2:
11 movw $BOOTSEG,%ax
12 movw %ax,%ds
13 movw %ax,%es
14 movw %ax,%fs
15 movw %ax,%gs
16
17 movw $msg,%ax
18 movw %ax,%bp
19 movw $0x01301,%ax
20 movw $0x0c,%bx #文字为红色
21 movw $12,%cx #字符串长度
22 movb $0,%dl
23 int $0x010 #通知显卡刷新内容
24
25 loop:
26 jmp loop
27
28 msg:
29 .ascii"Hello World!"
30
31 .org 510
32 boot_flag:
33 .word 0xaa55
在控制台编译bootsect.S,命令如下:
1 #as-o bootsect.o bootsect.S
2 #ld-m elf_x86_64-Ttext 0x0-s--oformat binary-o linux.img bootsect.o
如果一切顺利,则在当前目录下可以看到linux.img文件已创建。将这个文件复制到Bochs的linux011目录下,并在这个目录下执行run.bat文件,则会看到Bochs虚拟机运行起来以后在屏幕上打印了红色的“Hello World!”,如图1-1所示。

图1-1 在屏幕上打印“Hello World!”
如果使用QEMU运行,则需要将linux.img复制到QEMU所在的目录中,然后在QEMU文件路径中打开PowerShell或者cmd,并执行如下命令:
.\qemu-system-i386.exe-boot a-fda linux.img
则同样可以看到,QEMU虚拟机运行起来后在屏幕中打印了红色的“Hello World!”,如图1-2所示。

图1-2 在QEMU中显示“Hello World!”
代码清单1-1第11~15行是设置寄存器的值:将cs寄存器中的值设置到ds和es寄存器。第17~18行是将要打印的字符串的首地址放到bp寄存器。第23行是一条中断触发指令,中断号是0x10,表示和显示器相关的服务。中断功能号保存在ah中。在第19行,ax被赋值为0x1301,那么对应的ah值为0x13。0x13表示在teletype模式下显示字符串。同时al的值为0x01,表示显示输出方式为字符串中含显示字符和显示属性,并且显示后光标位置不变。第20行将0x0c放入bx,在中断号为0x10、功能号为0x13的情况下,bh寄存器存放的是页码,bl寄存器用于设置文字颜色,其中0xc代表红色。第21行将字符串长度送入cx寄存器。第22行表示输出光标的位置,dh表示行号、dl表示列号,这里都为0,表示光标在屏幕的左上角。
1.3.2 开机引导程序
BIOS(Basic Input/Output System,基本输入/输出系统)是计算机接通电源后执行的第一个程序。BIOS首先会做硬件检查,判断是否满足计算机运行的条件,例如,内存条如果没插好,BIOS会提示错误信息,某些情况下主板会发出蜂鸣声警告等。
做完硬件检查之后要确定启动顺序,当选中某块磁盘之后,控制权限就会交给这块磁盘上的MBR(Master Boot Record,主引导记录)。MBR位于磁盘的第一个扇区,一个扇区只有512字节,其中最后两个字节是0x55和0xAA,表明这个设备可以启动。
回顾1.3.1节的bootsect.S的ld选项,-Ttext 0x0的含义正是将目标文件bootsect.o的代码段放到linux.img的开头,也就是在第一个扇区。同时我们可以用如下命令查看第一个扇区的最后两个字节为0x55AA,可以用来启动。
xxd-s 510 linux.img
1981年8月,IBM公司最早的个人电脑IBM PC 5150上市,使用的是Intel的第一代个人电脑芯片8088。8088芯片本身需要占用0x0000~0x03FF的地址空间,用来保存各种中断处理程序(引导程序本身就是中断信号INT 19h的处理程序)。所以,内存只剩下0x0400至0x7FFF的地址空间可以使用。为了把尽量多的连续内存留给操作系统,引导程序就被放到了内存地址的尾部。因为一个扇区是512(十六进制为0x200)字节,引导程序本身也需要一段内存保存数据,系统就另外给它留出512字节。所以,引导扇区的预留位置就变成了0x7FFF−512−512+1=0x7FFF−0x200−0x200+0x1=0x7C00。
在1.3.1节的例子中,bootsect.S中定义了BOOTSEG=0x7c0,汇编代码将被加载到内存地址0x7C00执行。需要注意的是,早期的8086处理器的寄存器都是16位的,地址线是20位,这就意味着CPU的寻址能力是1MB(2的20次方),但是只采用一个寄存器只能寻址64KB(2的16次方),所以它采用了基地址加偏移的方式寻址,也就是使用两个寄存器的值拼接一个真实的物理地址。它的计算方式是物理地址等于基地址左移4位加上偏移值,例如下面的代码:
mov %ds:(%ax),%bx
上述代码表示以ds寄存器为基地址,以ax寄存器为偏移值计算一个地址,然后取这个内存地址处的值,送入bx寄存器。其中,真实物理地址的值是ds的值左移4位再加上ax寄存器的值。