修改系統引導程序


一, 實驗內容

    改寫bootsect.s和setup.s, 完成如下主要功能:

    1, bootsect.s能夠在屏幕上打印一段提示信息"XXX is booting...", 其中XXX是你給自己的操作系統起的名字,例如LZJos、Sunix等.

    2, bootsect.s能夠完成setup.s的載入, 並跳轉到setup.s開始地址執行. 

    3, setup.s能夠像屏幕輸出一行信息 "Now we are in SETUP" 

    4, setup.s能獲取至少一個基本的硬件參數(如內存參數、顯卡參數、硬盤參數等),將其存放在內存的特定地址,並輸出到屏幕上。

    setup.s不再加載linux內核, 保持上述信息顯示到屏幕上即可

二, 實驗步驟

1, 完成bootsect的屏幕輸出功能

      由於不需要加載linux內核,所以就不需要原始的linux代碼那么復雜,比如: 將bootsect自身移動到0x90000處等操作,可以忽略的。

   要顯示字符串,那么字符串顯示到屏幕的哪里呢?當然是當前光標的位置了!所以第一步就要先讀取光標的位置,這可以利用10號中斷的3號子程序來完成。要顯示字符串,可以利用10號功能的13號子程序來完成,需要注意的是一定要邊顯示字符邊移動光標,最終的光標要移動到字符串的末尾處。最后要注意用0xAA55來標記引導扇區。代碼如下:

! 文件:bootsect.s

entry _start
_start:
! 首先利用10號中斷的3號功能來讀取光標位置 mov ah,#0x03 xor bh,bh int 0x10 ! 再利用10號中斷的13號功能顯示字符串 mov cx,#50 ! 加上回車和換行,字符串一共包含50個字符,所以設置cx為50 mov bx,#0x0007 mov bp,#msg1 mov ax,#0x07c0 mov es,ax ! es:bp=顯示字符串的地址 mov ax,#0x1301 int 0x10 Inf_loop: jmp Inf_loop ! 無限循環 ! msg1處放置要顯示的字符串 msg1: .byte 13,10 ! 換行+回車 .ascii "AXF OS is booting, my name is Aixiangfei ..." .byte 13,10,13,10 ! 兩對換行+回車 ! 下面是啟動盤具有有效引導扇區的標志. 僅供BIOS中的程序加載扇區時識別使用。 ! 它必須位於引導扇區的最后兩個字節中. .org 510 boot_flag: .word 0xAA55 ! 引導扇區的標記就是0XAA55

  編譯和運行:  進入~/oslab/linux-0.11/boot/目錄,編譯並連接:

as86 -0 -a -o bootsect.o bootsect.s
ld86 -0 -s -o bootsect bootsect.o

  參數說明:-0(注意:這是數字0,不是字母O)表示生成8086的16位目標程序,-a表示生成與GNU as和ld部分兼容的代碼,-s告訴鏈接器ld86去除最后生成的可執行文件中的符號信息。

  如果這兩個命令沒有任何輸出,說明編譯與鏈接都通過了。需要留意的生成的bootsect的大小是544字節,而引導程序必須要正好占用一個磁盤扇區,即512個字節。造成多了32個字節的原因是ld86產生的是Minix可執行文件格式,這樣的可執行文件處理文本段、數據段等部分以外,還包括一個Minix可執行文件頭部。所以最后必須要把這多余的32個字節刪掉,可以用linux自帶的工具dd來完成:

dd bs=1 if=bootsect of=Image skip=32

  去掉這32個字節后,將生成的文件拷貝到linux-0.11目錄下,並一定要命名為“Image”(注意大小寫)。然后就可以run了

     程序有可能需要不斷地修改調試,為了方便,可以將上述的一系列操作寫道一個shell腳本文件 cbootsect.sh 中:

#!/bin/sh

as86 -0 -a -o bootsect.o bootsect.s
ld86 -0 -s -o bootsect bootsect.o

dd bs=1 if=bootsect of=Image skip=32

