讀者可在linux sourcecode的linux/arch/i386/boot子目錄下(本文以2.2.5版本為例)找到幾個(gè)以.S作為副檔名的組合語(yǔ)言檔,本文要說(shuō)明的即是其中的bootsect.S及setup.S兩個(gè)檔案,及盡量簡(jiǎn)單地說(shuō)明其所牽涉的 相關(guān)硬件部份。
bootsect.S
這個(gè)程序是linux kernel的第一個(gè)程序,包括了linux自己的bootstrap程序,但是在說(shuō)明這個(gè)程序前,必須先說(shuō)明一般IBM PC開機(jī)時(shí)的動(dòng)作(此處的開機(jī)是指"打開PC的電源"):
一般PC在電源打開時(shí),是由內(nèi)存中地址FFFF:0000開始執(zhí)行(這個(gè)地址一定在ROMBIOS中,ROMBIOS一般是在FE000h到FFFFFh中),而此處的內(nèi)容則是一個(gè)jump指令,jump到另一個(gè)位于ROMBIOS中的位置,開始執(zhí)行一系列的動(dòng)作,包括了檢查RAM,keyboard,顯示器,軟硬磁盤等等,這些動(dòng)作是由系統(tǒng)測(cè)試碼(system test code)來(lái)執(zhí)行的,隨著制作BIOS廠商的不同而會(huì)有些許差異,但都是大同小異,讀者可自行觀察自家機(jī)器開機(jī)時(shí), 屏幕上所顯示的檢查訊息。
緊接著系統(tǒng)測(cè)試碼之后,控制權(quán)會(huì)轉(zhuǎn)移給ROM中的啟動(dòng)程序(ROM bootstraproutine), 這個(gè)程序會(huì)將磁盤上的零道零扇區(qū)讀入內(nèi)存中(這就是一般所謂的bootsect,如果你曾接觸過(guò)電腦病毒,就大概聽過(guò)它的大名),至于被讀到內(nèi)存的哪里呢?----絕對(duì)位置07C0:0000(即07C00h處),這是IBM系列PC的特性。而位在linux開機(jī)磁盤的bootsect上的正是linux的bootsect程序,也就是說(shuō),bootsect是第一個(gè)被讀入內(nèi)存中并執(zhí)行的程序。現(xiàn)在,我們可以開始來(lái)看看到底bootsect做了什么。
第一步
首先,bootsect將它"自己"從被ROMBIOS載入的絕對(duì)地址0x7C00處搬到0x90000處,然后利用一個(gè)jmpi(jumpindirectly)的指令,跳到新位置的jmpi的下一行去執(zhí)行,關(guān)鍵 的匯編代碼如下:
! ld86 requires an entry symbol. This may as well be the usualone.
.globl_main
_main:
#if 0
int 3
#endif
movax,#BOOTSEG// BOOTSEG=0x07C0
movds,ax
movax,#INITSEG //INITSEG=0x9000
moves,ax
movcx,#256 //cx寄存器用作計(jì)數(shù)器
subsi,si
subdi,di
cld //CLD(CLear Direction flag)則是清方向標(biāo)志位,也就是使DF的值為0,
//在執(zhí)行串操作時(shí),使地址按遞增的方式變化,這 樣便于調(diào)整相關(guān)段的的當(dāng)前指針。
// 這條指令與STD(SeT Directionflag)的執(zhí)行結(jié)果相反,即置DF的值為1。
rep//MOVSB(MOVe String Byte):即字符串傳送指令,這條指令按字節(jié)傳送數(shù)據(jù)。
//通過(guò)SI和DI這兩個(gè)寄存器控制字符串的源地址和目標(biāo)地址,比如DS:SI這段地址的N個(gè)字節(jié)
//復(fù)制到ES:DI指向的地址,復(fù)制后DS:SI的內(nèi)容保持不變。 而REP(REPeat)指令就是“重復(fù)”的意思,
//術(shù)語(yǔ)叫做“重復(fù)前綴指令”,因?yàn)榧热皇莻鬟f字符串,則不可能一個(gè)字(節(jié))一個(gè)字(節(jié))地傳送,
//所以需要有一個(gè)寄存器來(lái)控制串長(zhǎng)度。這個(gè)寄存器就是CX,指令每次執(zhí)行前都會(huì)
//判斷CX的值是否為0(為0結(jié)束重復(fù),不為0,CX的值減1),以此來(lái)設(shè)定重復(fù)執(zhí)行的次數(shù)。
//因此設(shè)置好CX的值之后就可以用REP MOVSB了。
movsw
jmpigo,INITSEG
! ax and es already contain INITSEG
jmpi go,INITSEG
go:
.
.
.
表示將跳到CS為0x9000,IP為offset"go"的位置(CS:IP=0x9000:offsetgo),其中INITSEG=0x9000定義于程序開頭的部分,而go這個(gè)label則恰好是下一行指令所在的位置。
第二步
接著,將其它segment registers包括DS,ES,SS都指向0x9000這個(gè)位置,與CS看齊。另外將SP及DX指向一任意位移地址(offset),這個(gè)地址等一下會(huì)用來(lái)存放磁盤參數(shù)表 (disk parametertable)。
提到磁盤參數(shù)表,就必須提到BIOS中斷1Eh。先簡(jiǎn)單地介紹一下BIOS的中斷服務(wù):80x86將內(nèi)存最低的256*4bytes保留給256個(gè)中斷向量(每個(gè)interrupt vector大小為4bytes,所以一共有256*4=1024bytes),而其中的第1Eh個(gè)向量指向"磁盤參數(shù)表",這個(gè)表會(huì)告訴電腦如何去讀取磁盤機(jī),而我們所要做的事是搬移磁盤參數(shù)表到剛才所設(shè)定的任意地址。
接著,改變搬移來(lái)的參數(shù)表的參數(shù),以符合我們的需要。再將中斷向量1Eh指向我們所修改過(guò)的磁盤參數(shù)表,然后呼叫BIOSinterrupt的int13h(function0,即AH=0)重置磁盤控制卡及磁盤驅(qū)動(dòng)器,之后磁盤機(jī)就會(huì)照我們的意思動(dòng)作了。如果你曾trace過(guò)DOS的kernel,你會(huì)發(fā)現(xiàn),上述的動(dòng)作在DOS中也有類似的對(duì)應(yīng)流程。
現(xiàn)在讓我們來(lái)看看關(guān)鍵的程序碼:.
.
.
push #0
pop fs
mov bx,#0x78
.
(使GS:SI=FS:BX,指向磁盤參數(shù)表,
再將GS:SI所指地址的內(nèi)容搬移6個(gè)
word至ES:DI所指的地址)
.
.
此段程序是將FS:BX調(diào)整成0000:0078,接著再將GS:SI的內(nèi)容設(shè)成與FS:BX相同,此處0x78h即為int1Eh的起始位置(7*16+8=120,(1*16+14)*4=120)。調(diào)整ES:DI為剛才所設(shè)定的任意地址,從GS:SI搬移6個(gè)word(即12byte)到ES:DI所指的位置,顯然磁盤參數(shù)表的長(zhǎng)度就是6個(gè)word,(不過(guò)事實(shí)上,磁盤參數(shù)表的確實(shí)長(zhǎng)度是11個(gè)byte)。關(guān)于磁盤參數(shù)表,有興趣的讀者可自行參閱講述BIOSinterruptservices的技術(shù)手冊(cè),會(huì)有詳細(xì)的說(shuō)明。
讀者可以用debug自行觀察自家機(jī)器上dos的磁盤參數(shù)表的起始位置(即int1Eh的內(nèi)容)。以下是筆者機(jī)器的情形(筆者使用的操作系統(tǒng)是msdos6.2):
C:>debug
-d0000:0000
0000:0000 8A101601F4067000-1600CB04F4067000......p.......p.
0000:0010 F40670000301790E-43EB00F0EBEA00F0..p...y.C.......
0000:0020 04108E340C118E34-5700CB046F00CB04...4...4W...o...
0000:0030 8700CB0408079433-B700CB04F4067000.......3......p.
0000:0040 0C01790E4DF800F0-41F800F0BA165F06..y.M...A....._.
0000:0050 39E700F01B01790E-70118E341201790E9.....y.p..4..y.
0000:0060 00E000F085175F06-6EFE00F0EE067000......_.n.....p.
0000:0070 53FF00F0A4F000F0-220500003E4600C0S......."...>F..
^^^^^^^^
由上圖中可知,在DOS中磁盤參數(shù)表的起始位置(int1Eh的內(nèi)容)為0000:0522。接著觀察dos中位置0000:0522開始的11個(gè)byte,也就是磁盤參數(shù)表的內(nèi)容
C:>debug
-d0000:0520l10
0000:0520 4D53DF022502121B-FF54F60F08000000MS..%....T......
^^^^^^^^^^^^^^^^^^^^^^
此11byte即為磁盤參數(shù)表的內(nèi)容(分別是byte00h到0Ah)
在程序中我們所更動(dòng)的是第五個(gè)byte(byte04h),改為18h(在上圖例子中為12h),這個(gè)byte的功能是定義磁軌上一個(gè)磁區(qū)的資料筆數(shù)。關(guān)鍵的程序碼如下:
.
movb 4(di),*18
.
第三步
接著利用BIOS中斷服務(wù)int13h的第0號(hào)功能,重置磁盤控制器,使得剛才的設(shè)定發(fā)揮 功能。
.
.
xor ah,ah
xor dl,dl
int 0x13
.
.
第四步
完成重置磁盤控制器之后,bootsect就從磁盤上讀入緊鄰著bootsect的setup程序,也就是以后將會(huì)介紹的setup.S,此讀入動(dòng)作是利用BIOS中斷服務(wù)int13h的第2號(hào)功能。 setup的image將會(huì)讀入至程序所指定的內(nèi)存絕對(duì)地址0x90200處,也就是在內(nèi)存中緊鄰著bootsect所在的位置。待setup的image讀入內(nèi)存后,利用BIOS中斷服務(wù)int13h的第8號(hào)功能讀取目前磁盤機(jī)的參數(shù)。
第五步
再來(lái),就要讀入真正linux的kernel了,也就是你可以在linux的根目錄下看到的vmlinuz。在讀入前,將會(huì)先呼叫BIOS中斷服務(wù)int10h的第3號(hào)功能,讀取游標(biāo)位置,之后再呼叫BIOS中斷服務(wù)int10h的第13h號(hào)功能,在螢?zāi)簧陷敵鲎址?Loading",這個(gè)字 符串在bootlinux時(shí)都會(huì)首先被看到,相信大家應(yīng)該覺得很眼熟吧。
linux的kernel將會(huì)被讀入至內(nèi)存絕對(duì)地址0x10000處,關(guān)鍵的程序碼如下:
.
.
mov ax,#SYSSEG
mov es,ax
call read_it
call kill_motor
.
.
其中SYSSEG于程序開頭時(shí)定義為0x1000,先將ES內(nèi)容設(shè)為0x1000,接著在read_it這個(gè)子程序,便以ES為目的地的節(jié)地址,將kernel讀入內(nèi)存中,至于read_it子程序的詳細(xì)內(nèi)容筆者并不想一一介紹,不過(guò)聰明的讀者們應(yīng)該已經(jīng)猜到,read_it一定又利用了BIOSint13h與磁盤有關(guān)的I/O中斷服務(wù)了。
至于kill_motor子程序,它的功能在于停止軟盤機(jī)的馬達(dá)(各位聰明的讀者會(huì)不會(huì)覺得這個(gè)子程序的名稱取得頗為傳神呢?),其程序碼如下:
.
.
kill_motor:
push dx
mov dx,#0x3f2
xor al,al
outb
pop dx
ret
.
.
首先利用DX指定要輸出的port,而03f2這個(gè)port則是代表了軟盤控制器(floppy diskcontroller)的所在,再利用outb將資料送出,而我們送出的資料,當(dāng)然就是歸零過(guò)的AL了。如此一來(lái),軟盤的馬達(dá)就停止了。
第六步
接下來(lái)做的事是檢查root device,之后就仿照一開始的方法,利用indirect jump跳到剛剛已讀入的setup部份,程序碼如下:
.
.
jmpi 0,SETUPSEG
其中SETUPSEG已在先前定義為0x9020,所以CS:IP會(huì)設(shè)定為9020:0000,即跳到絕對(duì)地址為0x90200,也就是setup的起點(diǎn),而bootsect也大功告成了。
到此為止,內(nèi)存的內(nèi)容應(yīng)該如下圖所示:
比較
把大家所熟知的msdos與linux的開機(jī)部份做個(gè)粗淺的比較,msdos由位于磁盤上bootsect的boot程序負(fù)責(zé)把io.sys載入內(nèi)存中,而io.sys則負(fù)有把dos的kernel--msdos.sys載入內(nèi)存的重大責(zé)任。而linux則是由位于bootsect的bootsect程序負(fù)責(zé)把setup及l(fā)inux的kernel載入內(nèi)存中,再將控制權(quán)交給setup。
至于setup.S,就留到下一次再來(lái)討論了。
bootsect.S的代碼注釋:
! bootsect.s (c) 1991, 1992 Linus Torvalds 版權(quán)所有
! Drew Eckhardt修改過(guò)
! Bruce Evans (bde)修改過(guò)
!
! bootsect.s 被bios-啟動(dòng)子程序加載至0x7c00 (31k)處,并將自己
! 移到了地址0x90000 (576k)處,并跳轉(zhuǎn)至那里。
!
! bde - 不能盲目地跳轉(zhuǎn),有些系統(tǒng)可能只有512k的低
! 內(nèi)存。使用中斷0x12來(lái)獲得(系統(tǒng)的)最高內(nèi)存、等。
!
! 它然后使用BIOS中斷將setup直接加載到自己的后面(0x90200)(576.5k),
! 并將系統(tǒng)加載到地址0x10000處。
!
! 注意! 目前的內(nèi)核系統(tǒng)最大長(zhǎng)度限制為(8*65536-4096)(508k)字節(jié)長(zhǎng),即使是在
! 將來(lái)這也是沒(méi)有問(wèn)題的。我想讓它保持簡(jiǎn)單明了。這樣508k的最大內(nèi)核長(zhǎng)度應(yīng)該
! 是足夠了,尤其是這里沒(méi)有象minix中一樣包含緩沖區(qū)高速緩沖(而且尤其是現(xiàn)在
! 內(nèi)核是壓縮的 :-)
!
! 加載程序已經(jīng)做的盡量地簡(jiǎn)單了,所以持續(xù)的讀出錯(cuò)將導(dǎo)致死循環(huán)。只能手工重啟。
! 只要可能,通過(guò)一次取得整個(gè)磁道,加載過(guò)程可以做的很快的。
#include
!! config.h中(即autoconf.h中)沒(méi)有CONFIG_ROOT_RDONLY定義!!!?
#include
.text
SETUPSECS = 4 ! 默認(rèn)的setup程序扇區(qū)數(shù)(setup-sectors)的默認(rèn)值;
BOOTSEG = 0x7C0 ! bootsect的原始地址;
INITSEG = DEF_INITSEG ! 將bootsect程序移到這個(gè)段處(0x9000) - 避開;
SETUPSEG = DEF_SETUPSEG ! 設(shè)置程序(setup)從這里開始(0x9020);
SYSSEG = DEF_SYSSEG ! 系統(tǒng)加載至0x1000(65536)(64k)段處;
SYSSIZE = DEF_SYSSIZE ! 系統(tǒng)的大小(0x7F00): 要加載的16字節(jié)為一節(jié)的數(shù);
!! 以上4個(gè)DEF_參數(shù)定義在boot.h中:
!! DEF_INITSEG 0x9000
!! DEF_SYSSEG 0x1000
!! DEF_SETUPSEG 0x9020
!! DEF_SYSSIZE 0x7F00 (=32512=31.75k)*16=508k
! ROOT_DEV & SWAP_DEV 現(xiàn)在是由"build"中編制的;
ROOT_DEV = 0
SWAP_DEV = 0
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef CONFIG_ROOT_RDONLY
#define CONFIG_ROOT_RDONLY 1
#endif
! ld86 需要一個(gè)入口標(biāo)識(shí)符,這和通常的一樣;
.globl _main
_main:
#if 0
int 3
#endif
mov ax,#BOOTSEG !! 將ds段寄存器置為0x7C0;
mov ds,ax
mov ax,#INITSEG !! 將es段寄存器置為0x9000;
mov es,ax
mov cx,#256 !! 將cx計(jì)數(shù)器置為256(要移動(dòng)256個(gè)字, 512字節(jié));
sub si,si !! 源地址 ds:si=0x07C0:0x0000;
sub di,di !! 目的地址es:di=0x9000:0x0000;
cld !! 清方向標(biāo)志;
rep !! 將這段程序從0x7C0:0(31k)移至0x9000:0(576k)處;
movsw !! 共256個(gè)字(512字節(jié))(0x200長(zhǎng));
jmpi go,INITSEG !! 間接跳轉(zhuǎn)至移動(dòng)后的本程序go處;
! ax和es現(xiàn)在已經(jīng)含有INITSEG的值(0x9000);
go: mov di,#0x4000-12 ! 0x4000(16k)是>;=bootsect +setup 的長(zhǎng)度 +
! + 堆棧的長(zhǎng)度 的任意的值;
! 12 是磁盤參數(shù)塊的大小 es:di=0x94000-12=592k-12;
! bde - 將0xff00改成了0x4000以從0x6400處使用調(diào)試程序(bde)。如果
! 我們檢測(cè)過(guò)最高內(nèi)存的話就不用擔(dān)心這事了,還有,我的BIOS可以被配置為將wini驅(qū)動(dòng)
表
! 放在內(nèi)存高端而不是放在向量表中。老式的堆棧區(qū)可能會(huì)搞亂驅(qū)動(dòng)表;
mov ds,ax ! 置ds數(shù)據(jù)段為0x9000;
mov ss,ax ! 置堆棧段為0x9000;
mov sp,di ! 置堆棧指針I(yè)NITSEG:0x4000-12處;
! 上面執(zhí)行重復(fù)操作(rep)以后,cx為0;
mov fs,cx !! 置fs段寄存器=0;
mov bx,#0x78 ! fs:bx是磁盤參數(shù)表的地址;
push ds
seg fs
lds si,(bx) ! ds:si是源地址;
!! 將fs:bx地址所指的指針值放入ds:si中;
mov cl,#6 ! 拷貝12個(gè)字節(jié)到0x9000:0x4000-12開始處;
cld
push di !! 指針0x9000:0x4000-12處;
rep
movsw
pop di !! di仍指向0x9000:0x4000-12處(參數(shù)表開始處);
pop si !! ds =>; si=INITSEG(=0X9000);
movb 4(di),*36 ! 修正扇區(qū)計(jì)數(shù)值;
seg fs
mov (bx),di !!修改fs:bx(0000:0x0078)處磁盤參數(shù)表的地址為0x9000:0x4000-12;
seg fs
mov 2(bx),es
! 將setup程序所在的扇區(qū)(setup-sectors)直接加載到boot塊的后面。!! 0x90200開始處
;
! 注意,es已經(jīng)設(shè)置好了。
! 同樣經(jīng)過(guò)rep循環(huán)后cx為0
load_setup:
xor ah,ah ! 復(fù)位軟驅(qū)(FDC);
xor dl,dl
int 0x13
xor dx,dx ! 驅(qū)動(dòng)器0, 磁頭0;
mov cl,#0x02 ! 從扇區(qū)2開始,磁道0;
mov bx,#0x0200 ! 置數(shù)據(jù)緩沖區(qū)地址=es:bx=0x9000:0x200;
! 在INITSEG段中,即0x90200處;
mov ah,#0x02 ! 要調(diào)用功能號(hào)2(讀操作);
mov al,setup_sects ! 要讀入的扇區(qū)數(shù)SETUPSECS=4;
! (假釋所有數(shù)據(jù)都在磁頭0、磁道0);
int 0x13 ! 讀操作;
jnc ok_load_setup ! ok則繼續(xù);
push ax ! 否則顯示出錯(cuò)信息。保存ah的值(功能號(hào)2);
call print_nl !! 打印換行;
mov bp,sp !! bp將作為調(diào)用print_hex的參數(shù);
call print_hex !! 打印bp所指的數(shù)據(jù);
pop ax
jmp load_setup !! 重試!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!INT 13 - DISK - READ SECTOR(S) INTO MEMORY
!! AH = 02h
!! AL = number of sectors to read (must be nonzero)
!! CH = low eight bits of cylinder number
!! CL = sector number 1-63 (bits 0-5)
!! high two bits of cylinder (bits 6-7, hard disk only)
!! DH = head number
!! DL = drive number (bit 7 set for hard disk)
!! ES:BX ->; data buffer
!! Return: CF set on error
!! if AH = 11h (corrected ECC error), AL = burst length
!! CF clear if successful
!! AH = status (see #00234)
!! AL = number of sectors transferred (only valid if CF set forsome
!! BIOSes)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ok_load_setup:
! 取得磁盤驅(qū)動(dòng)器參數(shù),特別是每磁道扇區(qū)數(shù)(nr of sectors/track);
#if 0
! bde - Phoenix BIOS手冊(cè)中提到功能0x08只對(duì)硬盤起作用。
! 但它對(duì)于我的一個(gè)BIOS(1987 Award)不起作用。
! 不檢查錯(cuò)誤碼是致命的錯(cuò)誤。
xor dl,dl
mov ah,#0x08 ! AH=8用于取得驅(qū)動(dòng)器參數(shù);
int 0x13
xor ch,ch
!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! INT 13 - DISK - GET DRIVE PARAMETERS(PC,XT286,CONV,PS,ESDI,SCSI)
!! AH = 08h
!! DL = drive (bit 7 set for hard disk)
!!Return: CF set on error
!! AH = status (07h) (see #00234)
!! CF clear if successful
!! AH = 00h
!! AL = 00h on at least some BIOSes
!! BL = drive type (AT/PS2 floppies only) (see #00242)
!! CH = low eight bits of maximum cylinder number
!! CL = maximum sector number (bits 5-0)
!! high two bits of maximum cylinder number (bits 7-6)
!! DH = maximum head number
!! DL = number of drives
!! ESI ->; drive parameter table (floppiesonly)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#else
! 好象沒(méi)有BIOS調(diào)用可取得扇區(qū)數(shù)。如果扇區(qū)36可以讀就推測(cè)是36個(gè)扇區(qū),
! 如果扇區(qū)18可讀就推測(cè)是18個(gè)扇區(qū),如果扇區(qū)15可讀就推測(cè)是15個(gè)扇區(qū),
! 否則推測(cè)是9. [36, 18, 15, 9]
mov si,#disksizes ! ds:si->;要測(cè)試扇區(qū)數(shù)大小的表;
probe_loop:
lodsb !! ds:si所指的字節(jié) =>;al, si=si+1;
cbw ! 擴(kuò)展為字(word);
mov sectors, ax ! 第一個(gè)值是36,最后一個(gè)是9;
cmp si,#disksizes+4
jae got_sectors ! 如果所有測(cè)試都失敗了,就試9;
xchg ax,cx ! cx = 磁道和扇區(qū)(第一次是36=0x0024);
xor dx,dx ! 驅(qū)動(dòng)器0,磁頭0;
xor bl,bl !! 設(shè)置緩沖區(qū)es:bx = 0x9000:0x0a00(578.5k);
mov bh,setup_sects !! setup_sects = 4 (共2k);
inc bh
shl bh,#1 ! setup后面的地址(es=cs);
mov ax,#0x0201 ! 功能2(讀),1個(gè)扇區(qū);
int 0x13
jc probe_loop ! 如果不對(duì),就試用下一個(gè)值;
#endif
got_sectors:
! 恢復(fù)es
mov ax,#INITSEG
mov es,ax ! es = 0x9000;
! 打印一些無(wú)用的信息(換行后,顯示Loading)
![[Linux]Linux開機(jī)過(guò)程的分析(關(guān)于bootsect.S) bootsect.exe 32 下載](http://img.aihuau.com/images/01111101/01073238t01ac00b3b086b2961e.jpg)
mov ah,#0x03 ! 讀光標(biāo)位置;
xor bh,bh
int 0x10
mov cx,#9
mov bx,#0x0007 ! 頁(yè)0,屬性7 (normal);
mov bp,#msg1
mov ax,#0x1301 ! 寫字符串,移動(dòng)光標(biāo);
int 0x10
! ok, 我們已經(jīng)顯示出了信息,現(xiàn)在
! 我們要加載系統(tǒng)了(到0x10000處)(64k處)
mov ax,#SYSSEG
mov es,ax ! es=0x01000的段;
call read_it !! 讀system,es為輸入?yún)?shù);
call kill_motor !! 關(guān)閉驅(qū)動(dòng)器馬達(dá);
call print_nl !! 打印回車換行;
! 這以后,我們來(lái)檢查要使用哪個(gè)根設(shè)備(root-device)。如果已指定了設(shè)備(!=0)
! 則不做任何事而使用給定的設(shè)備。否則的話,使用/dev/fd0H2880 (2,32)或/dev/PS0
(2,28)
! 或者是/dev/at0 (2,8)之一,這取決于我們假設(shè)我們知道的扇區(qū)數(shù)而定。
!! |__ ps0?? (x,y)--表示主、次設(shè)備號(hào)?
seg cs
mov ax,root_dev
or ax,ax
jne root_defined
seg cs
mov bx,sectors !! sectors = 每磁道扇區(qū)數(shù);
mov ax,#0x0208 ! /dev/ps0 - 1.2Mb;
cmp bx,#15
je root_defined
mov al,#0x1c ! /dev/PS0 - 1.44Mb !! 0x1C = 28;
cmp bx,#18
je root_defined
mov al,0x20 ! /dev/fd0H2880 - 2.88Mb;
cmp bx,#36
je root_defined
mov al,#0 ! /dev/fd0 - autodetect;
root_defined:
seg cs
mov root_dev,ax !! 其中保存由設(shè)備的主、次設(shè)備號(hào);
! 這以后(所有程序都加載了),我們就跳轉(zhuǎn)至
! 被直接加載到boot塊后面的setup程序去:
jmpi 0,SETUPSEG !! 跳轉(zhuǎn)到0x9020:0000(setup程序的開始位置);
! 這段程序?qū)⑾到y(tǒng)(system)加載到0x10000(64k)處,
! 注意不要跨越64kb邊界。我們?cè)噲D以最快的速度
! 來(lái)加載,只要可能就整個(gè)磁道一起讀入。
!
! 輸入(in): es - 開始地址段(通常是0x1000)
!
sread: .word 0 ! 當(dāng)前磁道已讀的扇區(qū)數(shù);
head: .word 0 ! 當(dāng)前磁頭;
track: .word 0 ! 當(dāng)前磁道;
read_it:
mov al,setup_sects
inc al
mov sread,al !! 當(dāng)前sread=5;
mov ax,es !! es=0x1000;
test ax,#0x0fff !! (ax AND 0x0fff, if ax=0x1000 then zero-flag=1);
die: jne die ! es 必須在64kB的邊界;
xor bx,bx ! bx 是段內(nèi)的開始地址;
rp_read:
#ifdef __BIG_KERNEL__
#define CALL_HIGHLOAD_KLUDGE .word 0x1eff, 0x220 ! 調(diào)用 far *bootsect_kludge
! 注意: as86不能匯編這;
CALL_HIGHLOAD_KLUDGE ! 這是在setup.S中的程序;
#else
mov ax,es
sub ax,#SYSSEG ! 當(dāng)前es段值減system加載時(shí)的啟始段值(0x1000);
#endif
cmp ax,syssize ! 我們是否已經(jīng)都加載了?(ax=0x7f00 ?);
jbe ok1_read !! if ax <= syssize then 繼續(xù)讀;
ret !! 全都加載完了,返回!
ok1_read:
mov ax,sectors !! sectors=每磁道扇區(qū)數(shù);
sub ax,sread !! 減去當(dāng)前磁道已讀扇區(qū)數(shù),al=當(dāng)前磁道未讀的扇區(qū)數(shù)(ah=0);
mov cx,ax
shl cx,#9 !! 乘512,cx = 當(dāng)前磁道未讀的字節(jié)數(shù);
add cx,bx !! 加上段內(nèi)偏移值,es:bx為當(dāng)前讀入的數(shù)據(jù)緩沖區(qū)地址;
jnc ok2_read !! 如果沒(méi)有超過(guò)64K則繼續(xù)讀;
je ok2_read !! 如果正好64K也繼續(xù)讀;
xor ax,ax
sub ax,bx
shr ax,#9
ok2_read:
call read_track !! es:bx->;緩沖區(qū),al=要讀的扇區(qū)數(shù),也即當(dāng)前磁道未讀的扇區(qū)數(shù);
mov cx,ax !! ax仍為調(diào)用read_track之前的值,即為讀入的扇區(qū)數(shù);
add ax,sread !! ax = 當(dāng)前磁道已讀的扇區(qū)數(shù);
cmp ax,sectors !! 已經(jīng)讀完當(dāng)前磁道上的扇區(qū)了嗎?
jne ok3_read !! 沒(méi)有,則跳轉(zhuǎn);
mov ax,#1
sub ax,head !! 當(dāng)前是磁頭1嗎?
jne ok4_read !! 不是(是磁頭0)則跳轉(zhuǎn)(此時(shí)ax=1);
inc track !! 當(dāng)前是磁頭1,則讀下一磁道(當(dāng)前磁道加1);
ok4_read:
mov head,ax !! 保存當(dāng)前磁頭號(hào);
xor ax,ax !! 本磁道已讀扇區(qū)數(shù)清零;
ok3_read:
mov sread,ax !! 存本磁道已讀扇區(qū)數(shù);
shl cx,#9 !! 剛才一次讀操作讀入的扇區(qū)數(shù) * 512;
add bx,cx !! 調(diào)整數(shù)據(jù)緩沖區(qū)的起始指針;
jnc rp_read !! 如果該指針沒(méi)有超過(guò)64K的段內(nèi)最大偏移量,則跳轉(zhuǎn)繼續(xù)讀操作;
mov ax,es !! 如果超過(guò)了,則將段地址加0x1000(下一個(gè)64K段);
add ah,#0x10
mov es,ax
xor bx,bx !! 緩沖區(qū)地址段內(nèi)偏移量置零;
jmp rp_read !! 繼續(xù)讀操作;
read_track:
pusha !! 將寄存器ax,cx,dx,bx,sp,bp,si,di壓入堆棧;
pusha
mov ax,#0xe2e ! loading... message 2e = . !! 顯示一個(gè).
mov bx,#7
int 0x10
popa
mov dx,track !! track = 當(dāng)前磁道;
mov cx,sread
inc cx !! cl = 扇區(qū)號(hào),要讀的起始扇區(qū);
mov ch,dl !! ch = 磁道號(hào)的低8位;
mov dx,head !!
mov dh,dl !! dh = 當(dāng)前磁頭號(hào);
and dx,#0x0100 !! dl = 驅(qū)動(dòng)器號(hào)(0);
mov ah,#2 !! 功能2(讀),es:bx指向讀數(shù)據(jù)緩沖區(qū);
push dx ! 為出錯(cuò)轉(zhuǎn)儲(chǔ)保存寄存器的值到堆棧上;
push cx
push bx
push ax
int 0x13
jc bad_rt !! 如果出錯(cuò),則跳轉(zhuǎn);
add sp, #8 !! 清(放棄)堆棧上剛推入的4個(gè)寄存器值;
popa
ret
bad_rt: push ax ! 保存出錯(cuò)碼;
call print_all ! ah = error, al = read;
xor ah,ah
xor dl,dl
int 0x13
add sp,#10
popa
jmp read_track
print_all:
mov cx,#5 ! 出錯(cuò)碼 + 4個(gè)寄存器
mov bp,sp
print_loop:
push cx ! 保存剩余的計(jì)數(shù)值
call print_nl ! 為了增強(qiáng)閱讀性,打印換行
cmp cl, #5
jae no_reg ! 看看是否需要寄存器的名稱
mov ax,#0xe05 + A - l
sub al,cl
int 0x10
mov al,#X
int 0x10
mov al,#:
int 0x10
no_reg:
add bp,#2 ! 下一個(gè)寄存器
call print_hex ! 打印值
pop cx
loop print_loop
ret
print_nl: !! 打印回車換行。
mov ax,#0xe0d ! CR
int 0x10
mov al,#0xa ! LF
int 0x10
ret
print_hex:
mov cx, #4 ! 4個(gè)十六進(jìn)制數(shù)字
mov dx, (bp) ! 將(bp)所指的值放入dx中
print_digit:
rol dx, #4 ! 循環(huán)以使低4比特用上 !! 取dx的高4比特移到低4比特處。
mov ax, #0xe0f ! ah = 請(qǐng)求的功能值,al = 半字節(jié)(4個(gè)比特)掩碼。
and al, dl !! 取dl的低4比特值。
add al, #0x90 ! 將al轉(zhuǎn)換為ASCII十六進(jìn)制碼(4個(gè)指令)
daa !! 十進(jìn)制調(diào)整
adc al, #0x40 !! (adc dest, src ==>; dest := dest +src + c )
daa
int 0x10
loop print_digit
ret
kill_motor:
push dx
mov dx,#0x3f2
xor al,al
outb
pop dx
ret
!! 數(shù)據(jù)區(qū)
sectors:
.word 0 !! 當(dāng)前每磁道扇區(qū)數(shù)。(36||18||15||9)
disksizes: !! 每磁道扇區(qū)數(shù)表
.byte 36, 18, 15, 9
msg1:
.byte 13, 10
.ascii "Loading"
.org 497 !! 從boot程序的二進(jìn)制文件的497字節(jié)開始
setup_sects:
.byte SETUPSECS
root_flags:
.word CONFIG_ROOT_RDONLY
syssize:
.word SYSSIZE
swap_dev:
.word SWAP_DEV
ram_size:
.word RAMDISK
vid_mode:
.word SVGA_MODE
root_dev:
.word ROOT_DEV
boot_flag: !! 分區(qū)啟動(dòng)標(biāo)志
.word 0xAA55
愛華網(wǎng)



