fezOS

fezOS搭建

前言

2021年9月初

一直很好奇操作系统究竟是如何搭建起来的,现跟随这个项目尝试写一个自己的操作系统(暂命名为fezOS)。我将从引导扇区的编写开始,逐步将其搭建,本博客记录搭建过程中的点点滴滴

2021-9-25

计划有变,原项目进行至将内核装载如内存时,不知道为啥,我的电脑上一直无法成功运行。尝试了很多方法仍不能解决此问题。加之最关键的这本书没有写完,于是放弃原项目跟随学习,现在跟着《操作系统真相还原》这本书学习。之前的学习笔记保留,新项目的学习笔记接着旧的继续写

原项目

00-environment

本项目搭建于WSL的Ubuntu 20.04 LTS,按照作者文档提示,下载了qemunasm

下载qemu的过程有些曲折,我直接使用命令

1
sudo apt-get install qemu

下载该软件,但在01-bootsector-barebones小节中使用

1
qemu boot_sect_simple.bin

命令时,系统却报错找不到qemu命令。后找到解决方案为

1
sudo apt-get install qemu-system-i386

命令下载qemu-system-i386,再使用

1
qemu-system-i386 boot_sect_simple.bin

即可正常使用,若嫌名字太长,可以通过建立一个软链接

1
ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu

之后便可直接使用

1
qemu boot_sect_simple.bin

01-bootsector-barebones

这一节主要介绍了计算机在通电启动时究竟发生了些什么,关于这方面的知识请参见这篇文章

按文档中介绍,既可以蛮力地把这512个字节全列出来,也可以参考采用作者给出的汇编代码

1
2
3
4
5
6
7
8
; Infinite loop (e9 fd ff)
loop:
jmp loop

; Fill with 510 zeros minus the size of the previous code
times 510-($-$$) db 0
; Magic number
dw 0xaa55

其中($-$$)虽然结合上下文能理解什么意思,但从没见过这种用法,查阅nasm官方文档

1
2
3
4
5
NASM supports two special tokens in expressions, allowing calculations to involve 
the current assembly position: the $ and $$ tokens. $ evaluates to the assembly
position at the beginning of the line containing the expression; so you can code
an infinite loop using JMP $. $$ evaluates to the beginning of the current
section; so you can tell how far into the section you are by using ($-$$).

才得以解惑,仅仅几行代码就让我觉得作者的水平那是相当的高啊~

接着开开心心

1
qemu boot_sect_simple.bin

得目标结果

02-bootsector-print

这一节的内容不多,就是测试下用中断在终端输出字符

结合上一小节的内容猜测,BIOS通过检测扇区最后两字节是否为aa55来判断该扇区是否为其所寻找的引导扇区。当找到后,从该扇区头部开始执行指令

至于文档中提到的tty mode, 我简单了解了一下,但仍然不太理解。但不影响打印字符功能的实现,这里我简单修改了打印内容

03-bootsector-memory

本节介绍系统启动后BOOT SECTOR进程内存分布布局情况,参考这本书3.4.2节了解更多细节

04-bootsector-stack

这一节要求自行学习理解栈的概念,文档中提到sp指向栈顶,bp指向栈底,但就我自己学习王爽老师的汇编语言经历来看,bp是完全不需要指向栈底的。只需初始化sp即可,可以通过bp指针查看栈中任意位置内容

经测验,栈中(至少本实验的栈中)采用的是小端存法。即若ax = 0x3938,执行push ax0x39存于高地址,0x38存于低地址

05-bootsector-functions-strings

这一节介绍字符串和函数调用,都是很基础的东西,不必多说。

需要知道,汇编语言中一个程序要使用另一个文件的内容只需

1
%include "file.asm"

文档中还提到了被调用的函数应使用pushapopa指令确保消除函数调用的副作用。关于这两个函数我上网查了下,这样的函数实际上有两对,分别是pusha/popapushd/popd,两者的区别如下

The PUSHA (push all) and PUSHAD (push all double) mnemonics reference the same opcode. The PUSHA instruction is intended for use when the operand-size attribute is 16 and the PUSHAD instruction for when the operand-size attribute is 32. Some assemblers may force the operand size to 16 when PUSHA is used and to 32 when PUSHAD is used. Others may treat these mnemonics as synonyms (PUSHA/PUSHAD) and use the current setting of the operand-size attribute to determine the size of values to be pushed from the stack, regardless of the
mnemonic used.