cp Image ../

../../run

  運行結果:

 

2, setup的載入

  首先要確定setup是在磁盤的0磁道2扇區,linux 0.11中的setup占了4個扇區,而我們最后要寫的setup顯然沒有那么復雜,所以可以只用1個扇區就可以了。另外,由於bootsect位於0x7c00處,占用512個字節,所以可以將setup載入到0x7e00處。要想從磁盤中載入數據到內存,可以利用BIOS提供的13號中斷輕松完成。最后就直接用jumi指令跳轉到0x7e00處即可。對前面的bootsect.s擴展之后的完整代碼如下:

! 文件:bootsect.s

SETUPLEN = 1
SETUPSEG = 0x07e0

entry _start
_start: ! 首先利用10號中斷的3號功能來讀取光標位置 mov ah,#0x03 xor bh,bh int 0x10 ! 再利用10號中斷的13號功能顯示字符串 mov cx,#50 ! 加上回車和換行,字符串一共包含50個字符,所以設置cx為50 mov bx,#0x0007 mov bp,#msg1 mov ax,#0x07c0 mov es,ax ! es:bp=顯示字符串的地址 mov ax,#0x1301 int 0x10 load_setup: mov dx,#0x0000 ! 設置驅動器和磁頭(drive 0, head 0): 軟盤0磁頭 mov cx,#0x0002 ! 設置扇區號和磁道(sector 2, track 0):0磁頭、0磁道、2扇區 mov bx,#0x0200 ! 設置讀入的內存地址:BOOTSEG+address = 512,偏移512字節 mov ax,#0x0200+SETUPLEN ! 設置讀入的扇區個數(service 2, nr of sectors), ! SETUPLEN是讀入的扇區個數,Linux 0.11設置的是4, ! 我們不需要那么多,我們設置為1 int 0x13 ! 應用0x13號BIOS中斷讀入1個setup.s扇區 jnc ok_load_setup ! 讀入成功,跳轉到ok_load_setup: ok - continue mov dx,#0x0000 ! 軟驅、軟盤有問題才會執行到這里 mov ax,#0x0000 ! 否則復位軟驅 int 0x13 j load_setup ! 重新循環,再次嘗試讀取 ok_load_setup: jmpi 0,SETUPSEG ! msg1處放置要顯示的字符串 msg1: .byte 13,10 ! 換行+回車 .ascii "AXF OS is booting, my name is Aixiangfei ..." .byte 13,10,13,10 ! 兩對換行+回車 ! 下面是啟動盤具有有效引導扇區的標志. 僅供BIOS中的程序加載扇區時識別使用。 ! 它必須位於引導扇區的最后兩個字節中. .org 510 boot_flag: .word 0xAA55 ! 引導扇區的標記就是0XAA55

 

3, 完成setup的屏幕輸出功能

  這個很簡單,跟bootsect是一樣的。代碼如下:

! 文件:setup.s

entry _start
_start: ! 首先利用10號中斷的3號功能來讀取光標位置 mov ah,#0x03 xor bh,bh int 0x10 ! 再利用10號中斷的13號功能顯示字符串 mov cx,#26 mov bx,#0x0007 mov bp,#msg mov ax,cs mov es,ax mov ax,#0x1301 int 0x10 Inf_loop: jmp Inf_loop ! 無限循環 msg: .byte 13,10 .ascii "Now we are in SETUP." .byte 13,10,13,10 .org 510 boot_flag: .word 0xAA55

  編譯和與運行:現在有兩個文件都要編譯、鏈接。一個個手工編譯,效率低下,所以借助Makefile是最佳方式。linux 0.11中Makefile文件已經幫我們把這件事做好了。進入liux-0.11目錄后,使用命令:

