我的OS | 确认操作系统的执行状态

为了方便管理,“我的OS”源代码已经上传到GitHub。网址:

https://github.com/pengruiyang-cpu/My-OS/

昨天,我们成功制作了一个启动层。但是,这玩意儿啥都不干,咱也不知道它是不是在执行。所以,今天我们来让它可以输出hello, OS world!这行字符。

那么,怎么输出呢?

还记不记得我们昨天讲过的BIOS,它的大名可不是吹的。它除了检查操作系统和检查硬件,还有一系列有用的函数集。昨天我们也讲了一个叫做IDT中断记录表的东西,BIOS为了方便开发者们开发操作系统,提前注册了IDT,这样在我们想要用BIOS函数的时候就可以用汇编的int中断指令了。

我们来看一看在BIOS函数集中,怎么输出一个字符

int 0x10 AH = 0x13 显示一行字符串

  • AL寄存器 = 写入的模式

    • AL = 0x00 字符串的属性由BL寄存器提供,CX寄存器提供字符串的长度以B为单位,显示后光标的位置不变

    • AL = 0x01 同AL = 0x00 但光标会移动到字符串的末尾位置

    • AL = 0x02 字符串的属性由每个字符后面的字节提供,CX寄存器提供的Word为单位,显示后光标的位置不变

    • AL = 0x03 同AL = 0x02,但光标会移动到字符串的末尾

  • CX寄存器 = 字符串的长度

  • DH寄存器 = 光标的行数

  • DL寄存器 = 光标的列数

  • ES:BP = 字符串在内存中的物理地址(ES为段地址,BP为偏移地址)

  • BH = 页码

  • BL = 字符串的属性/颜色属性

    • 0~2Bits = 字体的颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)

    • 3Bit = 字体的亮度(0=普通,1=高亮度)

    • 4~6Bits = 背景颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)

    • 7Bit = 字体闪烁(0=不闪烁,1=闪烁) 

在此之前,我们还需要先初始化一下我们的寄存器。因为很可能这些寄存器的值就会影响我们最终的结果。

其实就几行,这样:

; 初始化寄存器的值
; mov指令将右边的寄存器或数的值复制到右边的寄存器中
; 将CS(Code Segment)的值赋给AX寄存器
mov ax, cs
; 将AX寄存器(从CS寄存器取来)的值赋给DS(Data Segment)寄存器
mov ds, ax
; 将AX寄存器的值赋给ES(Extra Segment)寄存器
mov es, ax
; 将AX寄存器的值赋给SS(Stack Segment)寄存器
mov ss, ax
; 将这段代码的起始位置赋值给SP寄存器(Stack Point)
mov sp, 0x7c00

对了,昨天忘了讲解一个叫做栈的东西。请看和这篇文章同时发出来的《什么是栈》啊。

初始化寄存器之后,我们就可以开始调用BIOS函数显示字符了。开始吧。

; 设置程序的起始位置为0x7c00(为了让CPU执行)
org 0x7c00
; 初始化寄存器
; mov指令将右边的寄存器或数的值复制到右边的寄存器中
; 将CS(Code Segment)的值赋给AX寄存器
mov ax, cs
; 将AX寄存器(从CS寄存器取来)的值赋给DS(Data Segment)寄存器
mov ds, ax
; 将AX寄存器的值赋给ES(Extra Segment)寄存器
mov es, ax
; 将AX寄存器的值赋给SS(Stack Segment)寄存器
mov ss, ax
; 将这段代码的起始位置赋值给SP寄存器(Stack Point)
mov sp, 0x7c00
; 显示hello, OS world
; int 0x10 AH = 0x13 显示一行字符串
  ; AL寄存器 = 写入的模式
    ; AL = 0x00 字符串的属性由BL寄存器提供,CX寄存器提供字符串的长度以B为单位,显示后光标的位置不变
    ; AL = 0x01 同AL = 0x00 但光标在显示完成后会移动到字符串的末尾
    ; AL = 0x02 字符串的属性由每个字符后面的字节提供,CX寄存器提供的Word为单位,显示后光标的位置不变
    ; AL = 0x03 同AL = 0x02,但光标会移动到字符串的末尾
  ; CX寄存器 = 字符串的长度
  ; DH寄存器 = 光标的行数
  ; DL寄存器 = 光标的列数
  ; ES:BP = 字符串在内存中的物理地址(ES为段地址,BP为偏移地址)
  ; BH = 页码
  ; BL = 字符串的属性/颜色属性
    ; 0~2Bits = 字体的颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)
    ; 3Bit = 字体的亮度(0=普通,1=高亮度)
    ; 4~6Bits = 背景颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)
    ; 7Bit = 字体闪烁(0=不闪烁,1=闪烁) 
