x86系列CPU可以在16位实模式和32位保护模式下运行,实模式的特点是地址总线只有20位,也就是只有1MB的寻址空间,为了兼容老的CPU,Intel x86系列CPU包括最新的CPU在上电时都运行在16位的实模式下,同时在硬件上强行将CS置成0xF000,IP置成0xFFF0,那么CS:IP就指向0xFFFF0这个地址,也就是上电瞬间代码从该处开始执行,而BIOS恰恰就存储在这个地方,可以想象一下,如果连BIOS都没有将会是一个什么结果。
BIOS程序被存储在计算机主板上的一块ROM芯片里,首先BIOS程序会将启动盘中的第一个扇区512字节内容读取到内存的0x7C00地址处,并跳转到这个地方,而这个扇区内容正是boot/bootsect.s,这样CPU的控制权将从BOIS转移到操作系统这边来,来看代码:
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 movw jmpi go,INITSEG
BOOTSEG值为0x07c0,INITSEG值为0x9000,然后由movw指令将地址0x07C00的512字节内容复制到0x90000地址处,然后跳转到go这个标号处。
go: mov ax,cs mov ds,ax mov es,ax ! put stack at 0x9ff00. mov ss,ax mov sp,#0xFF00 ! arbitrary value >>512
从jmpi指令处可以看出段地址即cs的值为0x9000,那么这里ds、es和ss寄存器的值都为0x9000,sp的值为0xFF00,即栈指针指向了地址0x9FF00处。
第一阶段装载已经执行完成,接下来将执行第二阶段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 jnc ok_load_setup ! ok - continue mov dx,#0x0000 mov ax,#0x0000 ! reset the diskette int 0x13 j load_setup
第二阶段的setup代码从第2个扇区开始共4个扇区,通过int 0x13的中断处理函数将这4个扇区代码复制到地址0x90200,也就是紧跟在bootsect代码后面,如果复制成功,跳转到符号ok_load_setup处。
ah = 2 表示读扇区 al 读扇区数 ch 磁道号 cl 扇区号 dh 磁头号(对于软盘即面号)dl 驱动器号、例如0: 软驱A 1: 软驱B es:bx 指向接收数据的缓冲区
接下来是ok_load_setup处,代码如下:
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
调用int 13h中断例程读取驱动器参数,这里主要是读取磁道最大扇区数,读取结果包存在cl寄存器的低6位(0~5),然后将cx寄存器值存储在sectors标号处,重置es寄存器值为0x9000。
! 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
首先调用int 10h的中断例程读取光标的位置,然后还是调用int 10h的中断例程显示一条字符串"Loading system ..."。这里之所以要显示这么一条信息,是因为接下来加载system部分时间比较长,为了防止在加载期间用户误认为是机器故障而执行不适当的操作而显示了这么一条信息。
已经显示了一条信息,接下来就是加载system部分到内存0x10000地址处。
! ok, we‘ve written the message, now ! we want to load the system (at 0x10000) mov ax,#SYSSEG mov es,ax ! segment of 0x010000 call read_it call kill_motor
调用read_it子函数完成加载。
! This routine loads the system at address 0x10000, making sure ! no 64kB boundaries are crossed. We try to load it as fast as ! possible, loading whole tracks whenever we can. ! ! in: es - starting address segment (normally 0x1000) ! sread: .word 1+SETUPLEN ! sectors read of current track head: .word 0 ! current head track: .word 0 ! current track read_it: mov ax,es test ax,#0x0fff die: jne die ! es must be at 64kB boundary xor bx,bx ! bx is starting address within segment rp_read: mov ax,es cmp ax,#ENDSEG ! have we loaded all yet? jb ok1_read ret ok1_read: seg cs mov ax,sectors sub ax,sread mov cx,ax shl cx,#9 add cx,bx jnc ok2_read je ok2_read xor ax,ax sub ax,bx shr ax,#9 ok2_read: call read_track mov cx,ax add ax,sread seg cs cmp ax,sectors jne ok3_read mov ax,#1 sub ax,head jne ok4_read inc track ok4_read: mov head,ax xor ax,ax ok3_read: mov sread,ax shl cx,#9 add bx,cx jnc rp_read mov ax,es add ax,#0x1000 mov es,ax xor bx,bx jmp rp_read read_track: push ax push bx push cx push dx mov dx,track mov cx,sread inc cx mov ch,dl mov dx,head mov dh,dl mov dl,#0 and dx,#0x0100 mov ah,#2 int 0x13 jc bad_rt pop dx pop cx pop bx pop ax ret bad_rt: mov ax,#0 mov dx,#0 int 0x13 pop dx pop cx pop bx pop ax jmp read_track
从上面这一大段可以看到,主要是通过int 13h中断例程将system部分加载到内存地址0x10000处,system大小是192k。system部分加载完成之后,调用kill_motor子函数。
/* * This procedure turns off the floppy drive motor, so * that we enter the kernel in a known state, and * don‘t have to worry about it later. */ kill_motor: push dx mov dx,#0x3f2 mov al,#0 outb pop dx ret
kill_motor用于关闭软盘驱动器马达。
! After that we check which root-device to use. If the device is ! defined (!= 0), nothing is done and the given device is used. ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending ! on the number of sectors that the BIOS reports currently. seg cs mov ax,root_dev cmp ax,#0 jne root_defined seg cs mov bx,sectors mov ax,#0x0208 ! /dev/ps0 - 1.2Mb cmp bx,#15 je root_defined mov ax,#0x021c ! /dev/PS0 - 1.44Mb cmp bx,#18 je root_defined undef_root: jmp undef_root root_defined: seg cs mov root_dev,ax
判断根文件系统设备是否被定义,如果已经被定义,则跳转到root_defined符号处,如果没有定义,然后根据磁道扇区数,来定义根文件系统设备,最后回写到root_dev处。
! after that (everyting loaded), we jump to ! the setup-routine loaded directly after ! the bootblock: jmpi 0,SETUPSEG
程序已经加载完成,根文件系统设备也已确定,跳转到setup代码处(0x90200)。
总结一下,这部分代码主要是将操作系统三个阶段代码从启动盘加载到内存中,最后跳转到第二阶段代码处。
参考书籍:
1. Linux内核设计的艺术
2. Linux内核完全注释 v3.0
Linux-0.11源代码阅读一 加载操作系统,布布扣,bubuko.com