$ make BootImage

  但是我們發現竟然出現了錯誤:

  原因:這是因為make根據Makefile的指引執行了tools/build.c,它是為生成整個內核的鏡像文件而設計的,沒考慮我們只需要bootsect.s和setup.s的情況。build.c從命令行參數得到bootsect、setup和system內核的文件名,將三者做簡單的整理后一起寫入Image。其中system是第三個參數(argv[3])。當“make all”或者“makeall”的時候,這個參數傳過來的是正確的文件名,build.c會打開它,將內容寫入Image。而“make BootImage”時,傳過來的是字符串"none"。所以,修改build.c的思路就是當argv[3]是"none"的時候,只寫bootsect和setup,忽略所有與system有關的工作,或者在該寫system的位置都寫上“0”。

  修改build.c文件很簡單,只需要把第178到183這幾行代碼刪除即可!

//    if ((id=open(argv[3],O_RDONLY,0))<0)
//        die("Unable to open 'system'");
//    if (read(id,buf,GCC_HEADER) != GCC_HEADER)
//        die("Unable to read header of 'system'");
//    if (((long *) buf)[5] != 0)
//        die("Non-GCC header of 'system'");

  run

  

  

4, 讀取硬件參數

  這個部分是最復雜的了。需要打印的硬件信息有:光標位置,內存大小,磁盤的柱面數,磁頭數,每磁道的扇區數。在這個實驗中,這些參數的信息可以保存在內存中的任意位置,在linux 0.11中,這些參數信息是被保存到了0x90000處,所以不妨跟linux 0.11一樣。

(1)獲取硬件參數

  獲得光標位置信息,這個很簡單,只需要調用13號中斷的3號子程序就可以得到,前面已經用過了的。

  獲得內存大小,可以調用用15號中斷的88號子程序得到,也很簡單。

  與磁盤相關的信息稍微復雜一點,這些信息被保存在0x0000:0x0104地址處的16個字節的中,這16個字節的信息叫做“磁盤參數表”。所以獲得磁盤信息的方法就是復制數據。

(2)數字轉字符

  現在已經將這些硬件參數取出來放在了0x90000處,接下來的工作是將這些參數顯示在屏幕上。這些參數都是一些無符號整數,所以需要做的主要工作是用匯編程序在屏幕上將這些整數用16進制的形式顯示出來。

  因為十六進制與二進制有很好的對應關系(每4位二進制數和1位十六進制數存在一一對應關系),顯示時只需將原二進制數每4位划成一組,按組求對應的ASCII碼送顯示器即可。ASCII碼與十六進制數字的對應關系為:0x30~0x39對應數字0~9,0x41~0x46對應數字a~f。從數字9到a,其ASCII碼間隔了7h,這一點在轉換時要特別注意。為使一個十六進制數能按高位到低位依次顯示,實際編程中,需對bx中的數每次循環左移一組(4位二進制),然后屏蔽掉當前高12位,對當前余下的4位(即1位十六進制數)求其ASCII碼,要判斷它是0~9還是a~f,是前者則加0x30得對應的ASCII碼,后者則要加0x37才行,最后送顯示器輸出。以上步驟重復4次,就可以完成bx中數以4位十六進制的形式顯示出來。

  因為在輸出的時候需要調用多次,所以最好把這個功能寫成一個函數print_bx,方便使用。既然要用到函數,故一定要先設置好棧。為了方便,還可以寫一個函數print_nl實現換行的功能。

INITSEG = 0x9000

entry _start
_start:
! 在顯示字符串之前必須先獲取當前光標的位置,這樣就可以把字符串顯示到當前光標處了
    mov    ah,#0x03
    xor    bh,bh
    int    0x10
    
! 利用10號中斷的13號功能打印字符串"Now we are in SETUP."
    mov    cx,#26
    mov    bx,#0x0007
    mov    bp,#msg1
    mov     ax,cs
    mov    es,ax
    mov    ax,#0x1301
    int    0x10

