修改系统引导程序

一, 实验内容

    改写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

 运行结果:

posted on 2015-08-19 09:46  艾翔飞  阅读(3545)  评论(1编辑  收藏  举报

导航