(0)操作系统学习笔记——利用BIOS打印字符串
BIOS引导
当 BIOS(Base Inpu/Out System, 基础输入/输出系统) 自检结束后会根据启动选项设置去选择启动设备(硬盘、软驱、U盘等存储介质,这里默认为软驱),检测软盘的第0磁头第0磁道第1扇区,是否以数值0x55和0xaa两字节作为结尾,如果是,BIOS就会认为这个扇区是一个 引导扇区(Boot Sector) ,进而把此扇区的所有数据复制到物理内存0x7c00处(因为读取磁盘的最小单位是一个扇区),随后处理器的执行权移交到这段程序(跳转到0x7c00)处执行。
简单地利用BIOS输出字符串
这个程序是用Intel格式的汇编语言编写的,并且使用NASM编译,它的功能并不复杂,只是调用BIOS中断,在屏幕打印一串字符串。
首先是寄存器的初始化部分,需要初始化一些段寄存器和栈指针寄存器:
org 0x7c00
BaseOfStack equ 0x7c00
Label_Start:
; 初始化一些段寄存器和栈指针
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
org表示程序的起始地址,这里设置成0x7c00,因为我们的程序是默认被加载到地址0x7c00处的,所以我们需要把程序的起始地址设置成0x7c00。此处我们把栈指针指向BaseOfStack处,亦即是0x7c00,认为0x7c00往下是我们的栈空间(栈内容增加的方向是从高地址向低地址增加的)。
接下来,就开始做一些打印前的准备,以及打印字符串了:
; 清屏
mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 184fh
int 10h ; 调用0x10号中断
; 设置光标位置
mov ax, 0200h
mov bx, 0000h
mov dx, 0000h
int 10h
; 打印字符:Start Bootint......
mov ax, 1301h
mov bx, 000fh
mov dx, 0000h
mov cx, 10
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartBootMessage
int 10h
首先是清屏,清除屏幕上的所有字符,清屏使用的是0x10号中断,然乎设置屏幕光标的位置,也是使用0x10号中断,然后就是打印字符串,也是使用0x10号中断。所以这里使用到0x10号中断的功能就包括:0x06、0x02、0x13。
-
0x10号中断,上卷指定范围的窗口(包括清屏功能)
功能号AH=0x06,按指定范围滚动窗口
AL=滚动的列数,若为0则实现清屏的功能
BH=滚动后空出位置放入的属性
CH=滚动范围的左上角坐标列号
CL=滚动范围的左上角坐标行号
DH=滚动范围的左下角坐标列号
DL=滚动范围的左下角坐标行号
BH=颜色属性(请自行百度或Google)
注: 当实现的是清屏功能(AL=0)其他的BX、CX、DX寄存器参数将不起作用。 -
0x10号中断,设置屏幕光标的位置
功能号AH=0x02,指定光标位置
DH=光标的列数
DL=光标的行数
BH=页码
注: 无论是行号还是列号,都是从0开始计数 -
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 => 要显示字符串的内存地址
- BH=页码
- BL=字符属性/颜色属性(自行百度或Google)
- AL=写入模式
显示完字符串后,接下来就是实现软盘驱动器的复位功能,也就是初始化软盘驱动器,将软盘驱动器的磁头移动到默认的位置:
; 初始化软盘驱动器
xor ah, ah
xor dl, dl
int 13h
jmp $
复位软驱使用的是0x13号中断,功能号AH=0x00。具体参数如下:
AH-0x00,重置磁盘驱动器,为下一次读写软盘做准备。
- DL=驱动器号,0x000x7F:软盘;0x800x0FF:硬盘
- DL=0x00代表第一个软盘驱动器("drive A:")
- DL=0x01代表第二个软盘驱动器("drive B:")
- DL=0x80代表第一个磁盘驱动器
- DL=0x81代表第二个磁盘驱动器
复位磁盘的功能在这段代码中无关紧要,也可以忽略,因为我们只是希望显示字符串在屏幕上。
对于指令jmp $,指令的意思是无条件跳转到当前指令的位置,说白了就是死循环,因为我们显示完字符串后,就没事可做了,所以就让它一直跳。
程序的主要逻辑到这,就基本结束了,剩下的就是填写打印信息和扇区末尾两个字节0xaa55了。
StartBootMessage: db "Start Boot"
; 扇区剩下的字节全部填满0(除了最后两个字节)
times 510 - ($ - $$) db 0
dw 0xaa55
因为计算机最少只能读取一个扇区,并且作为引导扇区,末尾两个字节必须是0xaa55,所以我们的程序要把整个引导扇区填满,并且末尾字节填上0xaa55。伪指令db表示填某个值(字节),伪指令dw也表示填一个值(字)。
那么我们的代码总体看来是这样的:
; File name: boot.asm
; org 用于指定程序的起始地址
; 如果不使用org指定程序的起始地址
; 那么编译器会把0x0000作为程序的起始地址。
org 0x7c00
BaseOfStack equ 0x7c00
Label_Start:
; 初始化一些段寄存器和栈指针
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
; 清屏
mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 184fh
int 10h ; 调用0x10号中断
; 设置光标位置
mov ax, 0200h
mov bx, 0000h
mov dx, 0000h
int 10h
; 打印字符:Start Bootint......
mov ax, 1301h
mov bx, 000fh
mov dx, 0000h
mov cx, 10
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartBootMessage
int 10h
; 初始化软盘驱动器
xor ah, ah
xor dl, dl
int 13h
jmp $
StartBootMessage: db "Start Boot"
; 扇区剩下的字节全部填满0(除了最后两个字节)
times 510 - ($ - $$) db 0
dw 0xaa55
测试程序
接下来就是跑我们的代码啦!以下测试的环境是:Win10 + NASM + Bochs2.68 + dd-0.5
- 注:
- NASM编译器需要自行安装,NASM的官网
- 因为本人这一台计算机使用的window系统,并且还没装Linux虚拟机,那就先用window做本次实验吧!不过最好还是使用Linux,不过鉴于本次程序比较简单,也没有用到GCC,所以在windows下做本次实验问题也不大。
- Bochs需要自行安装,这里可以获取所有版本的Bochs。根据自己的需要,去下载Bochs。
- window下并没有
dd命令,需要自行安装并且,下载地址
当然,不用dd命令也行,反正你要想办法把.bin文件中的512个字节,写到.img的开头,也就是替换掉.img文件的前512个字节。可以自行用编程语言写一个这样的程序,但是要注意,写完后,.img文件的大小应该保持不变,那么写这样的一个程序应该没什么难度。为了省事,我还是使用dd命令。 - 安装完成后记得配置环境变量,或者说要确保在Windows终端能够调用这些程序。
编译代码
把以上代码用NASM编译器编译:
> nasm ./boot.asm -o boot.bin
那么我们就得到了boot.bin文件,这是一个二进制文件,即是我们的程序翻译成二进制后的文件。
创建虚拟软盘镜像文件
我们使用的是Bochs虚拟机去运行我们的程序,那么我们还需要创建一个虚拟软盘镜像文件(.img),使用bximage,这个程序是Bochs自带的,因为我已经配置好环境变量了,那么直接在终端调用它,在终端输入bximage:
========================================================================
bximage
Disk Image Creation / Conversion / Resize and Commit Tool for Bochs
$Id: bximage.cc 12690 2015-03-20 18:01:52Z vruppert $
========================================================================
1. Create new floppy or hard disk image
2. Convert hard disk image to other format (mode)
3. Resize hard disk image
4. Commit 'undoable' redolog to base image
5. Disk image info
0. Quit
Please choose one [0] 1
选择1号功能,创建一个软驱或者磁盘映像。
Create image
Do you want to create a floppy disk image or a hard disk image?
Please type hd or fd. [hd] fd
输入fd表示创建一个软盘镜像文件。
Choose the size of floppy disk image to create, in megabytes.
Please type 160k, 180k, 320k, 360k, 720k, 1.2M, 1.44M, 1.68M, 1.72M, or 2.88M.
[1.44M]
这里指定软盘镜像文件的大小,这里直接回车,创建一个1.44M的软盘。
What should be the name of the image?
[a.img] boot.img
这里指定文件名,我们输入boot.img。
Creating floppy image 'boot.img' with 2880 sectors
The following line should appear in your bochsrc:
floppya: image="boot.img", status=inserted
(The line is stored in your windows clipboard, use CTRL-V to paste)
Press any key to continue
创建成功!按任意键结束。
关于bximge的其他功能,请自行发掘。
把编译的代码写进磁盘映像文件
使用dd命令:
> dd if=./boot.bin of=./boot.img bs=512 count=1 conv=notrunc
if:指定输入源文件名,也就是我们的编译好的代码文件(要注意路径是否正确)of:指定输出文件名,也就是我们的映像文件.img(要注意路径是否正确)bs:指定传输块大小,这里为512(B)count:指定写入到目标文件的块数量。conv=notrunc:规定在写入数据后不截断(改变)输出文件的大小。貌似我windows平台下的dd程序不支持这个操作选项,但是不添加好像也没啥问题。。。但是如果是linux下的话,还是要添加这个选项。
那么综上所述,在window下,dd命令的输入为:
> dd if=./boot.bin of=./boot.img bs=512 count=1
出现如下字样,说明写入成功:
rawwrite dd for windows version 0.5.
Written by John Newbigin <jn@it.swin.edu.au>
This program is covered by the GPL. See copying.txt for details
1+0 records in
1+0 records out
我们使用一些二进制文件查看工具(我使用的是VSCode的扩展hexdump for VSCode)看看是否真的写入进去了,我们使用VSCode打开boot.img文件,并且使用hexdump for VSCode预览(安装完扩展后,一般在右上角有个Show Hexdump按钮):