The POPA (pop all) and POPAD (pop all double) mnemonics reference the same opcode. The POPA instruction is intended for use when the operand-size attribute is 16 and the POPAD instruction for when the operand-size attribute is 32. Some assemblers may force the operand size to 16 when POPA is used and to 32 when POPAD is used (using the operand-size override prefix [66H] if necessary). Others may treat these mnemonics as synonyms (POPA/POPAD) and use the current setting of the operand-size attribute to determine the size of values to be popped from the stack, regardless of the mnemonic used. (The D flag in the current code segment’s segment descriptor determines the operand-size attribute.)

pusha/popa用于操作数为16位的情况,pushd/popd用于32位情形

测试结果如下

只换行不回车

06-bootsector-segmentation

介绍分段相关的问题,没什么新东西。不过倒是启发了我该如何讲清03节问题。当我们使用如mov al,[the_secret]这样的语句时,实际上是使用了mov al,[ds:the_secret],在03小节中我们没初始化ds寄存器,则其值默认为0,然而Loaded Boot Sector首地址实为0x7c00,故而应设置ds = 0x 7c0,采用org 0x7c00等价于此

本节代码运行结果

07-bootsector-disk

介绍了如何从别的扇区读取数据。由文档里的这篇文章可知,设置好几个通用寄存器的值,通过int 0x13中断调用就可以从磁盘指定扇区读取数据到es:bx所指定的位置处

两个程序的代码阅读起来没什么难度。boot_sect_disk.asm这程序写得挺好的,把异常处理机制完善了,挺有参考价值的一个模板程序。

本节代码运行结果

08-32bit-print

这节让了解32位保护模式,VGA以及video memory。当从16位系统变为32位系统时则不能再使用BIOS提供的中断在屏幕上输出字符,而采用在VGA memory从首地址为0xb8000处开始打印,而这不正是王爽老师的《汇编语言》教材上所采用的字符串输出方式吗?

这一节所涉及32位模式屏幕输出相关知识参见王老师的汇编教材即可

09-32bit-gdt

没啥好说的,参考这本书第4.2节了解关于GDT相关内容

10-32bit-enter

1.按需求定义好GDT表

2.使用cli关闭中断

3.使用lgbt [gdt_descriptor]加载GDT表

4.设置cr0寄存器最低为1,至此正式从16位实模式进入32位保护模式

5.通过一个far jump清空流水线中可能存在的尚未执行完的16位实模式下的指令

6.记得更新各段寄存器及堆栈

11-kernel-crosscompiler

12-kernel-c

假设我们有一个文件hello.c

1
2
3
4
5
6
7
8
9
10
11
Compile: 
gcc -ffreestanding -c hello.c -o hello.o

Display info from object file:
objdump -d hello.o

Link:
ld -o hello.bin -Ttext [offset] --oformat binary hello.o

Decompile:
ndisasm -b 32 hello.bin > hello.dis

13-kernel-barebones

就是在这里,我的实验环境始终无法运行书上例程,从此放弃原项目,转向《操作系统真相还原》的学习

操作系统真相还原

24号书一到手就迫不及待开始从头阅读,不得不吐槽口水话好多好多。哪怕预先知道这书比较啰嗦,但看下来真的有点打脑壳…明明几句话就能说清楚的东西却花几页的篇幅来介绍,看着看着人都给套进去了,知识屏蔽不如王爽老师的《汇编语言》(感觉知识屏蔽做得最好的是旧项目里那本教程,可惜没写完,不然绝对惊艳)

01-MBR

又从引导扇区开始学起,本书实验环境作者选择了bochs,我尝试使用这个,但是遇到了好多麻烦,花了我很多时间与精力也一直没法解决这些问题,又用回了qemu,不得不说qemu对比起来是真的方便易用。但bochs的尝试并非一无所获,bochs带有的一个创建虚拟硬盘的工具bximage,能非常方便的创造实验需要的一个虚拟硬盘

1
linux> bximage -hd -mode="flat" -size=60 -q hd60M.img

