操作系统(一)控制系统启动

Jan 于 2024-09-08 发布 浏览量

操作系统(一)控制系统启动

实验内容

阅读《Linux 内核完全注释》的第 6 章,对计算机和 Linux 0.11 的引导过程进行初步的了解; 按照下面的要求改写 0.11 的引导程序 bootsect.s 有兴趣同学可以做做进入保护模式前的设置程序 setup.s。

改写 bootsect.s 主要完成如下功能:

bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)

改写 setup.s 主要完成如下功能:

bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行”Now we are in SETUP”。 setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。 setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。

实验步骤

修改bootsect.s文件

entry _start
_start:
    !使用 0x10 号中断读取光标位置
    mov ah,#0x03
    xor bh,bh
    int 0x10
 
    !显示字符串 “Hello OS world, my name is JJT”
    !cx 为要显示字符串的长度(除字符串外还有3个换行+1个回车)
    mov cx,#36
    mov bx,#0x0007
 
    ! es:bp 为显示字符串的地址
    mov bp,#msg1
    mov ax,#0x07c0
    mov es,ax
    mov ax,#0x1301
    int 0x10
 
!设置一个无限循环
inf_loop:
    jmp inf_loop
 
!设置显示字符串
msg1:
    !回车+换行
    .byte   13,10
    .ascii  "Hello OS world, my name is JJT"
    !两对 “回车+换行”
    .byte   13,10,13,10
 
!boot_flag 必须在最后两个字节
.org 510
!设置引导扇区标记 0xAA55(必须设置,否则无法成功引导)
boot_flag:
    .word   0xAA55

设置开机引导字符

image

 #汇编 bootsect.s 为 16 位目标文件 bootsect.o
 as86 -0 -a -o bootsect.o bootsect.s
 
 #将目标文件 bootsect.o 链接成可执行文件 bootsect,这是引导扇区的最终可执行镜像
 ld86 -0 -s -o bootsect bootsect.o
 
 #将 bootsect 文件的一部分(跳过前 32 字节)复制到 Image 文件,创建一个最终可用的引导扇区镜像。
 dd bs=1 if=bootsect of=Image skip=32
 cp ./Image ../Image
 ../../run


修改bootsect.s文件

SETUPLEN = 2            ! 要读取的扇区数
SETUPSEG = 0x07e0       ! setup 读入内存后的起始地址,这里 bootsect 没有将自己挪动到 0x90000 处,所以setup=0x07e00

entry _start
_start:
    mov ah, #0x03       ! 第 0x10 号中断例程中的 0x03 号子程序,功能为获取光标位置
    xor bh, bh
    int 0x10

    mov cx, #23         ! 显示字符串的长度
    mov bx, #0x0002     ! bh=第 0 页,bl=文字颜色属性 2
    mov bp, #msg1
    mov ax, #0x07c0
    mov es, ax          ! es:bp 是将要显示的字符串的地址
    mov ax, #0x1301     ! ah=13h 写字符串,al=01 移动光标
    int 0x10


load_setup:
    mov dx, #0x0000                 ! dh=磁头号或面号 dl=驱动器号,软驱从0开始,硬盘从80h开始
    mov cx, #0x0002                 ! ch=磁道号 cl=扇区号
    mov bx, #0x0200                 ! es:bx 指向接收从扇区读入数据的内存区
    mov ax, #0x0200 + SETUPLEN      ! ah=int 13h 的功能号(2 表示读扇区) al=读取的扇区数
    int 0x13                        ! int 13h 是 BIOS 提供的访问磁盘的中断例程

    jnc ok_load_setup               ! 读入成功则跳转
    mov dx, #0x0000
    mov ax, #0x0000                 ! 软驱、硬盘有问题时,会复位软驱
    int 0x13
    jmp load_setup                  ! 重新循环,再次尝试读取

ok_load_setup:
    jmpi 0, SETUPSEG                ! 段间跳转指令 ip=0, cs=SETUPSEG


msg1:                   ! len = 3换行 + 3回车 + 字符串长度
    .byte 13, 10        ! 换行 + 回车
    .ascii "JJT is booting..."
    .byte 13, 10, 13, 10


.org 510
boot_flag:
    .word 0xAA55        ! 设置引导扇区标记 0xAA55

修改setup.s

INITSEG = 0x9000        ! setup.s 将获得的硬件参数放在内存的 0x90000 处

entry _start
_start:
! 显示字符串 "Now we are in SETUP"
    mov ah, #0x03       ! 第 0x10 号中断例程中的 0x03 号子程序,功能为获取光标位置
    xor bh, bh
    int 0x10

    mov cx, #25         ! 显示字符串的长度
    mov bx, #0x0002     ! bh=第 0 页,bl=文字颜色属性 2
    mov bp, #msg2
    mov ax, cs
    mov es, ax          ! es:bp 是将要显示的字符串的地址
    mov ax, #0x1301     ! ah=13h 写字符串,al=01 移动光标
    int 0x10


