从开机到main函数的执行分三步完成:
(1)启动BIOS,准备实模式下的中断向量表和中断服务程序。
(2)从启动盘加载操作系统程序到内存,并为保护模式做准备,加载操作系统程序的工作就是利用(1)中准备好的中断服务程序实现的。
(3)为执行32位main函数做过渡工作
本篇先详细说明(1)和(2),即从开机到操作系统内核加载到内存当中的过程。
一. BIOS启动
Intel CPU的硬件都是设计为加电即进入16位实模式状态运行,且硬件逻辑设计成了加电瞬间将CS的值置为0xF000,IP置为0xFFF0,CS:IP 0xFFFF0, 就正好是在固化在主板上一块小ROM芯片中BIOS程序的地址范围(0xFE000~0xFFFFF),而BIOS程序的入口地址正好是0xFFFF0,这样按下开机键后,就开始执行BIOS程序了。BIOS启动后会检测硬件(显卡、内存),并会在内存最开始的地方0x00000的地方用1KB的内存空间(0x00000~0x003FF)创建实模式下的中断向量表,在0x00400~0x004FF内存地址间构建BIOS数据区,并在0x0E05B的位置加载8KB与中断向量相对应的实模式下中断服务程序。注意:实模式下的中断向量占4个字节,CS占2个,IP占2个,每个中断向量都指向一个具体的中断服务程序。
二.加载操作系统内核
操作系统内核代码是分三次加载:
(1)BIOS中断int 0x19把第一扇区的bootsect的内容加载到内存
(2)执行int 0x13中断服务程序把启动盘中2~5扇区的setup程序加载到内存
(3)仍然执行int 0x13中断服务程序把启动盘中第6~240扇区的System模块加载到内存
2.1 引导程序bootsect加载及执行
计算机完成自检后会让CPU接收到一个int 0x19中断,从而使CPU跳转到int 0x19对应的中断服务程序入口地址(0x0E6F2),把启动盘第一扇区512B内容复制到0x07C00(BOOTSEG)内存地址处,这个过程和linux无关,是BIOS中先固化好的。第一扇区中的内容就是bootsect程序,是由bootsect.s汇编程序汇编而成的。至此,计算机内存中第一次有了linux操作系统的代码,虽然只是启动代码。
bootsect程序的执行(参见/boot/bootsect.s):
(1)规划内存:实模式下,CPU最大寻址范围是1MB
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
(2)将自身全部内容(512B)从内存0x07C00(BOOTSEG)处复制到0x90000(INITSEG)
entry start
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep !rep重复的是跟在它后面的一条指令,此处rep movw,重复的是movs指令:把DS:SI的内容复制到ES:DI中
movw !搬移完成后,CS = BOOTSEG(0x07C0)
搬移完成后,执行跳转,跳转后,CS:0x9000(INITSEG),IP为从INSTSEG到go:mov ax,cs这行对应的偏移
jmpi go,INITSEG !段间跳转指令,执行这条指令后:CS = INITSEG,IP = go;跳转完成后,操作系统已经不需要完全依耐BIOS
go: mov ax,cs
因为代码的整体位置发送了改变,CS也已经改变,所以需要对代码中各个段进行调整,包括DS、ES、SS、SP
mov ds,ax
mov es,ax
! put stack at 0x9ff00.
mov ss,ax
mov sp,#0xFF00 ! arbitrary value >>512 !给CS DS ES SS(栈基址寄存器)赋值0x9000,指定栈顶指针指向0x9FF00
执行完后,DS ES SS都是0x9000,SP是0x9FF00。
2.2 执行bootsect程序加载启动盘中2~5扇区的setup程序到内存
加载setup程序需要借助于BIOS的int 0x13中断服务程序,在执行这个程序之前需要将磁盘扇区、加载到的内存中的位置等信息传给中断服务程序,然后执行 int 0x13指令,产生0x13中断,执行中断程序,将setup.s对应的程序加载至内存SETUPSEG(0x90200)处,这样搬移过后的bootsect与加载完的setup连在一块了。
! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
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 上面mov指令是给中断服务程序传参数,通过读int 0x13中断向量,跳转到指定中断服务程序
jnc ok_load_setup ! ok - continue
mov dx,#0x0000
mov ax,#0x0000 ! reset the diskette
int 0x13
j load_setup
ok_load_setup:
2.3 执行int 0x13中断服务程序把启动盘中第6~240扇区的System模块加载到内存
由于这次是要加载240个扇区120KB数据,会耗费一定时间,因此设置"Load System"开机提示。加载工作主要是bootsect调用read_it子程序完成,这个子程序将软盘第六扇区开始的约240个扇区的system模块加载至内存的SYSSEG(0x10000)处往后的120KB空间中。
加载完成后,bootsect程序的都已经完成,接下来通过执行: jmpi 0,SETUPSEG跳转至0x90200处,即setup程序加载的位置,CS:IP指向setup程序的第一条指令,意味着由setup程序接着bootsect程序继续执行。setup程序现在开始执行,第一件事是利用BIOS提供的中断服务程序从设备上获取机器系统参数,包括光标位置、显示页面等数据,并分别从中断向量0x41和0x46所指向的内存地址处获取硬盘参数表1、硬盘参数表2,把它们存放在0x9000:0x0080和0x9000:0x9000处。这些机器参数被加载到内存的0x90000~0x901FC位置,机器参数是直接覆盖已经执行完成的bootsect程序所占用的内存空间。到此为止,操作系统内核程序加载到内存已经完成了。
entry start
start:
! ok, the read went well so we get current cursor position and save it for
! posterity.
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.
! Get memory size (extended mem, kB)
mov ah,#0x88
int 0x15
mov [2],ax
! Get video-card data:
mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width
! check for EGA/VGA and some config parameters
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx
! 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
! Get hd1 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46]
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb
! 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
is_disk1: !获取机器系统数据:光标位置、显示页面、硬盘参数等,并将这些覆盖到之前bootsect区域。