以上是书上给出的示例命令,我在我的电视使用上述命令时报错不知道-hd参数是啥。。。我也难得去找纠结到底是啥问题,而是直接

1
linux> bximage

进入交互界面创造了自己需要的硬盘,然后就把bochs扔到一边,回到qemu的怀抱

在前几章的学习中写了几个/os/boot/mbr.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
;main boot record03
;reads from disk and loads the loader
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov sp, 0x7c00
mov ax, 0xb800
mov gs, ax

;clear screen
;int 0x10 function code: 0x06
;description: roll up the screen
;input
;ah function code = 0x06
;al = number of rows roll up (0 for all)
;bh = attributes of rows
;(cl, ch) = top left corner (x, y) of the window
;(dl, dh) = botton right corner (x, y) of the window
;no return value
mov ax, 0x600
mov bx, 0x700
mov cx, 0 ;top left corner: (0, 0)
mov dx, 0x184f ;bottom right corner: (24, 79)
int 0x10

;print on screen
mov byte [gs: 0x00], '1'
mov byte [gs: 0x01], 0b10000011;

mov byte [gs: 0x02], ' '
mov byte [gs: 0x03], 0b10000011;

mov byte [gs: 0x04], 'M'
mov byte [gs: 0x05], 0b10000011;

mov byte [gs: 0x06], 'B'
mov byte [gs: 0x07], 0b10000011;

mov byte [gs: 0x08], 'R'
mov byte [gs: 0x09], 0b10000011;

mov eax, LOADER_START_SECTOR; ;起始扇区LBA地址
mov bx, LOADER_BASE_ADDR; ;写入的地址
mov cx, 1 ;待读入的扇区数
call rd_disk_m_16 ;调用硬盘读取程序

jmp LOADER_BASE_ADDR

rd_disk_m_16:
;功能:读取硬盘n个扇区
;eax = LBA扇区号
;bx = 数据写入内存的地址
;cx = 读入的扇区数
mov esi, eax ;备份eax
mov di, cx ;备份cx

;读写磁盘
;第一步:设置要读取的扇区数
mov dx, 0x1f2
mov al, cl
out dx, al ;读取的扇区数

mov eax, esi ;恢复ax

;第二步:将LBA地址存入0x1f3 ~ 0x1f6

;LBA地址7 ~ 0位写入端口0x1f3
mov dx, 0x1f3
out dx, al

;LBA地址15 ~ 8位写入端口0x1f4
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al

;LBA地址23 ~ 16位写入端口0x1f5
shr eax, cl
mov dx, 0x1f5
out dx, al

shr eax, cl
and al, 0xf ;LBA第27 ~ 24位
or al, 0xe0 ;设置7 ~ 4为1110,表示LBA模式
mov dx, 0x1f6
out dx, al

;第三步:向0x1f7端口写入读命令,0x20
mov dx, 0x1f7
mov al, 0x20
out dx, al

;第四步:检测硬件状态
.not_ready: ;同一端口,写入时表示写入命令字,读时表示读入硬件状态
nop
in al, dx
and al, 0x88 ;第3位为1表示硬件控制器已准备好数据传输
;第7位为1表示硬件忙
cmp al, 0x08
jnz .not_ready

;第5步:从0x1f0端口读数据
mov ax, di
mov dx, 256
mul dx
mov cx, ax
;di为要读取的扇区数,一个扇区512字节,一次读取读一个字,因此一个扇区读256次

mov dx, 0x1f0
.go_on_read:
in ax, dx
mov [bx], ax
add bx, 2
loop .go_on_read

ret

times 510 - ($ - $$) db 0
dw 0xaa55

MBR将位于磁盘第二个扇区的loader加载到内存地址LOADER_BASE_ADDR起始处的位置,然后跳转至此处执行loader的代码。本书所采用的硬盘读取方法是通过硬盘控制器的几个端口寄存器一个字一个字读入内存实现的,涉及8个寄存器的使用,只是理论就够让人头疼了,我实在是把各个寄存器相应功能及参数等等一一贴出来了,感兴趣的话请参阅此书3.5.3节

/os/boot/include/boot.inc文件内容

1
2
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