; 设置AL为0x01
mov al, 0x01
; 十进制的13,设置字符串的长度为13B
mov cx, 13
; 设置在第零行
mov dh, 0x0
; 设置在第零列
mov dl, 0x0
; 设置字符串的内存地址(段地址为DS(Data Segment, 数据段), 偏移地址为字符串的偏移地址)
; 由于需要给ES赋值但ES必须使用寄存器赋值,所以先将DS放进AX
; 由于AX中还存放着参数,所以需要PUSH进栈中 具体看《什么是栈》
push ax
; 将DS放进AX中
mov ax, ds
; 将AX(DS的值)放入ES
mov es, ax
; POP出栈
pop ax
; 设置字符串的偏移地址
mov bp, msg
; 设置显示的页码
mov bh, 0x0
; 设置显示的属性(BL寄存器为八位,16进制的F是4位,零省略不写)
; 0xF相当于二进制00001111,十进制的15
mov bl, 0xf
; 设置中断号码 BIOS将使用中断的号码确定你要调用哪个函数
mov ah, 0x13
; 调用中断
int 0x10
; 到此,信息已经显示完毕,跳转到end
jmp end
end:
  ; 显示完毕后的处理
  ; 为了省电而执行HLT,让CPU在触发中断后再工作
  hlt
  ; 重复执行
  jmp end
; 要显示的文字 如果要修改请还要修改记录字符串长度的寄存器值
msg: db "hello, OS world"
; 让BIOS知道这是一个启动层
times 510 - ($ - $$) db 0
; 填充0xaa和0x55
db 0x55, 0xaa

将这段代码使用NASM编译为OS.img,然后开启一个虚拟机软件,新建一个虚拟机,在软盘驱动器选项选择这个生成的OS.img文件。

然后,我们将它作为一个软盘映像文件放到虚拟机内。

我们没有清空屏幕,所以出现了这种尴尬的情况。我们看一看BIOS怎么清空屏幕。

int 0x10 ah = 0x6 滚动窗口或者清屏

AL寄存器 = 滚动的列数,如果为0则会清屏

BH寄存器 = 滚动后空出位置放入的属性

CH = 滚动范围的左上角列数

CL = 滚动范围的左上角行数

DH = 滚动范围的右上角列数

DL  = 滚动范围的右上角行数

BH = 颜色……不说了

由于我们只是要清空屏幕,我也懒得抄过来了。在AL = 0的时候别的寄存器是不管用的,不用管他。

于是,我们写出了以下代码:

; 清空屏幕
mov al, 0
; BIOS函数号码
mov ah, 0x6
; 调用中断
int 0x10

将它插到我们显示字符的代码之前,就是这样的。

; 设置程序的起始位置为0x7c00(为了让CPU执行)
org 0x7c00
; 初始化寄存器
; mov指令将右边的寄存器或数的值复制到右边的寄存器中
; 将CS(Code Segment)的值赋给AX寄存器
mov ax, cs
; 将AX寄存器(从CS寄存器取来)的值赋给DS(Data Segment)寄存器
mov ds, ax
; 将AX寄存器的值赋给ES(Extra Segment)寄存器
mov es, ax
; 将AX寄存器的值赋给SS(Stack Segment)寄存器
mov ss, ax
; 将这段代码的起始位置赋值给SP寄存器(Stack Point)
mov sp, 0x7c00
; 清空屏幕
; int 0x10 ah = 0x6 滚动窗口或者清屏
; AL寄存器 = 滚动的列数,如果为0则会清屏
; BH寄存器 = 滚动后空出位置放入的属性
; CH = 滚动范围的左上角列数
; CL = 滚动范围的左上角行数
; DH = 滚动范围的右上角列数
; DL  = 滚动范围的右上角行数
; BH = 颜色……不说了
mov al, 0
; BIOS函数号码
mov ah, 0x6
; 调用中断
int 0x10
; 显示hello, OS world
; int 0x10 AH = 0x13 显示一行字符串
  ; AL寄存器 = 写入的模式
    ; AL = 0x00 字符串的属性由BL寄存器提供,CX寄存器提供字符串的长度以B为单位,显示后光标的位置不变
    ; AL = 0x01 同AL = 0x00 但光标在显示完成后会移动到字符串的末尾
    ; AL = 0x02 字符串的属性由每个字符后面的字节提供,CX寄存器提供的Word为单位,显示后光标的位置不变
    ; AL = 0x03 同AL = 0x02,但光标会移动到字符串的末尾
  ; CX寄存器 = 字符串的长度
  ; DH寄存器 = 光标的行数
  ; DL寄存器 = 光标的列数
  ; ES:BP = 字符串在内存中的物理地址(ES为段地址,BP为偏移地址)
  ; BH = 页码
  ; BL = 字符串的属性/颜色属性
    ; 0~2Bits = 字体的颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)
    ; 3Bit = 字体的亮度(0=普通,1=高亮度)
    ; 4~6Bits = 背景颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)
    ; 7Bit = 字体闪烁(0=不闪烁,1=闪烁) 
