Featured image of post 纯裸机在屏幕打印文字

纯裸机在屏幕打印文字

字数: 843

引导扇区

BIOS 启动后加载并执行磁盘的第一个扇区,大小为 512 字节。这个扇区为引导扇区,用于启动存放在硬盘上的其他程序。

引导扇区的最后两个字节必须是 0x55 0xAA
加载过程为:
电脑开机->BIOS 自检->把引导扇区加载到内存 0x7c00-> 执行扇区代码,此时为完全的裸机状态,只有实模式的 CPU。

最简单的代码

最简单的就是写一段无限循环:

1
2
3
4
5
6
loop:
    jmp loop

times 510 - ($-$$) db 0

dw 0xaa55

这段代码将循环之后的代码到 510 字节用 0 填充,也就是nop指令。

1
times 510 - ($-$$) db 0

最后的魔数 dw 0xaa55 以通过 BIOS 检查为可运行。

编译:

1
nasm loop.asm -o loop.bin

用 qemu 运行:

1
qemu-system-x86_64 loop.bin

除了 BIOS 之外,什么都没输出。只是风扇开始转动。CPU 一个核心满载。

打印字符串

当然,只要做些工作,我们可以在屏幕上打印出文字出来。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
org 07C00h
mov ax, cs
mov ds, ax
mov es, ax
call DispStr
jmp $
DispStr:
    mov ax, BootMessage
    mov bp, ax
    mov cx, 16
    mov ax, 01301h
    mov bx, 000ch
    mov dl, 0
    int 10h
    ret
BootMessage: db "Hello OS world!"
times 510-($-$$) db 0
dw 0xaa55

分析下:

1
org 07C00h

是 intel 下内存专门放引导扇区的地方,这里指定从 07c00h 开始执行程序。
将 cs、ds、es 段寄存器都放在一起,也就是将代码、数据、字符串全放在同一个地方,让 CPU 知道它们在哪。

1
2
3
mov ax, cs
mov ds, ax
mov es, ax

因为在实模式下,我们不是很需要将代码和数据分开。
后面重复运行 DispStr 函数。

1
2
3
4
5
6
7
8
9
DispStr:
    mov ax, BootMessage ; 把字符串偏移地址放入 ax
    mov bp, ax ;  把字符串地址放入 bp 寄存器
    mov cx, 16 ; 保存字符串长度 16
    mov ax, 01301h ; 设置显示模式
    mov bx, 000ch ; 颜色:红色
    mov dl, 0 ; 从第 0 列开始显示
    int 10h ;调用 BIOS 中断
    ret ; 函数返回,回到 call 的下一行

这里是显示的核心代码。在引导扇区,只能使用 BIOS 中断。

1
mov bp, ax ;  把字符串地址放入 bp 寄存器

将 字符串地址放入 bp 寄存器。因为 BIOS 里字符串地址要放在 ES:BP 内,不然 BIOS 找不到需要显示的文字。

1
mov ax, 01301h ; 设置显示模式

这段设置显示模式,将 ax 寄存器分为两部分:

  • ah: 0x13 用中断的 13h 号功能:显示字符串
  • al: 0x01 给 13h 设置选项,表示字符串只包含字符,不包含颜色属性;显示完移动光标。

后面就很正常的参数,注释很清楚了。

和上面循环代码一样,编译后在 qemu 运行即可:

参考资料

  1. 自己动手写操作系统 于渊 电子工业出版社
  2. os-tutorial-zsh 01-bootsector-barebones