/os/boot/loader.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
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR

;输出背景色绿色,前景色红色,并跳动的字符串"2 LOADER"
mov byte [gs: 0x00], '2'
mov byte [gs: 0x01], 0xa4

mov byte [gs: 0x02], ' '
mov byte [gs: 0x03], 0xa4

mov byte [gs: 0x04], 'L'
mov byte [gs: 0x05], 0xa4

mov byte [gs: 0x06], 'O'
mov byte [gs: 0x07], 0xa4

mov byte [gs: 0x08], 'A'
mov byte [gs: 0x09], 0xa4

mov byte [gs: 0x0a], 'D'
mov byte [gs: 0x0b], 0xa4

mov byte [gs: 0x0c], 'E'
mov byte [gs: 0x0d], 0xa4

mov byte [gs: 0x0e], 'R'
mov byte [gs: 0x0f], 0xa4

jmp $

/os/boot/mbr.asm/os/boot/loader.asm都使用了位于/include目录下的/os/boot/include/boot.inc,因此需按如下格式编译

1
2
linux> nasm -I include/ -o mbr.bin mbr.asm
linxu> nasm -I include/ -o loader.bin loader.asm

再将两个文件分别写入我们虚拟磁盘的第一和第二个扇区

1
2
linux> dd if=./mbr.bin of=../hd60M.img bs=512 count=1 conv=notrunc
linxu> dd if=./loader.bin of=../hd60M.img bs=512 count=1 seek=2 conv=notrunc

就可以运行查看结果啦~

02-实模式到保护模式

1.构建gdt表

2.打开A20

1
2
3
in al, 0x92
or al, 0b00000010
out 0x92, al

3.加载gdt

1
lgdt [gdt_ptr]

4.cr0第0位置1

1
2
3
mov eax, cr0
or eax, 0x1
mov cr0, eax

5.刷新流水线

1
jmp dword SELECTOR_CODE: p_mode_start

插曲

我已经要疯了,使用qemu来进行第四章实验时又是出问题,一开始我在自己代码里找问题,但对比书上代码和别人的代码一直看不出问题,于是直接使用别人的能够得到正确显示的代码运行,但仍然没能得到预期的输出。于是我怀疑是qemu自己的问题,于是又只好用回bochs,接着,可预料到的,无穷无尽的报错,我的心里真的有点扛不住了,直到几分钟前(Sun Sep 26 16:20:34 CST 2021),终于,一切正常了!!!!!!!我找到了错误!!!!!!!!

写在这里:

下载bochs,去官网,按照书上所言,下载bochs-2.6.2.tar.gz,然后跟着书上步骤做,期间会遇见各种各样的问题,但在现在我看来都是小问题,网上各种论坛博客能找到解决方案,我就不细说了,唯一想提的,也就是我一直没解决掉的问题,在配置文件的romimagevgaromiamge两者的路径需要写成这样

1
2
romimage: file=你的路径/BIOS-bochs-latest
vgaromiamge: file=你的路径/BIOS-bochs-latest

在下载的bochs-2.6.2.tar.gz解压出来的bochs-2.6.2文件夹的bios/子文件夹里正好这两个文件,于是我就把配置文件里的路径设置为这两个的路径,即

1
2
romimage: file=/home/fez/Download/bios-2.6.2/bios/BIOS-bochs-latest
vgaromiamge: file=/home/fez/Download/bios-2.6.2/bios/BIOS-bochs-latest

于是乎,疯狂报错

1
ROM: couldn't open ROM image file '/home/fez/Download/bios-2.6.2/bios/BIOS-bochs-latest'

我就纳闷了,这不就是这个文件吗,为什么就是通过不了,看别人的这两个文件都在/usr目录下,我猜是不是/usr目录下也会有同名的这两个文件,于是搜索

1
2
3
fez@papyruszzz:$ find /usr -name 'BIOS-bochs-latest'
/usr/local/share/bochs/BIOS-bochs-latest
/usr/local/bin/bochs/share/bochs/BIOS-bochs-latest

果然!!!使用/usr/local/share/bochs/BIOS-bochs-latest这个,同理把vgaromiamge的目录也换成/usr目录下对应那个,完美解决

