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 ROOT_DEV = 0x306
在进行分析代码之前,需要注意的是boot文件夹中,bootsect和head中的汇编是基于intel汇编格式,而setup则是基于AT&T汇编语言。这也就是为什么在Makefile中存在两种汇编器。前者用于编译16位的汇编代码,用as86和ld86进行编译;而后者则已经进入到32位代码下面运行,利用as和ld进行编译。boot文件夹中存在这三个文件,其中bootsect和setup分别编译成单独的二进制单元,而head则和后面的C文件链接到system中。根据下面图可以知道boot文件夹里面三个文件的用处。bootsect部分主要用于检测磁盘参数并加载setup文件,而setup文件则将system整体复制到内存,并将system转移到系统的起始内存。这样分两步可以避免将BIOS提供的中断服务给覆盖。
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 go: mov ax,cs mov ds,ax mov es,ax mov ss,ax mov sp,#0xFF00 load_setup:
代码开头的entry告诉汇编器,只是程序的入口点,类似于C语言里面的main函数一样。在start和go之间,主要是将自身的代码复制到内存段0x8000中。然后利用一个长跳转,同时设置CS和PC。需要注意的是movw和rep的组合使用,复制的源地址有DS:SI指出,而目的地址由DS:DI指出。在这里,DS是data segment的缩写,而ES则是extend segment的缩写,SI和DI分别是source index和destination index。go和label之间的步骤设置所有的除CS之外的段寄存器,并且设置堆栈指针为0xFF00,则SS:SP为0x9FF00。由于堆栈指针是往下增长的,因此只要SP足够大不会覆盖原来的代码部分就可以了。
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:
这一步主要读取磁盘中的setup,如果读取不成功则充值磁盘并重新进行读取,否则进入到下一步(具体中断含义见grub0.97部分的分析)。
ok_load_setup: 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 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 mov ax,#SYSSEG mov es,ax ! segment of 0x010000 call read_it call kill_motor这一部分代码主要涉及到磁盘参数读取,利用磁盘参数决定下面的读取操作。实际上,system就在第六个磁盘的扇区中,但是system占用多少个柱面需要经过计算才能得到。因此在这里需要将磁盘的参数给保存下来。缓存下来之后就开始打印输出提示信息,同时设置参数准备为加载system做准备。
call kill_motor 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 jmpi 0,SETUPSEGkill_motor用于禁止软盘驱动器,因为system作为一个整体已经加载到内存中,所以软盘的作用已经完成了,因此可以将它禁止。在加载完成时对系统的根设备进行验证,root_dev可以在build中进行设置。root_dev中保存的是磁盘或者软盘的设备号。如果根设备的设备号验证失败,则会陷入死循环中。否则跳转到setup代码中进行下一步执行。
jmpi 0,SETUPSEG 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:
这一部分代码主要进行段验证,由于段访问时段寄存器左移4位形成段地址,然后加上偏移,所以段的低12位为0,bx中存放的是段偏移,初始化为0,表明是一个段的起始。下面验证system模块是否读取结束。
ok1_read: <span style="white-space:pre"> </span>seg cs <span style="white-space:pre"> </span>mov ax,sectors <span style="white-space:pre"> </span>sub ax,sread <span style="white-space:pre"> </span>mov cx,ax <span style="white-space:pre"> </span>shl cx,#9 <span style="white-space:pre"> </span>add cx,bx <span style="white-space:pre"> </span>jnc ok2_read <span style="white-space:pre"> </span>je ok2_read <span style="white-space:pre"> </span>xor ax,ax <span style="white-space:pre"> </span>sub ax,bx <span style="white-space:pre"> </span>shr ax,#9 ok2_read:这一部分验证一次读取是否会超过段限制,如果没有超过段限制,则直接跳转到ok2_read开始按照柱面为单位进行读取,否则,将64K中减去BX剩下的内存位置重新计算为扇区数目后开始读取。这一步主要是为了得到可靠的AX作为下一步读取的参数。
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:ok2_read部分,首先读取柱面的内容,然后验证是否已经读完这个柱面。若读完则进行磁头验证(软盘只有两个磁头,也就是只有两个面)。由于起始时head设置为0,如果超出了sub得到的结果不等,则需要重新设置磁头号码。如果相等,则需要递增柱面号。这里读完0号磁头的0号柱面则应该读取1号磁头的0号柱面。也就是system最多能够占用两个柱面。同时设置新的起始扇区号码,并验证是否这次读取已经叨叨这个段的尽头,如果达到段的尽头则需要重新设置段,否则直接进行读取就可以了。
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 kill_motor: push dx mov dx,#0x3f2 mov al,#0 outb pop dx ret对磁盘一个柱面的读取和之前的一样,只不过这里当做函数进行调用,而禁止软盘驱动则直接利用outb进行操作,其中DX存放的是软盘的相应寄存器的地址,AL存放的是操作码。