! 获取基本硬件参数
    mov ax, #INITSEG
    mov ds, ax          ! 设置 ds = 0x9000

    ! 读取光标的位置并写入 0x90000 处
    mov ah, #0x03       ! 读入光标位置
    xor bh, bh
    int 0x10
    mov [0], dx         ! 将获取的光标位置写入 ds:[0]=0x90000 处

    ! 读取内存大小并写入内存中
    mov ah, #0x88
    int 0x15
    mov [2], ax         ! 将内存大小写入 ds:[2]=0x90002 处

    ! 从 0x41 处拷贝 16 个字节(磁盘参数表)
    ! 在 PC 机中 BIOS 设定的中断向量表中 int 0x41 的中断向量位置(4*0x41 = 0x0000:0x0104)存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。
    mov ax, #0x0000
    mov ds, ax
    lds si, [4 * 0x41]
    mov ax, #INITSEG
    mov es, ax
    mov di, #0x0004
    mov cx, #0x10       ! 重复 16 次,因为每个硬盘参数表有 16 个字节大小。
    rep
    movsb


! 准备打印参数
    mov ax, cs
    mov es, ax
    mov ax, #INITSEG
    mov ds, ax
    mov ss, ax
    mov sp, #0xFF00

    ! 打印光标的位置
    mov ah, #0x03
    xor bh, bh
    int 0x10
    mov cx, #18
    mov bx, #0x0002
    mov bp, #msg_cursor
    mov ax, #0x1301
    int 0x10
    mov dx, [0]
    call print_hex      ! 调用 print_hex 显示信息

    ! 打印内存大小
    mov ah, #0x03
    xor bh, bh
    int 0x10
    mov cx, #14
    mov bx, #0x0002
    mov bp, #msg_memory
    mov ax, #0x1301
    int 0x10
    mov dx, [2]
    call print_hex      ! 调用 print_hex 显示信息
    ! 添加内存单位 KB
    mov ah, #0x03
    xor bh, bh
    int 0x10
    mov cx, #2
    mov bx, #0x0002
    mov bp, #msg_kb
    mov ax, #0x1301
    int 0x10

    ! 打印柱面数
    mov ah, #0x03
    xor bh, bh
    int 0x10
    mov cx, #7
    mov bx, #0x0002
    mov bp, #msg_cyles
    mov ax, #0x1301
    int 0x10
    mov dx, [0x4 + 0x0]
    call print_hex      ! 调用 print_hex 显示信息

    ! 打印磁头数
    mov ah, #0x03
    xor bh, bh
    int 0x10
    mov cx, #8
    mov bx, #0x0002
    mov bp, #msg_heads
    mov ax, #0x1301
    int 0x10
    mov dx, [0x4 + 0x2]
    call print_hex      ! 调用 print_hex 显示信息

    ! 打印扇区
    mov ah, #0x03
    xor bh, bh
    int 0x10
    mov cx, #10
    mov bx, #0x0002
    mov bp, #msg_sectors
    mov ax, #0x1301
    int 0x10
    mov dx, [0x4 + 0x0e]
    call print_hex      ! 调用 print_hex 显示信息
    call print_nl       ! 打印换行回车

inf_loop:
    jmp inf_loop        ! 设置一个无限循环


! 以 16 进制方式打印栈顶的 16 位数
print_hex:
    mov cx, #4          ! 循环的次数,一个 dx 寄存器有 16 位,每 4 位显示一个 ASCII 字符,因此需要循环 4 次
print_digit:
    rol dx, #4          ! 循环左移,将 dx 的高 4 位移到低 4 位处
    mov ax, #0xe0f      ! ah=0x0e为int 0x10的子程序0x0e(显示一个字符串) al=要显示字符的 ASCII 码
    and al, dl          ! 取 dl 的低 4 位,通过与运算放入 al 中
    add al, #0x30       ! 数字 + 0x30 == 对应的 ASCII 码
    cmp al, #0x3a       ! 比较指令,仅对标志寄存器位有影响
    jl outp             ! jl 小于跳转
    add al, #0x07       ! a~f 是 字符 + 0x37 == 对应的 ASCII 码
outp:
    mov bx, #0x0002
    int 0x10
    loop print_digit
    ret
print_nl:
    mov ax, #0xe0d
    int 0x10            ! 打印回车
    mov al, #0xa
    int 0x10            ! 打印换行
    ret


! 提示信息
msg2:                   ! len = 3换行 + 3回车 + 字符串长度
    .byte 13, 10        ! 换行 + 回车
    .ascii "Now we are in SETUP"
    .byte 13, 10, 13, 10
msg_cursor:
    .byte 13, 10
    .ascii "Cursor position:"
msg_memory:
    .byte 13,10
    .ascii "Memory Size:"
msg_kb:
    .ascii "KB"
msg_cyles:
    .byte 13,10
    .ascii "Cyls:"
msg_heads:
    .byte 13,10
    .ascii "Heads:"
msg_sectors:
    .byte 13,10
    .ascii "Sectors:"


.org 510
boot_flag:
    .word 0xAA55

为tools/build.c添加一些注释

                        die("Write call failed");
                i += c;
        }

//      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'");
//      for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
//              if (write(1,buf,c)!=c)
//                      die("Write call failed");
//      close(id);
//      fprintf(stderr,"System is %d bytes.\n",i);
//      if (i > SYS_SIZE*16)
//              die("System is too big");
        return(0);
}

cd path/oslab/linux-0.11
make BootImage
#确保出现sync
../run

ps: 实验完成后请恢复初始化,不然下个实验会受影响

# 删除原来的文件
cd path/oslab
sudo rm -rf ./*

# 重新解压确保代码初始化
tar -zxvf hit-oslab-linux-20110823.tar.gz 

作于2024年09月08日