我靠。。。。还记得刚弹出正确运行界面时,压抑不住的怒吼。。。。。。。

至此,又换回bochs来进行实验

6.代码及实验结果

/os/boot/mbr.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
;main boot record03
;reads from disk and loads the loader
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov sp, 0x7c00
mov ax, 0xb800
mov gs, ax

;clear screen
;int 0x10 function code: 0x06
;description: roll up the screen
;input
;ah function code = 0x06
;al = number of rows roll up (0 for all)
;bh = attributes of rows
;(cl, ch) = top left corner (x, y) of the window
;(dl, dh) = botton right corner (x, y) of the window
;no return value
mov ax, 0x600
mov bx, 0x700
mov cx, 0 ;top left corner: (0, 0)
mov dx, 0x184f ;bottom right corner: (24, 79)
int 0x10

;print on screen
mov byte [gs: 0x00], '1'
mov byte [gs: 0x01], 0b10000011;

mov byte [gs: 0x02], ' '
mov byte [gs: 0x03], 0b10000011;

mov byte [gs: 0x04], 'M'
mov byte [gs: 0x05], 0b10000011;

mov byte [gs: 0x06], 'B'
mov byte [gs: 0x07], 0b10000011;

mov byte [gs: 0x08], 'R'
mov byte [gs: 0x09], 0b10000011;

mov eax, LOADER_START_SECTOR; ;起始扇区LBA地址
mov bx, LOADER_BASE_ADDR; ;写入的地址
mov cx, 4 ;待读入的扇区数
call rd_disk_m_16 ;调用硬盘读取程序

jmp LOADER_BASE_ADDR

rd_disk_m_16:
;功能:读取硬盘n个扇区
;eax = LBA扇区号
;bx = 数据写入内存的地址
;cx = 读入的扇区数
mov esi, eax ;备份eax
mov di, cx ;备份cx

;读写磁盘
;第一步:设置要读取的扇区数
mov dx, 0x1f2
mov al, cl
out dx, al ;读取的扇区数

mov eax, esi ;恢复ax

;第二步:将LBA地址存入0x1f3 ~ 0x1f6

;LBA地址7 ~ 0位写入端口0x1f3
mov dx, 0x1f3
out dx, al

;LBA地址15 ~ 8位写入端口0x1f4
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al

;LBA地址23 ~ 16位写入端口0x1f5
shr eax, cl
mov dx, 0x1f5
out dx, al

shr eax, cl
and al, 0xf ;LBA第27 ~ 24位
or al, 0xe0 ;设置7 ~ 4为1110,表示LBA模式
mov dx, 0x1f6
out dx, al

;第三步:向0x1f7端口写入读命令,0x20
mov dx, 0x1f7
mov al, 0x20
out dx, al

;第四步:检测硬件状态
.not_ready: ;同一端口,写入时表示写入命令字,读时表示读入硬件状态
nop
in al, dx
and al, 0x88 ;第3位为1表示硬件控制器已准备好数据传输
;第7位为1表示硬件忙
cmp al, 0x08
jnz .not_ready

;第5步:从0x1f0端口读数据
mov ax, di
mov dx, 256
mul dx
mov cx, ax
;di为要读取的扇区数,一个扇区512字节,一次读取读一个字,因此一个扇区读256次

mov dx, 0x1f0
.go_on_read:
in ax, dx
mov [bx], ax
add bx, 2
loop .go_on_read

ret

times 510 - ($ - $$) db 0
dw 0xaa55

/os/boot/loader.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start

;构建gdt
GDT_BASE:
dd 0x00000000
dd 0x00000000
CODE_DESC:
dd 0x0000ffff
dd DESC_CODE_HIGH4
DATA_STACK_DESC:
dd 0x0000ffff
dd DESC_DATA_HIGH4
VIDEO_DESC:
dd 0x80000007 ;limit = (0xbffff - 0xb8000) / 4k = 7
dd DESC_VIDEO_HIGH4 ;此时DPL为0
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 60 dq 0 ;此处预留60个描述符空位
SELECTOR_CODE equ (0x1 << 3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x2 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x3 << 3) + TI_GDT + RPL0

;以下是gdt指针,前两字节是gdt界限,后4字节是gdt起始地址
gdt_ptr dw GDT_LIMIT
dd GDT_BASE