nice!看来是写进了。
启动Bochs虚拟机,执行程序
那么最后一步就是要执行程序了,我使用的是Bochs虚拟机(当然你也可以使用VBOX虚拟机或者其它,只要配置好相应的磁盘文件和一些配置,为什么我使用Bochs呢?因为它占用空间小啊!下载快啊!)。
使用Bochs首先需要创建一台虚拟机,也就是要配置映像文件的路径什么的,那么开始吧!
首先在终端中输入bochs(需要配置环境变量,或者能调用得到bochs.exe):
那么就会出现入下界面:

接下来就是指定映像文件的路径,在Edit Options中找到Disk & Boot选项(即是图中的Edit Options最后一个选项),选中它然后在左边点击Edit或者双击它。
接下来会出现形如这样的界面:

其中红框中的内容需要自行指定映像文件的路径,然后磁盘的类型大小,根据实际情况填写,Status中要选择为inserted表示磁盘已经插入到主机中了。
最后再在同一界面,选择Boot Options选项卡,再设置软驱为第一启动设备:

然后点击OK。回到主界面,然后可以选择保存设置,保存成一个叫bochsrc.bxrc的文件,下次启动的时候,就不用再配置了。
然后点击Start启动虚拟机:
然后你就可以看到:

Good Job!我们的字符正确的显示出来了!
因为我们之前已经把配置信息保存成bochsrc.bxrc文件了,下次启动的时候,直接可以在命令行中输入:
> bochs -f .\bochsrc.bxrc
然后直接点击Start按钮就行了!
- 注: 要注意
bochsrc.bxrc文件路径是否正确。
至此,我们已经可以成功地利用BIOS向屏幕输出字符了!
- 参考文献:
[1] 田宇.一个64位操作系统的设计与实现[M].北京:人民邮电出版社,2018.5.
浙公网安备 33010602011771号