30天自制操作系统(三)

提前开坑写!绝不咕咕咕QAQ

制作真正的IPL

这章主要讲了给启动区装载程序。

对于昨天的汇编代码,我们在其中加入一些内容,得到如下代码:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
org 0x7c00      ; 指明程序装载地址

fat12:
jmp entry
db 0x90
db "HARIBOTE"
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 "HARIBOTE-OS"
db "FAT12 "
resb 18

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

mov ax,0x0820
mov es,ax ; 设置缓冲地址
mov ch,0 ; 柱面0
mov dh,0 ; 磁头0
mov cl,2 ; 扇区2

mov ah,0x02 ; AH=0x02:读盘
mov al,1 ; 1个扇区
mov bx,0
mov dl,0x00 ; 0号驱动器
int 0x13 ; 调用磁盘BIOS
jc error
jmp succeed

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

error:
mov si,errormsg
jmp putloop

succeed:
mov si,okmsg
jmp putloop

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 ; 跳到循环开始

errormsg:
db 0x0a, 0x0a
db "load error"
db 0x0a
db 0

okmsg:
db 0x0a, 0x0a
db "load succeed"
db 0x0a
db 0

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

在第31-13行是新添加的代码,我们单独拿出来看:

首先还是一些新的汇编指令:

  • JC:“jump if carry”的缩写,表示如果进位标志(carry flag)是1的话,就跳转。
  • JNC:“jump if not carry”的缩写,和上面正好相反,进位标志是0的时候跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
mov ax,0x0820
mov es,ax ; 设置缓冲地址
mov ch,0 ; 柱面0
mov dh,0 ; 磁头0
mov cl,2 ; 扇区2

mov ah,0x02 ; AH=0x02:读盘
mov al,1 ; 1个扇区
mov bx,0
mov dl,0x00 ; 0号驱动器
int 0x13 ; 调用磁盘BIOS
jc error
jmp succeed

对于指令INT 0x13,它的描述如下:

AH=0x02; (读盘)
AH=0x03; (写盘)
AH=0x04; (校验)
AH=0x0c; (寻道)
AL=处理对象的扇区数; (只能同时处理连续的扇区)
CH=柱面号&0xff;
CL=扇区号 (0-5位)| (柱面号&0x300) >>2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址; (寻道及校验时不使用)
返回值:
FLACS.CF==0:没有错误,AH==0
FLACS.CF==1:有错误,错误号码存入AH内

代码中我们使用的MOV AH,0x02是读盘的意思。

FLACS.CF就是进位标志,当进位标志为1时候,JC error会跳转到error。