LOADERMSG db '2 loader in real.'

loader_start:
;int 0x10 功能号:0x13 功能:打印字符串
mov sp, LOADER_BASE_ADDR
mov bp, LOADERMSG ;es:bp = 字符串首地址
mov cx, 17 ;cx = 字符串长度
mov ax, 0x1301
mov bx, 0x001f ;bh = 页号,bl = 属性(蓝底粉红字)
mov dx, 0x1800
int 0x10

;准备进入保护模式
;1. 打开A20
in al, 0x92
or al, 0x2
out 0x92, al

;2. 加载GDT
lgdt [gdt_ptr]

;3. cr0第0位置1
mov eax, cr0
or eax, 0x1
mov cr0, eax

;4. 刷新流水线
jmp dword SELECTOR_CODE: p_mode_start

[bits 32]
p_mode_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp, LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax

mov byte [gs: 160], 'P'
mov byte [gs: 161], 0x1f

jmp $

/os/boot/include/boot.inc

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
;loader and kernel
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

;gdt描述符属性
DESC_G_4K equ 0x800000
DESC_D_32 equ 0x400000
DESC_L equ 0x0
DESC_AVL equ 0x0
DESC_LIMIT_CODE2 equ 0xf0000
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 0xb ;书上这里有误,我的是正确的
DESC_P equ 0x8000
DESC_DPL_0 equ 0x0
DESC_DPL_1 equ 0x2000
DESC_DPL_2 equ 0x4000
DESC_DPL_3 equ 0x6000
DESC_S_CODE equ 0x1000
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0x0
DESC_TYPE_CODE equ 0x800 ;x=1, c=0, r=0, a=0 代码段可执行,非一致,不可读,已访问位清零
DESC_TYPE_DATA equ 0x200 ;x=0, e=0, w=1, a=0 数据段不可执行,向上扩展,可写,已访问位清零
DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL +DESC_LIMIT_CODE2 + \
DESC_P +DESC_DPL_0 +DESC_S_CODE + \
DESC_TYPE_CODE + 0X00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL +DESC_LIMIT_DATA2 + \
DESC_P +DESC_DPL_0 +DESC_S_DATA + \
DESC_TYPE_DATA + 0X00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL +DESC_LIMIT_VIDEO2 + \
DESC_P +DESC_DPL_0 +DESC_S_DATA + \
DESC_TYPE_DATA + 0X00
;选择子属性
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b

有个小坑,loader.bin文件大小已经超过512字节了,一个扇区不够,应该用两个,指令为

1
linux> dd if=./loader.bin of=../hd60M.img bs=512 count=2 seek=2 conv=notrunc

运行结果

7.一点点不一样

相比原项目的从实模式进入保护模式程序,《操作系统真相还原》这本书里没提关中断的事情。不知道是暂时没讲到中断所以暂时不管还是单纯忘了或者觉得不需要?

反正就模拟实验而言确实不关中断也无伤大雅

03-保护模式进阶

1.物理地址容量

书上介绍了三种获取地址容量的方法,都是通过调用BIOS中断实现,个人认为这个知识点过于琐碎,对我的学习目标——认识操作系统——帮助有限,不想多说。具体细节见此参考文章

2.分页

介绍了分页机制原理及实现(采用二级页表)。代码和分析书上都很详细,我这里也懒得贴了。参考文章

再次从头开始

计算机启动过程

8086寻址范围为0x0 ~ 0xFFFFF,计算机通电时,CS: IP寄存器被设为0xFFFF:0000,即从地址0xFFFF0处起开始执行,只有十六字节就到头了,0xFFFF0处的指令为jmp far F000:e05b,于是又跳转至0xFE05B处开始执行,这才是BIOS代码真正开始的地方。

接下来BIOS便检测内存、显卡等外设,在0x000 ~ 0x3FF建立中断向量表IVT并填写中断例程。当BIOS完成其使命后,在磁盘0盘0道1扇区寻址MBR,MBR的末尾两字节为0x55AA。若找到,BIOS将MBR加载至0x7C00处,然后jmp 0: 0x7C00跳转至MBR起始处,开始执行MBR代码