; 设置AL为0x01
mov al, 0x01
; 十进制的13,设置字符串的长度为13B
mov cx, 13
; 设置在第零行
mov dh, 0x0
; 设置在第零列
mov dl, 0x0
; 设置字符串的内存地址(段地址为DS(Data Segment, 数据段), 偏移地址为字符串的偏移地址)
; 由于需要给ES赋值但ES必须使用寄存器赋值,所以先将DS放进AX
; 由于AX中还存放着参数,所以需要PUSH进栈中 具体看《什么是栈》
push ax
; 将DS放进AX中
mov ax, ds
; 将AX(DS的值)放入ES
mov es, ax
; POP出栈
pop ax
; 设置字符串的偏移地址
mov bp, msg
; 设置显示的页码
mov bh, 0x0
; 设置显示的属性(BL寄存器为八位,16进制的F是4位,零省略不写)
; 0xF相当于二进制00001111,十进制的15
mov bl, 0xf
; 设置中断号码 BIOS将使用中断的号码确定你要调用哪个函数
mov ah, 0x13
; 调用中断
int 0x10
; 到此,信息已经显示完毕,跳转到end
jmp end
end:
  ; 显示完毕后的处理
  ; 为了省电而执行HLT,让CPU在触发中断后再工作
  hlt
  ; 重复执行
  jmp end
; 要显示的文字 如果要修改请还要修改记录字符串长度的寄存器值
msg: db "hello, OS world"
; 让BIOS知道这是一个启动层
times 510 - ($ - $$) db 0
; 填充0xaa和0x55
db 0x55, 0xaa

执行:

啊,为什么啊……

笔者换了一个虚拟机软件,果然成功了!

但是,为什么只显示了13个字符啊?

笔者第一反应就是在boot.asm中字符串的大小写错了,结果是真的……

明明要显示16个字符,却写了13个,笔者也是真棒!!!

修改后的源代码如下:

; 设置程序的起始位置为0x7c00(为了让CPU执行)
org 0x7c00
; 初始化寄存器
; mov指令将右边的寄存器或数的值复制到右边的寄存器中
; 将CS(Code Segment)的值赋给AX寄存器
mov ax, cs
; 将AX寄存器(从CS寄存器取来)的值赋给DS(Data Segment)寄存器
mov ds, ax
; 将AX寄存器的值赋给ES(Extra Segment)寄存器
mov es, ax
; 将AX寄存器的值赋给SS(Stack Segment)寄存器
mov ss, ax
; 将这段代码的起始位置赋值给SP寄存器(Stack Point)
mov sp, 0x7c00
; 显示hello, OS world
; int 0x10 AH = 0x13 显示一行字符串
  ; AL寄存器 = 写入的模式
    ; AL = 0x00 字符串的属性由BL寄存器提供,CX寄存器提供字符串的长度以B为单位,显示后光标的位置不变
    ; AL = 0x01 同AL = 0x00 但光标在显示完成后会移动到字符串的末尾
    ; AL = 0x02 字符串的属性由每个字符后面的字节提供,CX寄存器提供的Word为单位,显示后光标的位置不变
    ; AL = 0x03 同AL = 0x02,但光标会移动到字符串的末尾
  ; CX寄存器 = 字符串的长度
  ; DH寄存器 = 光标的行数
  ; DL寄存器 = 光标的列数
  ; ES:BP = 字符串在内存中的物理地址(ES为段地址,BP为偏移地址)
  ; BH = 页码
  ; BL = 字符串的属性/颜色属性
    ; 0~2Bits = 字体的颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)
    ; 3Bit = 字体的亮度(0=普通,1=高亮度)
    ; 4~6Bits = 背景颜色(0=黑色,1=蓝色,2=绿色,3=青色,4=红色,5=紫色,6=棕色,7=白色)
    ; 7Bit = 字体闪烁(0=不闪烁,1=闪烁) 
; 设置AL为0x01
mov al, 0x01
; 十进制的16,设置字符串的长度为16B
mov cx, 16
; 设置在第零行
mov dh, 0x0
; 设置在第零列
mov dl, 0x0
; 设置字符串的内存地址(段地址为DS(Data Segment, 数据段), 偏移地址为字符串的偏移地址)
; 由于需要给ES赋值但ES必须使用寄存器赋值,所以先将DS放进AX
; 由于AX中还存放着参数,所以需要PUSH进栈中 具体看《什么是栈》
push ax
; 将DS放进AX中
mov ax, ds
; 将AX(DS的值)放入ES
mov es, ax
; POP出栈
pop ax
; 设置字符串的偏移地址
mov bp, msg
; 设置显示的页码
mov bh, 0x0
; 设置显示的属性(BL寄存器为八位,16进制的F是4位,零省略不写)
; 0xF相当于二进制00001111,十进制的15
mov bl, 0xf
; 设置中断号码 BIOS将使用中断的号码确定你要调用哪个函数
mov ah, 0x13
; 调用中断
int 0x10
; 到此,信息已经显示完毕,跳转到end
jmp end
end:
  ; 显示完毕后的处理
  ; 为了省电而执行HLT,让CPU在触发中断后再工作
  hlt
  ; 重复执行
  jmp end
; 要显示的文字 如果要修改请还要修改记录字符串长度的寄存器值
msg: db "hello, OS world"
; 让BIOS知道这是一个启动层
times 510 - ($ - $$) db 0
; 填充0xaa和0x55
db 0x55, 0xaa

运行一遍:

成功了!太棒了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

(笔者已经疯了)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值