【哈工大_操作系统实验】Lab2 操作系统的引导

September 01, 2024

Github代码仓库链接

本节将更新哈工大《操作系统》课程第二个 Lab 实验 操作系统的引导。按照实验书要求,介绍了非常详细的实验操作流程,并提供了超级无敌详细的代码注释。文末附完整 bootsect.ssetup.s 标准答案代码以及超详细注释。

实验目的:

  • 熟悉 hit-oslab 实验环境;
  • 建立对操作系统引导过程的深入认识;
  • 掌握操作系统的基本开发过程;
  • 能对操作系统代码进行简单的控制,揭开操作系统的神秘面纱。

实验任务:

1、bootsect.s 能在屏幕上打印一段提示信息“XXX is booting...”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等 2、bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。 3、setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。

实验工具准备:

文件名 介绍
hit-操作系统实验指导书.pdf 哈工大OS实验指导书
Linux内核完全注释(修正版v3.0).pdf 赵博士对Linux v0.11 OS进行了详细全面的注释和说明
file1615.pdf BIOS 涉及的中断数据手册
hit-oslab-linux-20110823.tar.gz hit-oslab 实验环境
gcc-3.4-ubuntu.tar.gz Linux v0.11 所使用的编译器
Bochs 汇编级调试指令 bochs 基本调试指令大全
最全ASCII码对照表0-255 屏幕输出字符对照的 ASCII 码
x86_64 常用寄存器大全 x86_64 常用寄存器大全

一、bootsect.s 的屏幕输出功能

需要实现:bootsect.s 能在屏幕上打印一段提示信息“XXX is booting...”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等

参考赵博士《Linux 内核 0.11 完全注释(修正版 V3.0)》第六章,其中非常详细的解释了 Linux_v0.11 的启动引导过程。

就比如下面这张图,形象生动地概括了OS引动过程:

  1. 首先,80x86 架构CPU进入实模式(16位),从 0xfff0 开始自动执行代码,在地址0处初始化中断向量。
  2. 从系统软盘上将第一扇区 bootsect.s 程序读入物理地址 0x07c00 处(共512 bytes),并赋给 bootsect 控制权。
  3. 接下来 bootsect.s 将自身移动到 0x90000 处,为了腾出内存空间,以便给操作系统内核使用。
  4. 将 setup.s 加载到内存 0x90200 处,即 bootsect.s 512 bytes 之后,并赋给 setup.s 控制权。
  5. system 模块加载到 0x10000 处。(不能移动到0x00000处原因:因为在执行是 setup.s 模块时,需要利用 ROM BIOS 的中断调用来获取机器的参数,不能把中断向量表、BIOS数据区给覆盖掉)
  6. system 模块移动到 0x00000 处,进入保护模式(32位),并赋给 system 控制权。

在这里插入图片描述

1、编写 bootsect.s

需要注意的是,我们要从头开始编写 bootsect.s,而不是在 Linux0.11 的源码上进行修改,这样子无法深刻理解 bootsect 引导过程。

解压 hit-oslab-linux-20110823.tar.gz ,并命名为 os_lab_Lab1 ,在此源码基础上我们进一步完成实验。进入 os_lab_Lab1/linux-0.11/boot/ 文件夹下的 bootsect.s 文件,我们清空文件内容,重头开始编写。

  • 获取光标位置
entry _start
_start:
! 输出一些信息
    ! 读入光标位置
    mov ah, #0x03
    xor bh, bh             ! 显示页码设置
    int 0x10

其中,中断 int 10ah = 03H 功能以及参数 bh 可以在文件 file1615.pdf 查到,如下图所示。

在这里插入图片描述

  • 输出显示字符串。同理可以查表发现,0x10中断对应的 ah = 13 功能为向屏幕输出显示字符串,并且各个参数的功能也都有详细介绍,具体可以参照代码及注释。

在这里插入图片描述

BOOTSEG = 0x07c0;    ! bootsect 程序开始的地址

! 输出一些信息
    ! 设置开机启动字符串
    mov cx, #26            ! 字符串长度(20字符+3个回车+3个换行)
    mov bx, #0x0007        ! 设置 显示页号 和 字符属性(BL表示显示颜色 01:蓝色,03:青色,07:普通白色)
    mov ax, #BOOTSEG       ! 字符串 段地址
    mov es, ax             ! 不能将立即数直接赋给段寄存器,ax 充当临时寄存器
    mov bp, #msg1          ! 字符串 偏移地址
    mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式
    int 0x10               ! 显示服务中断

