好了,終於來到具體的講解了,其實這個彙編文件,我已經看過好幾次了,每次看都很新鮮,每次看都會理解得更深入(可能這就是所謂的"溫故而知新"吧)。本人的記性十分差,看過的東西、寫過的代碼都很快就被忘記,正如這彙編文件,每次看都很新鮮。所以我有必要把每個點都寫得清清楚楚,一來可以讓自己能記錄下容易被忘記的部份,而且還可以讓初學者很基本地瞭解整個過程(那怕只是一句彙編語句)。
正如我前面一章提到過,bootsect.s主要是做搬運工,即把setup.s和內核部份(內核部份在編譯時把head.s也編譯到裡頭了,在內核部份的前端)搬到內存中。由於整個文件比較長,所以我將會將其拆分為一段一段講解,也可以當作是功能劃分。下邊代碼注釋中一部份來自本人理解而寫出的,一部份來自《Linux内核完全注释》。
宏定義:
也許這名稱不太貼切,不過這裡確實是定義了一些後面會用到,卻不會被當作變量的數據,就像C語言的寵定義,編譯時,宏會被替換成具體數據。
1 ! 2 ! SYS_SIZE is the number of clicks (16 bytes) to be loaded. 3 ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current 4 ! versions of linux 5 ! 6 SYSSIZE = 0x3000 ! 指編譯連接后的system 模組大小。SYS_SIZE 是段地址。
! 0x3000 實際地址為 0x30000(即 192kb),足夠存放當前版本內核。
7 ! 8 ! bootsect.s (C) 1991 Linus Torvalds 9 ! 10 ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves 11 ! iself out of the way to address 0x90000, and jumps there. 12 ! 13 ! It then loads 'setup' directly after itself (0x90200), and the system 14 ! at 0x10000, using BIOS interrupts. 15 ! 16 ! NOTE! currently system is at most 8*65536 bytes long. This should be no 17 ! problem, even in the future. I want to keep it simple. This 512 kB 18 ! kernel size should be enough, especially as this doesn't contain the 19 ! buffer cache as in minix 20 ! 21 ! The loader has been made as simple as possible, and continuos 22 ! read errors will result in a unbreakable loop. Reboot by hand. It 23 ! loads pretty fast by getting whole sectors at a time whenever possible. 24 25 .globl begtext, begdata, begbss, endtext, enddata, endbss 26 .text 27 begtext: 28 .data 29 begdata: 30 .bss 31 begbss: 32 .text 33 34 SETUPLEN = 4 ! nr of setup-sectors !setup.s 所占的扇區數 35 BOOTSEG = 0x07c0 ! original address of boot-sector !bootsect.s 起始地址(段地址,下同)(被BIOS搬到此地址) 36 INITSEG = 0x9000 ! we move boot here - out of the way !bootsect.s 將會被搬到的內存起始地址 37 SETUPSEG = 0x9020 ! setup starts here !setup.s 起始地址 38 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). !內核被載入的內存地址 39 ENDSEG = SYSSEG + SYSSIZE ! where to stop loading !內核載入結束地址 40 41 ! ROOT_DEV: 0x000 - same type of floppy as boot. 42 ! 0x301 - first partition on first drive etc 43 ROOT_DEV = 0x306 ! 指定根文件系统设备是第2个硬盘的第1个分区。这是Linux老式的硬盘命名方式,具体值的含义如下: ! 设备号=主设备号*256 + 次设备号(也即dev_no = (major << 8) + minor) ! (主设备号:1-内存, 2-磁盘, 3-硬盘, 4-ttyx, 5-tty, 6-并行口, 7-非命名管道) ! 0x300 - /dev/hd0 - 代表整个第1 个硬盘; ! 0x301 - /dev/hd1 - 第1 个盘的第1 个分区; ! ... ! 0x304 - /dev/hd4 - 第1 个盘的第4 个分区; ! 0x305 - /dev/hd5 - 代表整个第2 个硬盘; ! 0x306 - /dev/hd6 - 第2 个盘的第1 个分区; ! ... ! 0x309 - /dev/hd9 - 第2 个盘的第4 个分区; ! 从linux 内核0.95 版后已经使用与现在相同的命名方法了。
以上的都只是一些定義所以沒有什麽好說的,不過這些定義后面都要用,所以得先知道是怎麼一回事,才能更好得往下看。
bootsect模塊搬遷:
這裡除了代碼搬運外,也做了一定段寄存器的設置,以及棧的設置。
1 entry start !告知连接程序,程序从start 标号开始执行。 2 start: 3 mov ax,#BOOTSEG 4 mov ds,ax !把BOOTSEG(0x07c0)寫入DS(數據段寄存器) 5 mov ax,#INITSEG 6 mov es,ax !把INITSEG(0x9000)寫入ES(附加段寄存器) 7 mov cx,#256 !把cx寫為256(用於循環,由於寄存器是16位的,所以循環256次,可以移動512 bytes,即一個扇區的大小) !清除數據及其標誌(網上有人說這操作 比直接賦0 再清標誌要快,其實這裡所說的標誌我還不太知道哪裡,只知道在CR中的某個或某些標誌)
8 sub si,si !源地址 ds:si = 0x07c0:0x0000 9 sub di,di !目的地址 es:di = 0x9000,:0x0000 10 rep !這兩句語句很簡單,卻很難找到明確的說明,其實可以寫成rep movw,表示重複操作movw ,cx次(256次)。 11 movw !在網上movw也是有參數的,如果不帶參數,表示有默認值,即 movw es:di, ds:si,
!意思是把ds:si的數據移動到es:di中,並且每次操作后di,si會自動加1 12 jmpi go,INITSEG !把bootsect移動到0x90000后,把IP指針指向go.(由於數據是移動過去的,
!所以偏移地址是一樣的,這裡就跳到移動后的go的偏移地址) 13 go: mov ax,cs 14 mov ds,ax 15 mov es,ax 16 ! put stack at 0x9ff00. 17 mov ss,ax !將ds、es和ss都置成移動后的代碼所在的段處(0x9000) 18 mov sp,#0xFF00 ! arbitrary value >>512
! 由于代码段移动过了,所以要重新设置堆栈段的位置。
! sp 只要指向远大于512 偏移(即地址0x90200)处都可以。
! 因为从0x90200 地址开始处还要放置setup 程序,
! 而此时setup 程序大约为4 个扇区,因此sp 要指向大
! 于(0x200 + 0x200 * 4 + 堆栈大小)处。
! 這裡可能需要說明一下棧的結構。由於內存的高地址是棧底,
! 低地址是棧頂,而sp存放的是棧頂指針,由於現在棧沒有數據,
! 所以,棧底等於棧頂,實際的棧空間為0xFF00 - (0x200 +
! 0x200 * 4) = 0xF500
setup模塊讀取:
1 ! load the setup-sectors directly after the bootblock. 2 ! Note that 'es' is already set up. 3 4 load_setup: 5 mov dx,#0x0000 ! drive 0, head 0 6 mov cx,#0x0002 ! sector 2, track 0 7 mov bx,#0x0200 ! address = 512, in INITSEG ! 0x90200(0x9000:0x0200) 8 mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors 9 int 0x13 ! read it
! int 0x13是BIOS的磁盤服務中斷。這里使用了Read Disk Sectors(AH = 2),用於讀取磁盤扇區。操作配置如下:
! AH = 02
! AL = number of sectors to read (1-128 dec.)
! CH = track/cylinder number (0-1023 dec., see below)
! CL = sector number (1-17 dec.)
! DH = head number (0-15 dec.)
! DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1)
! ES:BX = pointer to buffer
! on return:
! AH = status (see INT 13,STATUS)
! AL = number of sectors read
! CF = 0 if successful
! = 1 if error
! |F|E|D|C|B|A|9|8|7|6|5-0| CX
! | | | | | | | | | | `----- sector number
! | | | | | | | | `--------- high order 2 bits of track/cylinder
! `------------------------ low order 8 bits of track/cyl number
10 jnc ok_load_setup ! ok - continue ! 如果CF不為1,跳到ok_load_setup處執行,否則執行下邊的操作。 11 mov dx,#0x0000 12 mov ax,#0x0000 ! reset the diskette 13 int 0x13 ! Reset Disk System(AH = 0)
! AH = 00
! DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1)
! on return:
! AH = disk operation status (see INT 13,STATUS)
! CF = 0 if successful
! = 1 if error
14 j load_setup ! 跳回load_setup,重新讀取扇區(如果沒有磁盤或磁盤損壞,將進入死循環)
載入內核:
1 ok_load_setup: 2 3 ! Get disk drive parameters, specifically nr of sectors/track 4 5 mov dl,#0x00 6 mov ax,#0x0800 ! AH=8 is get drive parameters 7 int 0x13 ! Get Current Drive Parameters
! AH = 08
! DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1)
! on return:
! AH = status (see INT 13,STATUS)
! BL = CMOS drive type
! 01 - 5¬ 360K 03 - 3« 720K
! 02 - 5¬ 1.2Mb 04 - 3« 1.44Mb
! CH = cylinders (0-1023 dec. see below)
! CL = sectors per track (see below)
! DH = number of sides (0 based)
! DL = number of drives attached
! ES:DI = pointer to 11 byte Disk Base Table (DBT)
! CF = 0 if successful
! = 1 if error
8 mov ch,#0x00 9 seg cs ! 表示下一條語句的操作數在cs段寄存器所指的段中 10 mov sectors,cx ! 暫存cx(ch被置0,cl數據【扇區/磁道】來自上一操作)的數據到變量sectors中 11 mov ax,#INITSEG 12 mov es,ax ! 把es段寄存器設為INITSEG(0x9000) 13 14 ! Print some inane message 15 16 mov ah,#0x03 ! read cursor pos 17 xor bh,bh ! 異或清零 18 int 0x10 ! 獲取指針位置
! AH = 03
! BH = video page
! on return:
! CH = cursor starting scan line (low order 5 bits)
! CL = cursor ending scan line (low order 5 bits)
! DH = row
! DL = column 19 20 mov cx,#24 ! 設置24個字符串長度 21 mov bx,#0x0007 ! page 0, attribute 7 (normal) 22 mov bp,#msg1 ! 把字符串msg1(msg1是一個變量)地址賦給bp 23 mov ax,#0x1301 ! write string, move cursor 24 int 0x10 ! 顯示字符串
! AH = 13h
! AL = write mode (see bit settings below)
! = 0 string is chars only, attribute in BL, cursor not moved
! = 1 string is chard only, attribute in BL, cursor moved
! = 2 string contains chars and attributes, cursor not moved
! = 3 string contains chars and attributes, cursor moved
! BH = video page number
! BL = attribute if mode 0 or 1 (AL bit 1=0)
! CX = length of string (ignoring attributes)
! DH = row coordinate
! DL = column coordinate
! ES:BP = pointer to string
! Bit settings for write mode (register AL):
! |7|6|5|4|3|2|1|0| AL
! | | | | | | | `---- 0=don't move cursor, 1=move cursor
! | | | | | | `----- 0=BL has attributes, 1=string has attributes
! `---------------- unused
! returns nothing
25 26 ! ok, we've written the message, now 27 ! we want to load the system (at 0x10000) 28 29 mov ax,#SYSSEG 30 mov es,ax ! segment of 0x010000 31 call read_it ! 讀取磁盤上的system模塊(包括head和內核),es為輸入參數 32 call kill_motor ! 關閉驅動器馬達,就可以知道驅動器狀態了
獲取根設備,并轉向setup模塊:
1 ! After that we check which root-device to use. If the device is 2 ! defined (!= 0), nothing is done and the given device is used. 3 ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending 4 ! on the number of sectors that the BIOS reports currently. 5 6 seg cs 7 mov ax,root_dev 8 cmp ax,#0 ! 判斷root_dev是否為0,在前面定義部份有說明 9 jne root_defined ! 如果不等於,跳到root_defined處執行。 10 seg cs 11 mov bx,sectors ! 把扇區數(扇區/磁道)放入bx中。 12 mov ax,#0x0208 ! /dev/ps0 - 1.2Mb !把0x0208寫入ax,用於過后保存到root_dev(前提是判斷條件成立),下同 13 cmp bx,#15 ! 如果扇區數等於15,跳到root_defined,下同 14 je root_defined 15 mov ax,#0x021c ! /dev/PS0 - 1.44Mb 16 cmp bx,#18 17 je root_defined 18 undef_root: 19 jmp undef_root ! 如果是未定義root,將進入死循環 20 root_defined: 21 seg cs 22 mov root_dev,ax ! 把ax的值賦給root_dev變量。 23 24 ! after that (everyting loaded), we jump to 25 ! the setup-routine loaded directly after 26 ! the bootblock: 27 28 jmpi 0,SETUPSEG ! 跳到SETUPSEG(0x9000),偏移值為0處,運行。
好了到這里主流程已經結束了,下面將講bootsect.s剩下的部份,主要是“函數”的定義及變量的定義。
內核載入“函數”定義:
這裡可複雜了,不是因為太長,而是因為跳轉得比較多,會很容易被搞混亂了 。
1 ! This routine loads the system at address 0x10000, making sure 2 ! no 64kB boundaries are crossed. We try to load it as fast as 3 ! possible, loading whole tracks whenever we can. 4 ! 5 ! in: es - starting address segment (normally 0x1000) 6 !
! “局部變量”定義 7 sread: .word 1+SETUPLEN ! sectors read of current track ! 已讀扇區數,由於一開始已經讀出了bootsect和setup模塊,所以初值要賦為1 + SETUPLEN 8 head: .word 0 ! current head ! 磁頭號 9 track: .word 0 ! current track ! 磁道號 10 11 read_it:
! 测试输入的段值。从盘上读入的数据必须存放在位于内存地址64KB 的边界开始处,否则进入死循环。
! 清bx 寄存器,用于表示当前段内存放数据的开始位置。
12 mov ax,es ! 用ax獲取es寄存器的值。 13 test ax,#0x0fff 14 die: jne die ! es must be at 64kB boundary 15 xor bx,bx ! bx is starting address within segment 16 rp_read:
! 判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),
! 如果不是就跳转至下面ok1_read 标号处继续读数据。否则退出子程序返回。
17 mov ax,es ! 由於一個段地址只能放64kb(0xffff),所以這裡會跨多個段操作。
! SYSSIZE = 0x3000,所以需要3次迭代(es每次累加0x1000,執行到這裡,內存地址應為0x1000) 18 cmp ax,#ENDSEG ! have we loaded all yet? ! 是否已经加载了全部数据? 19 jb ok1_read ! 如果未載入完畢,跳到ok1_read 20 ret ! 返回調用處 21 ok1_read: 22 seg cs 23 mov ax,sectors ! 用ax獲取每磁道的扇區數 24 sub ax,sread ! sread為已經讀扇區數(一個磁道內),ax - sread 為未讀扇區數 25 mov cx,ax 26 shl cx,#9 ! cx × 512(0x200是扇區大小,一個扇區512bytes),表示剩餘未讀的字節數 27 add cx,bx ! bx 是當前偏移地址(來自上一次操作)。結果是指 已經讀取的數量 + 未讀取的數量
! 用於判斷當前段的剩餘空間是否能夠把剩餘數據讀入,(因為段只有64kb,system卻有192kb)
! 以便段地址的變化。 28 jnc ok2_read ! 如果沒有溢出,或者 29 je ok2_read ! 如果為0(65536 = 0x10000,在16位無符號數表達時為0),跳到ok2_read 30 xor ax,ax ! ax清零 31 sub ax,bx ! 由於寄存器是16位無符號的,所以0 - bx = 65536 - bx,結果為段內剩餘內存數 32 shr ax,#9 ! ax ÷ 512 ,表示段內剩餘內存可以存放的扇區數 33 ok2_read: 34 call read_track ! 讀取一個磁道 35 mov cx,ax ! 把段內剩餘可存放扇區數賦給cx 36 add ax,sread ! ax加上已讀扇區數 37 seg cs 38 cmp ax,sectors ! 與每磁道的扇區數作比較 39 jne ok3_read ! 如果不等就跳到ok3_read 40 mov ax,#1 ! ax置0,用於磁頭號轉換, 41 sub ax,head ! 由於軟盤只有兩面,所以磁頭號只有0和1兩個 42 jne ok4_read ! 如果被設磁頭號與當磁頭號不同,并跳到ok4_read 43 inc track ! 磁道號加1 44 ok4_read: 45 mov head,ax ! 把ax賦給變量head 46 xor ax,ax ! ax清0 47 ok3_read: 48 mov sread,ax ! 當前的ax為 已讀扇區數 + 段內還可存放扇區數 49 shl cx,#9 ! 把cx轉成要存放字節數 50 add bx,cx ! cx + 當前段內偏移地址 51 jnc rp_read ! 如果沒有溢出,跳到rp_read執行 52 mov ax,es ! ax獲取當前段地址 53 add ax,#0x1000 ! ax + 0x1000,加0x1000就是指向下一段(因為段內只能尋址64kb,即0~0xffff,只能把段寄存器指向下一段才能被尋址) 54 mov es,ax ! es段地址被修改 55 xor bx,bx ! bx(段內偏移地址)清0 56 jmp rp_read ! 跳到rp_read繼續執行 57 58 read_track: 59 push ax ! 數據保護,入棧 60 push bx 61 push cx 62 push dx 63 mov dx,track ! dx獲取當前磁道號 64 mov cx,sread ! cx獲取當前已讀扇區數(扇區號) 65 inc cx ! 已讀扇區數自增1 66 mov ch,dl ! 把磁道號放到ch中 67 mov dx,head ! 把磁頭號放到dx中 68 mov dh,dl ! 把dl(磁頭號)放到dh中 69 mov dl,#0 ! 把dl置0 70 and dx,#0x0100 ! 對dx進行與操作 71 mov ah,#2 ! 置功能號為2,即為讀取扇區 72 int 0x13 ! 前面這裡有提到,或在文章最后的網址中查找 73 jc bad_rt ! 如果讀取失敗,跳到bad_rt 74 pop dx 75 pop cx 76 pop bx 77 pop ax ! 恢復數據,出棧 78 ret ! 返回調用處 79 bad_rt: 80 mov ax,#0 81 mov dx,#0 82 int 0x13 ! 復位磁盤系統,前面這裡有提到,或在文章最后的網址查找 83 pop dx 84 pop cx 85 pop bx 86 pop ax ! 恢復數據,出棧 87 jmp read_track ! 執行完畢,跳到read_track重讀扇區
不知道這樣注釋夠不夠清晰,這裡基本把最簡單的語句也注釋了(希望不會被說大累贅吧),上面的理解大概就這樣,如果有不理解的可以提出哦(有錯請指正),這裡就注釋都挺耗時的,不過學習本來就是個耗時的過程,如果找到我方法可以把所耗的時間縮短。
停止驅動電機及變量聲明:
1 /* 2 * This procedure turns off the floppy drive motor, so 3 * that we enter the kernel in a known state, and 4 * don't have to worry about it later. 5 */ 6 kill_motor: 7 push dx ! dx數據保護 8 mov dx,#0x3f2 ! 软驱控制卡的驱动端口,只写。 9 mov al,#0 ! A 驱动器,关闭FDC,禁止DMA 和中断请求,关闭马达。 10 outb ! 将al中的内容输出到dx指定的端口去。(不知道爲什麽這個指令沒找到具體的說明,
! 不知道是否已經被修改了,out倒是有) 11 pop dx ! dx數據恢復 12 ret ! 返回調用 13 14 sectors: 15 .word 0 ! sectors聲明為16位的變量 16 17 msg1: 18 .byte 13,10 ! (字節),換行、回車,下同 19 .ascii "Loading system ..." ! ascii碼值為"Loading system ...",其中占了18個字節 20 .byte 13,10,13,10 ! 21
! 寫入根設備號(0x901FC地址為如下數據,這代碼空間(0x90000~0x901FF)將會被覆蓋,剩下被覆蓋部份在setup模塊被執行,可見下一節一開始的表) 22 .org 508 ! 表示下面语句从地址508(0x1FC)开始,所以root_dev在启动扇区的第508开始的2个字节中。 23 root_dev: 24 .word ROOT_DEV ! root_dev聲明為16位的變量 25 boot_flag: 26 .word 0xAA55 ! 硬盤有效標誌(與MBR有關),要在這里瞭解一下MBR, 27 ! http://zh.wikipedia.org/wiki/MBR 28 .text 29 endtext: 30 .data 31 enddata: 32 .bss 33 endbss:
到此,bootsect.s文件的講述完成。下一步將進入setup.s的講述。
由於方便瞭解,本文的分段是按順序劃分的,所以可以把各段連接回去。本文使用分段及注釋的方式講述,這樣可以更好地瞭解各個小區域的功能,由於採用了此方式,所以分段也沒有做總結(因為段標題基本就是其總結了)。如果有任何不瞭解及錯處,請提出,謝謝。