本文内容的重点是x86硬件架构下Linux0.11的引导代码部分,不讨论BIOS本身及其加载,简略介绍BIOS加载引导扇区的部分。
和现在流行的UEFI不同,这个古老的系统由BIOS引导。
BIOS是英文"Basic Input Output System"的缩略词,直译过来后中文名称就是"基本输入输出系统"。——百度百科
1. 加载硬盘数据之前
我们都知道,CPU只能执行内存中的代码。如果代码在硬盘等外设上面,就需要先将其加载进内存再执行。
在Windows下,当我们双击exe程序希望运行它的时候,操作系统会帮我们把硬盘中的程序读入内存然后运行。在按下计算机电源键的时候,操作系统自身也需要"被"加载进内存。这个操作可以分为几个步骤,第一步的加载就是由BIOS完成的,这时CPU处于实模式。
- 注:所有x86 CPU在刚上电时都运行在“实模式”下,这个时候的CPU就相当于一个8086,运行16位代码并拥有20位寻址能力。
2. BIOS加载引导扇区
- 什么是引导扇区:引导扇区就是磁盘等存储外设的第一个物理扇区。这个扇区又叫MBR(Master Boot Record,主引导记录)
Linux0.11的代码就从这里开始。 - BIOS会把指定磁盘的第一个物理扇区的512字节内容复制到内存的0x07C00处,这是一个约定,所有的BIOS都会这么做。
这个时候CS:IP就指向物理地址0x07c00,下一步CPU就会运行从磁盘读来的第一条指令,也就是Linux0.11的第一条指令。至此,第一部分的引导就完成了。
3. Linux的代码第一次被CPU运行
- 当第一句Linux的代码被运行,BIOS就结束了它加载MBR的使命。从此,计算机的控制权就交给了Linux。
- 前面提到这时候处于实模式,MBR中放的是一段8086的机器码,这段程序是由汇编语言写成的,源文件是
/boot/bootsect.s
。他的作用是将另外两批Linux代码,分别是4个扇区的/boot/setup.s
和240个扇区的system模块复制进入内存,本篇文章介绍到屏幕上显示“Loading system …”为止,后面的内容将在下一篇文章中介绍。
以下是这个文件的开头几行,每一个数据的作用将在后面说明。
SYSSIZE = 0x3000
SETUPLEN = 4 ! nr of setup-sectors
BOOTSEG = 0x07c0 ! original address of boot-sector
INITSEG = 0x9000 ! we move boot here - out of the way
SETUPSEG = 0x9020 ! setup starts here
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE ! where to stop loading
(注意注释符是!
不是;
)
bootsect.s
的前几条指令如下,它首先做的事是把自身(位于0x07c00,也就是段地址为BOOTSEG
,偏移量为0)复制到物理地址0x90000(段地址为INITSEG
,偏移量为0)处。需要说明以下rep
命令,它的作用是重复执行下面一条命令直到CX寄存器为0,每执行一次会将CX的值减1。可以看到执行了256次“字传送”操作,也就是移动了512个字节(一个扇区)的数据。
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
下面有一个关键的jmpi的指令,它将CS的值置为INITSEG
也就是0x9000,IP的值置为标号go所标记的指令处的偏移量。这个指令使程序跳转到新复制到的位置继续执行。然后重置数据段地址寄存器和附加段地址寄存器的值为INITSEG
的值,也就是0x9000。并且将栈顶指针指向0x9FF00
jmpi go,INITSEG
go: mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,#0xFF00 ! arbitrary value >>512
- 在第一段代码把自己在内存中的位置重新安排好之后,就要开始装载开始提到的4个扇区的Linux程序了。这个装载操作要借助BIOS提供的中断服务程序:直接磁盘服务(Direct Disk Service——INT 13H)的02H功能。如果操作成功则跳转到标号
ok_load_setup:
处,否则使用功能号00H将磁盘系统复位后重新读取磁盘数据。
02H功能:从硬盘读取数据:
- AH存放功能号02H,AL存放要读取的扇区数量
- ES:BX表示了缓冲区的地址,即复制的目标地址,BX就是缓冲区的偏移量
- CH存放柱面号,CL存放读取的起始扇区
- DH存放磁头号,DL存放驱动器号,00H~7FH:软盘;80H~0FFH:硬盘
02H功能执行完成会影响CF标志位:
- CF=0——操作成功,AH=00H,AL=传输的扇区数
- CF=1——操作失败,AH=状态代码(略)
00H功能:磁盘系统复位:
- AH存放功能号00H
- DL存放驱动器号,00H~7FH:软盘;80H~0FFH:硬盘
00H功能执行完成后会影响CF标志位:
- CF=0——操作成功,AH=00H
- CF=1——操作失败,AH=状态代码
load_setup:
mov dx,#0x0000 ! drive 0, head 0
mov cx,#0x0002 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG
mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors
int 0x13 ! read it
jnc ok_load_setup ! ok - continue
mov dx,#0x0000
mov ax,#0x0000 ! reset the diskette
int 0x13
j load_setup
ok_load_setup:
- 下一步是读取磁盘参数,用到int 13H的08H功能,从代码中也能看出来Linux0.11是存在软盘中的
08H功能:读取磁盘参数
- AH存放功能号08H ,
- DL存放驱动器号,00H~7FH:软盘;80H~0FFH:硬盘
00H功能执行完成后会影响CF标志位:
- CF=1——操作失败,AH=状态代码,
- CF=0——操作成功:
BL存储读取到的磁盘容量01H — 360K,02H — 1.2M,03H — 720K ,04H — 1.44M
CH存储柱面数的低8位
CL的位7-6存储柱面数的高2位
CL的位5-0存储扇区数
DH存储磁头数
DL存储驱动器数
ES:DI表示磁盘驱动器参数表地址
ok_load_setup:
! Get disk drive parameters, specifically nr of sectors/track
mov dl,#0x00
mov ax,#0x0800 ! AH=8 is get drive parameters
int 0x13
mov ch,#0x00
seg cs
mov sectors,cx
mov ax,#INITSEG
mov es,ax
sectors:
.word 0
- 在屏幕上显示一些信息,使用int 10H,这段信息就是经典的“Loading system …”
! Print some inane message
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10
mov cx,#24
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg1
mov ax,#0x1301 ! write string, move cursor
int 0x10
! 作者注:这些数据放在源文件末尾,为方便阅读,放在这里
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10