! 无限循环
inf_loop:
	jmp inf_loop

! 显示字符串
msg1:
    .byte    13,10         ! 回车 + 换行
    .ascii    "Joker is booting ..."
    .byte    13,10,13,10

.org 510                   ! 将当前位置设置为内存地址 510
! 设置引导扇区标记
    .word    0xAA55        ! 指定引导扇区的最后两个字节,bootsect必须以它结尾,才能引导

需要注意的是:在结尾处我们需要使用 .org 指令 ,设置最后两个字节为 bootsect 程序标识。

可能有不少读者不太了解 org 指令 ,在此处给出Unix操作系统指令大全:115个最常用的Linux命令行大全 - 知乎 (zhihu.com),大家遇到不懂的指令可以在该文中查到。

2、编译和运行

cd ~/my_space/OS_HIT/oslab_Lab1/linux-0.11/boot
as86 -0 -a -o bootsect.o bootsect.s   // 用as86编译器,编译生成目标文件 bootsect.o
ld86 -0 -s -o bootsect bootsect.o     // 用ld86链接器,将目标文件连接成可执行文件

直接在 linux-0.11 文件夹下用 make all 也可以,因为Makefile里面定义了这两条语句了

编译后,通过 ls -l 指令可以查看生成文件信息。

在这里插入图片描述

需要留意的文件是 bootsect 的文件大小是 544 字节,而引导程序必须要正好占用一个磁盘扇区,即 512 个字节。造成多了 32 个字节的原因是 ld86 产生的是 Minix 可执行文件格式,这样的可执行文件处理文本段、数据段等部分以外,还包括一个 Minix 可执行文件头部。

  • 因此,我们需要去掉这 32 个字节后,将生成的文件拷贝到 linux-0.11 目录下,并一定要命名为“Image”。
dd bs=1 if=bootsect of=Image skip=32   // 去掉前 32 个 bytes
cp ./Image ../Image                    // 移动到 linux-0.11 目录下
../../run                              // 运行

至此,实验任务一 就完成了,实现效果如下所示。

在这里插入图片描述

二、bootsect.s 导入 setup.s

需要实现:bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。

1、编写 setup.s 屏幕输出

我们首先来编写 setup.s 向屏幕输出功能,同理需要重头开始编写 setup.s,与 bootsect.s 类似。

entry _start
_start:
    ! 读入光标位置
    mov ah, #0x03
    xor bh, bh
    int 0x10

    ! 设置开机启动字符串
    mov cx, #25            ! 字符串长度(19字符+3个回车+3个换行)
    mov bx, #0x0007        ! 设置 显示页号 和 字符属性
    mov ax, cs             ! 使用cs值获取 段地址
    mov es, ax             ! ax 充当临时寄存器
    mov bp, #msg2          ! 字符串 偏移地址
    mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式
    int 0x10

! 无限循环
inf_loop:
    jmp inf_loop

! 显示字符串
msg2:
    .byte    13,10         ! 回车 + 换行
    .ascii    "Now we are in SETUP"
    .byte    13,10,13,10

2、在 bootsect.s 中载入 setup.s

在 bootsects 中载入 setup.s ,需要用到中断 int 0x1302H 号功能——从磁盘中载入数据到内存。

在这里插入图片描述

具体代码如下所示:(除了新增代码,我们还需要去掉在 bootsect.s 添加的无限循环)

SETUPSEG = 0x07e0;               ! setup 程序开始的地址(没有移动到 0x9000 处)
SETUPLEN = 4;                    ! setup 占用扇区数量

! 加载 setup.s 程序
load_setup:
    mov    dx,#0x0000            ! 设置驱动器和磁头(drive 0, head 0)
    mov    cx,#0x0002            ! 设置扇区号和磁道(sector 2, track 0)
    mov    bx,#0x0200            ! 偏移地址为 512 bytes
    mov    ax,#0x0200+SETUPLEN   ! ah=02H:从磁盘读数据到内存;al=04H,读入四个扇区
    int    0x13                  ! 低级磁盘服务中断
    jnc    ok_load_setup         ! 加载成功,跳转(无进位时跳转)
    !加载错误
    mov    dx,#0x0000
    mov    ax,#0x0000            ! 复位软驱
    int    0x13
    jmp load_setup               ! 再次尝试