这样我们就把第二个扇区的内容,读入了ES标记的那块内存(0x8200

对于Makefile,我们重写了一下,Makefile中可以定义一些简单的变量,看起来会更直观一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOTLOADER = haribote.asm
IMG_FILE = haribote.img
QEMU = qemu-system-i386


img:bootloader
truedd if=bootloader of=$(IMG_FILE) count=1 bs=512
truedd if=/dev/zero of=$(IMG_FILE) bs=512 seek=1 count=2879

bootloader:
truenasm -o bootloader $(BOOTLOADER)

run:
true$(QEMU) -drive file=$(IMG_FILE),if=floppy

clean:
truerm bootloader $(IMG_FILE)

试错

由于软盘这东西性能不可靠,有时候会发生不读数据的情况,为此我们需要多尝试几次。

我们改写今天新要添加的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    mov ax,0x0820
mov es,ax ; 设置缓冲地址
mov ch,0 ; 柱面0
mov dh,0 ; 磁头0
mov cl,2 ; 扇区2

mov si,0 ; 重试次数计数器
retry:
mov ah,0x02 ; AH=0x02:读盘
mov al,1 ; 1个扇区
mov bx,0
mov dl,0x00 ; 0号驱动器
int 0x13 ; 调用磁盘BIOS
jnc succeed ; 没出错的话跳转到succeed
add si,1 ; 计数器加1
cmp si,5 ; 计数器和5比较
jae error ; si>=5时候跳转到error
mov ah,0x00
mov dl,0x00 ; 0号驱动器
int 0x13 ; 重置驱动器
jmp retry ; 重试

其中,JAE(“jump if above or equal”的缩写)是条件跳转指令,表示在前面比较结果大于等于时候跳转。

出错后,在重试之前要先对软盘做复位处理,“AH=0x00,DL=0x00,INT 0x13”的功能是复位软盘状态,再读一次。

读入多个扇区和柱面

多读几个也就是循环几次的事情。

首先是读取多个扇区,逻辑大概是这样的:

  1. 设置初始情况为当前柱面0,磁头0,扇区2
  2. 读取当前扇区,成功的话进行下一步,失败的话重复执行,失败超过5次程序结束并提示错误
  3. 当前扇区+1
  4. 如果扇区数<=设定读取的扇区数,则回到第2步,否则结束程序

我们修改我们添加的汇编代码,增加一层循环:

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
    mov ax,0x0820
mov es,ax ; 设置缓冲地址
mov ch,0 ; 柱面0
mov dh,0 ; 磁头0
mov cl,2 ; 扇区2
readloop:
mov si,0 ; 重试次数计数器
retry:
mov ah,0x02 ; AH=0x02:读盘
mov al,1 ; 1个扇区
mov bx,0
mov dl,0x00 ; 0号驱动器
int 0x13 ; 调用磁盘BIOS
jnc next ; 没出错的话跳转到next
add si,1 ; 计数器加1
cmp si,5 ; 计数器和5比较
jae error ; si>=5时候跳转到error
mov ah,0x00
mov dl,0x00 ; 0号驱动器
int 0x13 ; 重置驱动器
jmp retry ; 重试
next:
mov ax,es
add ax,0x0020 ; 磁盘缓冲内存地址后移0x200
mov es,ax ; 因为没有add es,0x020指令,用ax绕了个弯
add cl,1 ; 扇区+1
cmp cl,18 ; 比较当前扇区和18的关系
jbe readloop ; <=18则循环
jmp succeed

这样我们就成功把2-18的扇区读入了内存。

当然读取多个柱面的话,也是类似的逻辑:

  1. 设置初始情况为当前柱面0,磁头0,扇区2
  2. 读取当前扇区,成功的话进行下一步,失败的话重复执行,失败超过5次程序结束并提示错误
  3. 当前扇区+1
  4. 如果扇区数<=18,则回到第2步,否则继续执行
  5. 重置当前扇区为1,当前磁头+1
  6. 如果当前磁头<2,则回到第2步,否则继续执行
  7. 重置当前磁头为0,当前柱面+1
  8. 如果当前柱面<设定读取的柱面数,则回到第2步,否则结束程序
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
    mov ax,0x0820
mov es,ax ; 设置缓冲地址
mov ch,0 ; 柱面0
mov dh,0 ; 磁头0
mov cl,2 ; 扇区2
readloop:
mov si,0 ; 重试次数计数器
retry:
mov ah,0x02 ; AH=0x02:读盘
mov al,1 ; 1个扇区
mov bx,0
mov dl,0x00 ; 0号驱动器
int 0x13 ; 调用磁盘BIOS
jnc next ; 没出错的话跳转到next
add si,1 ; 计数器加1
cmp si,5 ; 计数器和5比较
jae error ; si>=5时候跳转到error
mov ah,0x00
mov dl,0x00 ; 0号驱动器
int 0x13 ; 重置驱动器
jmp retry ; 重试
next:
mov ax,es
add ax,0x0020 ; 磁盘缓冲内存地址后移0x200
mov es,ax ; 因为没有add es,0x020指令,用ax绕了个弯
add cl,1 ; 扇区+1
cmp cl,18 ; 比较当前扇区和18的关系
jbe readloop ; <=18则循环
mov cl,1 ; 扇区重置为1
add dh,1 ; 磁头+1
cmp dh,2 ; 比较当前磁头和2的关系
jb readloop ; <=2则循环
mov dh,0 ; 磁头置0
add ch,1 ; 柱面+1
cmp ch,cyls ; 这里cyls是10,也就是ch和10比较
jb readloop ; <10则循环
jmp succeed

开发操作系统并从启动区执行

前面我们相当于完成了启动区的制作,我们来做系统的部分。

首先先写一段非常短小的程序

1
2
3
fin:
hlt
jmp fin

然后用nasm编译得到haribote.sys

找到之前做好的img镜像,找一个文件夹挂载它,把编译好的系统程序复制进去,再卸载掉它。

1
2
3
4
5
mkdir floppy
sudo momunt -o loop haribote.img floppy
cp haribote.sys floppy
sudo umount floppy
rm -r floppy

使用二进制编辑器打开img文件,看一看haribote.sys在软盘中是什么样的。

首先是0x002600开始,这里的内容是文件名。

然后在0x004400开始,这里的内容就是文件本身了。

我们计算一下,磁盘内容被装载到内存0x8000的位置,我们向后推0x4400就是haribote.sys文件的内容了。

所以磁盘0x004400对应的应该就是内存的0x8000+0x4400=0xc400号地址

这个时候我们就能写一段系统软件的代码。

1
2
3
4
5
6
7
8
9
org 0xc400  ; 程序位置

mov al,0x13 ; VGA显卡,320x200x8位色彩
mov ah,0x00
int 0x10

fin:
hlt
jmp fin

ORG 0xc400就是它开始执行的位置。

关于INT 0x10指令,它的描述如下:

设置显卡模式
AH=0x00;
AL=模式:(省略一些不重要的画面模式)

  • 0x03:16色字符模式,80x25
  • 0x12:VGA图形模式,640x480x4位彩色模式,独特的四面存储模式
  • 0x13:VGA图形模式,320x200x8位彩色模式,调色板模式
  • 0x6a:扩展VGA图形模式,800x600x4位彩色模式,独特的四面存储模式(有的显卡不支持)

    返回值:无

我们选用了0x13模式,如果画面正常,画面应该是一片漆黑,由于变成了图形模式,光标会消失.

然后我们重写IPL,让它在读取磁盘成功后,跳转到0xc400

1
2
succeed:
jmp 0xc400 ; 跳转到haribote.sys的内容在内存中的位置

当然为了方便,我们还是改一改Makefile

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
BOOTLOADER = bootloader.asm
SYSTEM = haribote.asm
IMG_FILE = haribote.img
SYS_FILE = haribote.sys
QEMU = qemu-system-i386

default:
truemake bootloader
truemake system
truemake img
truemake copy

system:
truenasm -o $(SYS_FILE) $(SYSTEM)

bootloader:
truenasm -o bootloader $(BOOTLOADER)

img:
truedd if=bootloader of=$(IMG_FILE) count=1 bs=512
truedd if=/dev/zero of=$(IMG_FILE) bs=512 seek=1 count=2879

copy:
truemkdir -p /tmp/floppy
truemount -o loop $(IMG_FILE) /tmp/floppy -o fat=12
truesync
truecp $(SYS_FILE) /tmp/floppy
truesync
trueumount /tmp/floppy
truesync
truerm -r /tmp/floppy

run:
true$(QEMU) -drive file=$(IMG_FILE),if=floppy

clean:
truerm bootloader $(IMG_FILE) $(SYS_FILE)

当然这次由于使用了mount/umount命令,需要使用管理员权限执行。

sudo make run,如果画面一片漆黑,说明就成功了!

32位模式前期准备

再鸽一鸽

导入C语言

咕咕咕


科目三模拟+考试,停更三天,8.18日恢复更新。