! 下面開始讀取一些硬件參數

    ! 讀入光標位置信息,保存到0x90000處
    mov    ax,#INITSEG
    mov    ds,ax
    mov    ah,#0x03
    xor    bh,bh
    int    0x10
    mov    [0],ds

    ! 讀入內存大小位置信息,保存到0x90002處
    mov    ah,#0x88
    int    0x15
    mov    [2],ax

    ! 從0x41處拷貝16個字節(磁盤參數表)到0x90004處
    mov    ax,#0x0000
    mov    ds,ax
    lds    si,[4*0x41]
    mov    ax,#INITSEG
    mov    es,ax
    mov    di,#0x0004
    mov    cx,#0x10
    rep       ! 重復16次
    movsb


! 先打印光標位置
    ! 打印字符串之前要先讀取光標位置,將字符串打印到當前光標處
    mov    ah,#0x03
    xor    bh,bh
    int    0x10

    ! 打印字符串 "Cursor POS:"
    mov    cx,#11
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg2
    mov    ax,#0x1301
    int    0x10    
    
    ! 調用打印函數,打印光標位置
    mov    ax,#0x9000
    mov    ds,ax
    mov    dx,0x0
    call    print_hex
    call    print_nl

! 打印內存大小
    ! 打印字符串"Memory SIZE:"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 讀取光標位置

    mov    cx,#12
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg3
    mov    ax,#0x1301
    int    0x10    


    ! 調用打印函數,打印內存大小信息
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x2
    call     print_hex


    ! 打印字符串"KB"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 讀取光標位置    
    
    mov    cx,#2
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg4
    mov    ax,#0x1301
    int    0x10    
    call    print_nl    

!打印柱面數
    ! 打印字符串"Cyls"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 讀取光標位置

    mov    cx,#5
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg5
    mov    ax,#0x1301
    int    0x10    
    

    ! 調用打印函數打印磁盤柱面數
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x4
    call    print_hex
    call    print_nl

! 打印磁頭數
    ! 打印字符串"Heads:"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 讀取光標位置

    mov    cx,#6
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg6
    mov    ax,#0x1301
    int    0x10    

    
    ! 調用打印函數打印磁盤磁頭數
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x6
    call    print_hex
    call    print_nl


! 打印每磁道扇區數
    ! 打印字符串"sectors"
    mov    ah,#0x03
    xor    bh,bh
    int    0x10    ! 讀取光標位置

    mov    cx,#8
    mov    bx,#0x0007
    mov    ax,cs
    mov    es,ax
    mov    bp,#msg7
    mov    ax,#0x1301
    int    0x10    

    ! 調用打印函數打印扇區數
    mov    ax,#0x9000    
    mov    ds,ax
    mov    dx,0x12
    call    print_hex
    call    print_nl

Inf_loop:
    jmp Inf_loop    ! 無限循環


! print_hex函數:將一個數字轉換為ascii碼字符,並打印到屏幕上
! 參數值:dx
! 返回值:無
print_hex:
    mov    cx,#4            ! 要打印4個十六進制數字,故循環4次
print_digit:
    rol    dx,#4            ! 循環以使低4比特用上 !! 取dx的高4比特移到低4比特處
    mov    ax,#0xe0f        ! ah = 請求的功能值,al = 半字節(4個比特)掩碼
    and    al,dl            ! 取dl的低4比特值
    add    al,#0x30        ! 給al數字加上十六進制0x30
    cmp    al,#0x3a    
    jl    outp            ! 如果是一個不大於十的數字
    add    al,#0x07    ! 如果是a~f,要多加7
outp:
    int    0x10
    loop    print_digit    ! 用loop重復4次
    ret

! 打印回車換行
print_nl:
    mov    ax,#0xe0d
    int    0x10
    mov    al,#0xa
    int    0x10
    ret

msg1:
    .byte 13,10
    .ascii "Now we are in SETUP."
    .byte 13,10,13,10

msg2:
    .ascii "Cursor POS:"

msg3:    
    .ascii "Memory SIZE:"

msg4:
    .ascii "KB"

msg5:    
    .ascii "Cyls:"

msg6:
    .ascii "Heads:"

msg7:
    .ascii "Sectors:"


.org 510
boot_flag:
    .word 0xAA55

 運行結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM