30天自制操作系统(二)

哈哈哈哈哈哈,我果然没有咕咕咕!!!

继续开发

昨天使用汇编写出了helloos的代码中,有一部分“程序主体”还没有说明,这一章着重讲了这部分代码。

昨天代码中还没有讲到的其他内容,涉及到软盘的一些知识,书中会在后面内容提到。

一些新的汇编指令:

  • ORG:“origin”的缩写,指定机器语言装载的地址。
  • JMP:“jump”的缩写,跳转到指定标签。类似C语言的goto
  • MOV:“move”的缩写,表示赋值运算,MOV SS,AX相当于C语言中的SS=AX;。
  • ADD:累加运算,ADD SI,1相当于C语言的SI+=1;
  • CPM:“compare”的缩写,比较指令,告诉CPU比较对象。
  • JE:条件跳转指令,“jump if equal”,意思是相等就跳转,条件取决于前面的CMP指令。
  • INT:调用BIOS内置函数。
  • HLT:“halt”的缩写,让CPU停止,如果外部发生变化如按下键盘或者移动鼠标,CPU会唤醒并继续执行工作。

一些寄存器的名称:

  • AX:accumumlator,累加寄存器
  • CX:counter,计数寄存器
  • DX:data,数据寄存器
  • BX:base,基址寄存器
  • SP:stack pointer,栈指针寄存器
  • BP:base pointer,基址指针寄存器
  • SI:source index,源变址寄存器
  • DI:destination index,目的变址寄存器

以上这些都是16位的寄存器,除此之外还有8个8位寄存器,把A、C、D、B后面跟L(低位)或者H(高位),可以得到8个8位寄存器。

但是这8个8位寄存器并不是独立的,只是把原本16位的寄存器拆成高低两部分用了。

不管如何使用,16位CPU最多只能存16个字节。

当然,对于32位CPU是可以使用32位寄存器的,名称是在这些16位寄存器前面加“E”,如EAX、ECX……

64位CPU书上暂无讲解,先留个坑。

然后,昨天的代码,可以写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
org 0x7c00      ; 指明程序装载地址

fat12:
jmp entry
db 0x90
db "HELLOIPL"
dw 512
db 1
dw 1
db 2
dw 224
dw 2880
db 0xf0
dw 9
dw 18
dw 2
dd 0
dd 2880
db 0,0,0x29
dd 0xffffffff
db "HELLO-OS "
db "FAT12 "
resb 18

entry:
mov ax,0 ; 初始化寄存器
mov ss,ax
mov ds,ax
mov es,ax
mov sp,0x7c00
mov si,msg

putloop:
mov al,[si]
add si,1 ; 给SI+1,内存地址后移
cmp al,0 ; 判断是否为0
je fin ; 为0则跳出循环
mov ah,0x0e ; 显示一个字符
mov bx,15 ; 指定字符颜色
int 0x10 ; 调用显卡BIOS
jmp putloop ; 跳到循环开始

fin:
hlt ; CPU动作停止
jmp fin ; 死循环

msg:
db 0x0a, 0x0a
db "hello, world"
db 0x0a
db 0

endpart:
resb 0x1fe-($-$$)
db 0x55, 0xaa

db 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
resb 4600
db 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
resb 1469432

开头指定装载地址为0x7c00,是因为软件用途分类中规定的启动区内容装载地址的范围是0x00007c00-0x00007dff

entry:这类语句是标签声明,可以供JMP命令跳转使用,其本身表示当前语句内存地址。entry为0x7c50,写成JMP 0x7c50也是可以的。所以后面的mov si,msg其实是把msg对应的内存地址赋值给了si。

第34行mov al,[si]的方括号意义为“内存”,能直接赋值内存地址的寄存器只有BX、BP、SI、DI。此时的SI存放的应该是之前赋值的msg块的地址,这个写法相当于把SI地址上对应的一个字节的内容赋值给AL。

输出的循环,SI作为一个地址每次向后移动,从msg的头部开始,把msg中的内容一个字节一个字节的输出来。

关于字符串是怎么输出的,INT 0x10是这样描述的

AH=0x0e;
AL=character code;
BH=0;
BL=color code;
返回值:无
注:beep、退格(back space)、CR、LF都会被当做控制字符处理。