ok_load_setup:
    jmpi 0,SETUPSEG              ! 赋予 setup.s 控制权

! 无限循环
! inf_loop:
!    jmp inf_loop

3、编译和运行

为了加快编译速度,避免一个一个手动编译,我们将借助 makefile 来实现编译。我们将用到 linux-0.11/tools/build.c 文件来实现。注意:在使用 make BootImage 之前,我们需要修改一下 build.c 代码,因为我们还没有编写内核文件 kernel,所以会出现报错。

  • 注释掉 tool/build.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 linux-0.11
make BootImage
../run

至此,实验任务二 就完成了,实现效果如下所示。

在这里插入图片描述

三、setup.s 获取硬件参数并输出显示

需要实现:setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。

1、获取硬件参数

获取硬件参数的代码大致都一样,我们将主要介绍 获取内存大小获取磁盘参数表,其他参数获取参考文末代码。

  • 要获取内存大小,我们需要用到中断 int 0x15,调用 ah = 0x88 功能实现。具体代码如下:
BOOTSEG = 0x07c0;         ! bootsect 读入段地址
INITSEG = 0x9000;         ! 初始数据段存放的位置
SETUPSEG = 0x07e0;        ! setup 程序开始的地址(没有移动到 0x90200 处)

! 设置段地址,将硬件参数取出来放在内存 0x90000
    mov ax, #INITSEG
    mov ds, ax            ! 数据段地址 ds = 0x9000

! 读光标位置,存入 dx
    mov ah, #0x03         ! AH = 3 -> 读光标位置
    xor bh, bh            ! 显示页数 = 0
    int 0x10
    ! 将光标位置写入 0x90000.
    mov [0], dx

! 读入内存大小位置
    mov ah, #0x88         ! AH = 0x88 -> 读入内存大小
    int 0x15              ! 内存大存入 AX
    mov [2], ax           ! 存入 9000 后面两个偏移
  • 获取磁盘参数表,。在 PC 机中 BIOS 设定的中断向量表中 int 0x41 的中断向量位置 4*0x41 = 0x0000:0x0104存放着第一个硬盘的基本参数表。第二个硬盘的基本参数表入口地址存于 int 0x46 中断向量位置处。每个硬盘参数表有 16 个字节大小。
! 获取磁盘参数表,从 0x41 处拷贝 16 个字节
    mov ax, #0x0000       ! 不允许直接将立即数加载到段寄存器,需要使用通用寄存器ax
    mov ds, ax            ! 数据段地址 ds = 0x0000
    lds    si,[4*0x41]    ! 取中断向量 41 的值,即 hd0 参数表的地址 ds:si
    mov    ax,INITSEG
    mov    es,ax
    mov    di,#0x0080     ! 传输的目的地址: es:di = 9000:0080
    mov    cx,#0x10       ! 重复执行次数 = 16
    rep                   ! 重复执行movsb, DS:SI -> ES:DI
    movsb                 ! 每执行一次 movsb 指令,源地址和目的地址的偏移都会自动递增

2、显示获得的参数

为了以 16进制 的形式显示获取的参数,我们需要编写显示函数。(ASCII码请参考:最全ASCII码对照表0-255

以十六进制方式显示比较简单。这是因为十六进制与二进制有很好的对应关系(每 4 位二进制数和 1 位十六进制数存在一一对应关系),显示时只需将原二进制数每 4 位划成一组,按组求对应的 ASCII 码送显示器即可。分为两种情况

  • 数字 09 : 对应 ASCII 码为 0x300x39,故显示时加上 0x30
  • 数字 af :对应ASCII码为 0x410x46,故显示时在原先加 0x30 的基础上,还要添加 0x07
  • 相关显示函数如下所示。将临时寄存器 ax 中存放的16位二进制数,通过4位十六进制的ASCII字符进行显示。其中使用循环左移 rol 不断获取高位数据 -> 并通过 and 操作保留第四位数据 -> 再统一加上 0x30 之后判断是否大于数字9,若大于再加上 0x07。
! 以 16 进制 打印寄存器 ax 中的16位数
print_hex:
    mov cx, #4        ! 循环次数s
    mov dx,ax         ! 将ax所指的值放入dx中,ax作为参数传递寄存器
print_digit:
    rol dx,#4         ! 将 DX 寄存器中的值向左循环移位 4 位,相当于将 最高的4位 移到 最低的4位。后面取出最低四位,打印的时候实现高位在前
    mov ax,#0x0e0f     ! ah = 0eh -> 显示字符, al = 字符:半字节(4个比特)掩码。
    and al,dl         ! 取 dl 的低4比特值。
    add al,#0x30      ! 给al数字加上十六进制 0x30, 0~9 + 0x30 = "0~9"
    cmp al,#0x3a      ! 判断 al 是否大于 字符 9
    jl outp           ! al < 数字"9"后面的字符,说明是 0~9,则跳转
    add al,#0x07      ! al > "9", 是a~f,要多加7h
outp:
    int 0x10          ! 打印字符存储在 al 中
    loop print_digit  ! cx = 4,循环四次
    ret               ! 函数返回指令,表示函数执行结束,返回到调用该函数的位置

! 打印显示回车换行
print_nl:
    mov ax, #0x0e0d
    int 0x10
    mov al, #0x0a
    int 0x10
    ret

回车和换行的区别 回车(13):表示光标移动到当前行的开头,但不改变行号 换行(10):表示光标移动到下一行,但不会移动到行首

  • 调用显示函数来实现硬件参数显示:
! 开始显示参数
! 前面修改了ds数据段寄存器,这里将其设置为0x9000
    mov ax,#INITSEG        ! 设置数据段地址,后续显示硬件参数使用
    mov ds,ax              ! 0x9000
    mov ax,#SETUPSEG       ! 设置附加段地址,后续显示字符串使用
    mov    es,ax           ! 0x07e0

! 显示 光标位置
    ! 获取光标位置
    mov    ah,#0x03        
    xor    bh,bh
    int    0x10
    ! 显示字符串
    mov bp, #cur           ! 串 偏移地址
    mov cx, #11            ! 串 长度
    mov bx, #0x0003        ! 页号 = 0 + 颜色设置(03:青色,07:普通白色)
    mov ax, #0x1301        ! 显示字符串 + char...
    int 0x10
    ! 显示数值
    mov ax, [0]            ! 打印函数参数存入 ax
    call print_hex         ! 调用打印寄存器函数
    call print_nl          ! 打印回车换行

! 显示 内存大小
    ! 获取光标位置
    mov    ah,#0x03        
    xor    bh,bh
    int    0x10
    ! 显示字符串提示
    mov bp, #mem
    mov cx, #12
    mov bx, #0x0007        
    mov ax, #0x1301        ! 显示字符串 + char...
    int 0x10
    ! 显示数值
    mov ax, [2]
    call print_hex
    ! 显示 KB
    mov    ah,#0x03        ! read cursor pos
    xor    bh,bh
    int    0x10
    mov    cx,#6
    mov    bx,#0x0007      ! page 0, attribute c 
    mov    bp,#cyl
    mov    ax,#0x1301      ! write string, move cursor
    int    0x10

! 无限循环
inf_loop:
    jmp inf_loop

! 显示字符串
msg2:
    .byte    13,10         ! 回车 + 换行
    .ascii    "Now we are in SETUP"
    .byte    13,10,13,10

! 光标位置
cur:
    .ascii    "Cursor POS:"

! 内存大小
mem:
    .ascii    "Memory SIZE:"

! 提示信息
cyl:
    .ascii "KB"
    .byte 13,10,13,10

至此,实验任务三 就完成了,实现效果如下所示。

在这里插入图片描述

至此,Lab2 实验介绍完毕,文末将附上 bootsect.ssetup.s 完整代码及详细注释。

实验 Lab2 代码与 Linux-0.11 代码的区别在于:

  • Lab2 无需将 bootsect.s 从 0x07c00 移动到 0x90000,故 setup.s 存放在其后 0x07e00 段位置处,也没有移动到 0x90200

四、完整代码汇总

bootsect.s

! 声明了几个全局符号,用来标识程序的代码段、数据段和未初始化数据段的起始和结束位置
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text                 ! 接下来的指令是 代码段
begtext:              ! 标识代码段的起始位置
.data                 ! 接下来的指令是 数据段
begdata:              ! 标识数据段的起始位置
.bss                  ! 接下来的指令是 未初始化数据段
begbss:               ! 标识未初始化数据段的起始位置
.text                 ! 接下来的指令是 代码段


BOOTSEG = 0x07c0;     ! bootsect 程序开始的地址
SETUPSEG = 0x07e0;    ! setup 程序开始的地址(没有移动到 0x9000 处)
SETUPLEN = 4;         ! setup 占用扇区数量

! 指定程序的入口点 为 _start
entry _start
_start:

! 从go标号处开始执行,并设置代码段 cs = BOOTSEG
    jmpi    go,BOOTSEG
! 设置 ds=es=cs
go:    mov    ax,cs
    mov    ds,ax  
    mov    es,ax

! 输出一些信息
    ! 读入光标位置
    mov ah, #0x03
    xor bh, bh             ! 显示页码设置
    int 0x10

    ! 设置开机启动字符串
    mov cx, #26            ! 字符串长度(20字符+3个回车+3个换行)
    mov bx, #0x0007        ! 设置 显示页号 和 字符属性
    mov ax, #BOOTSEG       ! 字符串 段地址
    mov es, ax             ! 不能将立即数直接赋给段寄存器,ax 充当临时寄存器
    mov bp, #msg1          ! 字符串 偏移地址
    mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式
    int 0x10               ! 显示服务中断

! 加载 setup.s 程序
load_setup:
    mov    dx,#0x0000            ! 设置驱动器和磁头(drive 0, head 0)
    mov    cx,#0x0002            ! 设置扇区号和磁道(sector 2, track 0)
    mov    bx,#0x0200            ! 偏移地址为 512 bytes
    mov    ax,#0x0200+SETUPLEN   ! ah=02H:从磁盘读数据到内存;al=04H,读入四个扇区
    int    0x13                  ! 低级磁盘服务中断
    jnc    ok_load_setup         ! 加载成功,跳转
    !加载错误
    mov    dx,#0x0000
    mov    ax,#0x0000            ! 复位软驱
    int    0x13
    jmp load_setup               ! 再次尝试

! 加载成功后,开始执行setup代码
ok_load_setup:
    jmpi 0,SETUPSEG


! 显示字符串
msg1:
    .byte    13,10               ! 回车 + 换行
    .ascii    "Joker is booting ..."
    .byte    13,10,13,10

! 将当前位置设置为内存地址 510 字节处
.org 510                
! 设置引导扇区标记
    .word    0xAA55               ! 指定引导扇区的最后两个字节,bootsect必须以它结尾,才能引导

.text            
endtext:        ! 标识代码段的结束位置
.data
enddata:        ! 标识数据段的结束位置
.bss
endbss:         ! 标识未初始化数据段的结束位置

setup.s

BOOTSEG = 0x07c0;        ! bootsect 读入段地址
INITSEG = 0x9000;        ! 初始数据段存放的位置
SETUPSEG = 0x07e0;       ! setup 程序开始的地址(没有移动到 0x9000 处)

! 指定程序的入口点 为 _start
entry _start
_start:

! 从go标号处开始执行,并设置代码段 cs = SETUPSEG
    jmpi    go,SETUPSEG
! 设置 ds=es=cs
go:    mov    ax,cs
    mov    ds,ax  
    mov    es,ax

! 输出一些信息
    ! 读入光标位置
    mov ah, #0x03
    xor bh, bh
    int 0x10

    ! 设置开机启动字符串
    mov cx, #25            ! 字符串长度(20字符+3个回车+3个换行)
    mov bx, #0x0007        ! 设置 显示页号 和 字符属性
    mov ax, cs             ! 使用cs值获取 段地址
    mov es, ax             ! ax 充当临时寄存器
    mov bp, #msg2          ! 字符串 偏移地址
    mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式
    int 0x10


! 设置数据段ds,将硬件参数取出来放在内存 0x90000
    mov ax, #INITSEG
    mov ds, ax             ! 数据段地址 ds = 0x9000

! 读光标位置,存入数据段
    mov ah, #0x03          ! AH = 3 -> 读光标位置
    xor bh, bh             ! 显示页数 = 0
    int 0x10
    ! 将光标位置写入 0x90000.
    mov [0], dx

! 读入内存大小位置
    mov ah, #0x88          ! AH = 0x88 -> 读入内存大小
    int 0x15
    mov [2], ax            ! 存入 9000 后面两个偏移

! 获取磁盘参数表,从 0x41 处拷贝 16 个字节
    mov ax, #0x0000        ! 不允许直接将立即数加载到段寄存器,需要使用通用寄存器ax
    mov ds, ax             ! 数据段地址 ds = 0x0000
    lds    si,[4*0x41]     ! 取中断向量 41 的值,即 hd0 参数表的地址 ds:si
    mov    ax,INITSEG
    mov    es,ax
    mov    di,#0x0080      ! 传输的目的地址: es:di = 9000:0080
    mov    cx,#0x10        ! 重复执行次数 = 16
    rep                    ! 重复执行movsb, DS:SI -> ES:DI
    movsb                  ! 每执行一次 movsb 指令,源地址和目的地址的偏移都会自动递增


! 开始显示参数
! 前面修改了ds数据段寄存器,这里将其设置为0x9000
    mov ax,#INITSEG        ! 设置数据段地址,后续显示硬件参数使用
    mov ds,ax              ! 0x9000
    mov ax,#SETUPSEG       ! 设置附加段地址,后续显示字符串使用
    mov    es,ax           ! 0x07e0

! 显示 光标位置
    ! 获取光标位置
    mov    ah,#0x03        
    xor    bh,bh
    int    0x10
    ! 显示字符串
    mov bp, #cur           ! 串 偏移地址
    mov cx, #11            ! 串 长度
    mov bx, #0x0003        ! 页号 = 0 + 颜色设置(03:青色,07:普通白色)
    mov ax, #0x1301        ! 显示字符串 + char...
    int 0x10
    ! 显示数值
    mov ax, [0]            ! 打印函数参数存入 ax
    call print_hex         ! 调用打印寄存器函数
    call print_nl          ! 打印回车换行

! 显示 内存大小
    ! 获取光标位置
    mov    ah,#0x03        
    xor    bh,bh
    int    0x10
    ! 显示字符串提示
    mov bp, #mem
    mov cx, #12
    mov bx, #0x0007        
    mov ax, #0x1301        ! 显示字符串 + char...
    int 0x10
    ! 显示数值
    mov ax, [2]
    call print_hex
    ! 显示 KB
    mov    ah,#0x03        ! read cursor pos
    xor    bh,bh
    int    0x10
    mov    cx,#6
    mov    bx,#0x0007      ! page 0, attribute c 
    mov    bp,#cyl
    mov    ax,#0x1301      ! write string, move cursor
    int    0x10


! 无限循环
inf_loop:
    jmp inf_loop

! 以 16 进制 打印寄存器 ax 中的16位数
print_hex:
    mov cx, #4             ! 循环次数s
    mov dx,ax              ! 将ax所指的值放入dx中,ax作为参数传递寄存器
print_digit:
    rol dx,#4              ! 将 DX 寄存器中的值向左循环移位 4 位,相当于将 高4位 移到 低4位
    mov ax,#0xe0f          ! ah = 0eh -> 显示字符, al = 字符:半字节(4个比特)掩码。
    and al,dl              ! 取 dl 的低4比特值。
    add al,#0x30           ! 给al数字加上十六进制 0x30, 0~9 + 0x30 = "0~9"
    cmp al,#0x3a           ! 判断 al 是否大于 字符 9
    jl outp                ! al < 数字"9"后面的字符,说明是 0~9,则跳转
    add al,#0x07           ! al > "9", 是a~f,要多加7h
outp:
    int 0x10               ! 打印字符存储在 al 中
    loop print_digit       ! cx = 4,循环四次
    ret                    ! 函数返回指令,表示函数执行结束,返回到调用该函数的位置

! 打印回车换行
print_nl:
    mov ax, #0x0e0d
    int 0x10
    mov al, #0x0a
    int 0x10
    ret

! 显示字符串
msg2:
    .byte    13,10        ! 回车 + 换行
    .ascii    "Now we are in SETUP"
    .byte    13,10,13,10

! 光标位置
cur:
    .ascii    "Cursor POS:"

! 内存大小
mem:
    .ascii    "Memory SIZE:"

! 提示信息
cyl:
    .ascii "KB"
    .byte 13,10,13,10

Profile picture

Written by JokerDebug who works at Southeast University, Nanjing, China You can follow me on Github