制作启动区

考虑今后开发,我们不应该一次性做整个磁盘映像,而是先只用它制作一个512字节的启动区,剩下的让磁盘映像管理工具做就好了。

首先,我们截掉helloos.asm的后半部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
org 0x7c00      ; 指明程序装载地址

fat12:
jmp entry
db 0x90
db "HELLOIPL"
dw 512
db 1
dw 1
db 2
dw 224
dw 2880
db 0xf0
dw 9
dw 18
dw 2
dd 0
dd 2880
db 0,0,0x29
dd 0xffffffff
db "HELLO-OS "
db "FAT12 "
resb 18

entry:
mov ax,0 ; 初始化寄存器
mov ss,ax
mov ds,ax
mov es,ax
mov sp,0x7c00
mov si,msg

putloop:
mov al,[si]
add si,1 ; 给SI+1,内存地址后移
cmp al,0 ; 判断是否为0
je fin ; 为0则跳出循环
mov ah,0x0e ; 显示一个字符
mov bx,15 ; 指定字符颜色
int 0x10 ; 调用显卡BIOS
jmp putloop ; 跳到循环开始

fin:
hlt ; CPU动作停止
jmp fin ; 死循环

msg:
db 0x0a, 0x0a
db "hello, world"
db 0x0a
db 0

endpart:
resb 0x1fe-($-$$)
db 0x55, 0xaa

; 去掉了后面的东西

然后,终端执行:

1
nasm -o bootloader helloos.asm

这样我们得到了一个二进制引导文件。

继续执行:

1
2
dd if=bootloader of=helloos.img count=1 bs=512
dd if=/dev/zero of=helloos.img bs=512 seek=1 count=2879

helloos.img第一个块(512字节)写入引导文件,后面2850个块全部拿0填充。

关于dd命令相关的参数

if=<file>:输入文件名,缺省为标准输入。 从file读取,如if=/dev/zero,该设备无穷尽地提供0
of=<file>:输出文件名,缺省为标准输出。 向file写出,可以写文件,可以写裸设备。
ibs=<bytes>:一次读入 bytes 个字节(即一个块大小为 bytes 个字节)。
obs=<bytes>:一次写 bytes 个字节(即一个块大小为 bytes 个字节)。
bs=<bytes>:同时设置读写块的大小为 bytes ,可代替 ibs 和 obs。
cbs=<bytes>:一次转换 bytes 个字节,即转换缓冲区大小。
skip=<blocks>:从输入文件开头跳过 blocks 个块后再开始复制。
seek=<blocks>:从输出文件开头跳过 blocks 个块后再开始复制。
count=<blocks>:仅拷贝 blocks 个块,块大小等于 ibs 指定的字节数。

这样我们就得到了helloos.img,使用qemu运行即可。

使用Make

Make是一个工具程序,维基百科对其介绍如下

在软件开发中,make是一个工具程序(Utility software),经由读取叫做“makefile”的文件,自动化建构软件。它是一种转化文件形式的工具,转换的目标称为“target”;与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。它的依赖关系检查系统非常简单,主要根据依赖文件的修改时间进行判断。大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。它使用叫做“makefile”的文件来确定一个target文件的依赖关系,然后把生成这个target的相关命令传给shell去执行。

我们使用Make来辅助可以方便我们构建,不需要再在终端中输入过多复杂的命令。

如刚刚的一系列构建命令,我们可以写成一个Makefile文件。

1
2
3
4
5
6
7
8
9
10
11
12
img:bootloader
truedd if=bootloader of=helloos.img count=1 bs=512
truedd if=/dev/zero of=helloos.img bs=512 seek=1 count=2879

bootloader:
truenasm -o bootloader helloos.asm

run:
trueqemu-system-i386 -drive file=helloos.img,if=floppy

clean:
truerm bootloader helloos.img

这样我们构建只需要执行make img && make run即可编译并在qemu中运行。

把img写在第一个是make的默认命令,当执行make的时候,默认执行make img

img冒号后面的bootloader表示执行make img时候如果没有bootloader文件,则先执行make bootloader,再执行make img


明天继续!不当鸽子!