bootloader重构

boot程序(主引导程序512字节以内)必须加载loader文件到内存,并且跳转到loader执行

loader作为加载器主要功能:通过BIOS获取硬件信息,之后加载内核,进入内核的保护模式

bootloader合起来就是加载真正的操作系统内核Kernel,操作系统内核使用汇编与C语言实现

image-20220703140112440

既然如此为什么需要一个中转loader,不直接boot跳转到kernel呢?
因为boot小于512字节,无法完成过多功能(比如全局段描述表建立和初始化等等)

boot.asm和loader.asm都需要加载某个其他文件的功能,因此可以将此功能独立出来放到文件blfunc.asm中

  1. common.asm 定义常量和宏
  2. blfunc.asm 实模式下的文件加载功能定义
  3. boot.asm加载loader并且跳转到引导扇区
  4. loader.asm 必要的硬件初始化,加载kernel,进入保护模式并且跳转到kernel

最后文件架构如下所示:

image-20220703140127099

loader文件被加载到0x9000处,因此si开始指向0x9000处,一个扇区512字节,每次加512字节si迟早得溢出变为0,这也就导致了load加载其他文件的大小不能超过30KB(0xFFFF-0x9000==30KB),加载loader肯定是没有任何问题的,但是如果是去加载kernel内核文件(kernel内核大小可能远远不止30KB大小),无法访问整个kernel内存

si最多可以访问0x0000~0xFFFF总计64KB大小,借助es:si指向0x9000,es的值为0x900左移四位加上si=0即可得到,es:si=0x9000,如果si加512的过程中变为了0,说明si的值已经加了64KB以至于变回了0因此修改es的值了,es变为es+0x1000即可,这样es和si都修改就可以访问整个kernel内存了

blfunc.asm接口设计

功能性函数放进来之后还需要定义用法,要代码复用必须包含此文件且定义一些常量,这些常量是接口的一部分,定义了栈被加载的目标地址,目标文件名字与长度。

注意%include “blfunc.asm”必须是第一条“包含”语句,因为有一条强制跳转语句

image-20220703140846732

内核雏形构造

image-20220702161149694

kernel.out是linux下的可执行程序,文件格式是elf格式(固定数据格式),x86处理器只认识代码和数据,无法正确执行elf可执行程序

因此需要格式转换,总体思路:

  1. 提取elf文件中的代码段与数据段(删除elf文件格式信息)
  2. 重定位提取后的代码段和数据段,得到内核文件
  3. 加载内核文件到内存(起始地址可自定义)
  4. 跳转到内核入口地址处执行
1
2
3
4
5
6
7
8
9
10
11
12
global  _start

extern KMain

[section .text]
[bits 32]
_start:
mov ebp, 0

call KMain

jmp $
1
2
3
4
5
6
7
#include "kernel.h"

void KMain()
{

}

使用平坦模型访问内核起始地址处执行代码

1
2
3
4
[section .s32]
[bits 32]
CODE32_SEGMENT:
jmp dword Code32FlatSelector : BaseOfTarget

可以看到内核代码成功的加载到了0xb000处并且执行了KMain函数

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
<bochs:3> info break
Num Type Disp Enb Address
1 pbreakpoint keep y 0x00000000b000
<bochs:4> s
Next at t=16227014
(0) [0x00000000b005] 0008:0000b005 (unk. ctxt): call .+2 (0x0000b00c) ; e802000000
<bochs:5> s
Next at t=16227015
(0) [0x00000000b00c] 0008:0000b00c (unk. ctxt): push ebp ; 55
<bochs:6> s
Next at t=16227016
(0) [0x00000000b00d] 0008:0000b00d (unk. ctxt): mov ebp, esp ; 89e5
<bochs:7> s
Next at t=16227017
(0) [0x00000000b00f] 0008:0000b00f (unk. ctxt): call .+8 (0x0000b01c) ; e808000000
<bochs:8> s
Next at t=16227018
(0) [0x00000000b01c] 0008:0000b01c (unk. ctxt): mov eax, dword ptr ss:[esp] ; 8b0424
<bochs:9> s
Next at t=16227019
(0) [0x00000000b01f] 0008:0000b01f (unk. ctxt): ret ; c3
<bochs:10> s
Next at t=16227020
(0) [0x00000000b014] 0008:0000b014 (unk. ctxt): add eax, 0x00002fe8 ; 05e82f0000
<bochs:11> s
Next at t=16227021
(0) [0x00000000b019] 0008:0000b019 (unk. ctxt): nop ; 90
<bochs:12> s
Next at t=16227022
(0) [0x00000000b01a] 0008:0000b01a (unk. ctxt): pop ebp ; 5d
<bochs:13> s
Next at t=16227023
(0) [0x00000000b01b] 0008:0000b01b (unk. ctxt): ret ; c3
<bochs:14> s
Next at t=16227024
(0) [0x00000000b00a] 0008:0000b00a (unk. ctxt): jmp .-2 (0x0000b00a) ; ebfe
<bochs:15> s
Next at t=16227025
(0) [0x00000000b00a] 0008:0000b00a (unk. ctxt): jmp .-2 (0x0000b00a) ; ebfe
<bochs:16>

接着修改Kentry,

image-20220703145912663

内核的屏幕打印函数

image-20220703201319036

接口设计

image-20220703201430910

先前实现的打印函数是通过寄存器传参的,然而C与汇编嵌套编程,无法让C语言传入寄存器参数,因此需要重新设计。

以下是 AT&T 格式的汇编,与 Intel 格式的汇编有一些不同。二者语法上主要有以下几个不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
* 寄存器命名原则
AT&T: %eax Intel: eax
* 源/目的操作数顺序
AT&T: movl %eax, %ebx Intel: mov ebx, eax
* 常数/立即数的格式 
AT&T: movl $_value, %ebx Intel: mov eax, _value
把value的地址放入eax寄存器
AT&T: movl $0xd00d, %ebx Intel: mov ebx, 0xd00d
* 操作数长度标识
AT&T: movw %ax, %bx Intel: mov bx, ax
* 寻址方式
AT&T: immed32(basepointer, indexpointer, indexscale)
Intel: [basepointer + indexpointer × indexscale + imm32)

内嵌汇编实例:

image-20220703201759325

1
2
3
4
5
6
7
8
9
10
int main()
{
int result = 0, input = 0;
asm volatile(
"movl %1, %0\n" //input->result
: "=r"(result) //输出参数
: "=r"(input)); //输入参数
//result = input
return 0;
}

解决打印不变的问题—光标追踪:

通过操作 0X03D4 与 0X03D5 端口对光标位置进行设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mov	bx,	(80 * 12 + 38)	;光标位置12行38列

; 设置光标位置的高8位
mov dx, 0x03D4
mov al, 0x0E
out dx, al
mov dx, 0x03D5
mov al, bh
out dx, al

; 设置光标位置的低8位
mov dx, 0x03D4
mov al, 0x0F
out dx, al
mov dx, 0x03D5
mov al, bl
out dx, al

C语言中嵌入了AT&T 格式的汇编

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
int SetPrintPos(short w, short h)
{
int ret = 0;

if( ret = ((0 <= w) && (w <= SCREEN_WIDTH) && (0 <= h) && (h <= SCREEN_HEIGHT)) )
{
unsigned short bx = SCREEN_WIDTH * h + w;

gPosW = w;
gPosH = h;

asm volatile(
"movw %0, %%bx\n" //%0代表的参数(即"r"(bx) )->bx寄存器
"movw $0x03D4, %%dx\n"
"movb $0x0E, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bh, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D4, %%dx\n"
"movb $0x0F, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bl, %%al\n"
"outb %%al, %%dx\n"
:
: "r"(bx)
: "ax", "bx", "dx"
);
}

return ret;
}
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

#include "kernel.h"
#include "screen.h"

static int gPosW = 0; //全局变量代表打印的位置横轴
static int gPosH = 0; //全局变量代表打印的位置纵轴
static char gColor = SCREEN_WHITE;

void ClearScreen()
{
int h = 0;
int w = 0;

SetPrintPos(0, 0);

for(h=0; h<SCREEN_HEIGHT; h++)
{
for(w=0; w<SCREEN_WIDTH; w++)
{
PrintChar(' ');
}
}

SetPrintPos(0, 0);
}

int SetPrintPos(short w, short h)
{
int ret = 0;

if( ret = ((0 <= w) && (w <= SCREEN_WIDTH) && (0 <= h) && (h <= SCREEN_HEIGHT)) )
{
unsigned short bx = SCREEN_WIDTH * h + w;

gPosW = w;
gPosH = h;

asm volatile(
"movw %0, %%bx\n"
"movw $0x03D4, %%dx\n"
"movb $0x0E, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bh, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D4, %%dx\n"
"movb $0x0F, %%al\n"
"outb %%al, %%dx\n"
"movw $0x03D5, %%dx\n"
"movb %%bl, %%al\n"
"outb %%al, %%dx\n"
:
: "r"(bx)
: "ax", "bx", "dx"
);
}

return ret;
}

void SetPrintColor(PrintColor c)
{
gColor = c;
}

int PrintChar(char c)
{
int ret = 0;

if( (c == '\n') || (c == '\r') )
{
ret = SetPrintPos(0, gPosH + 1);
}
else
{
int pw = gPosW;
int ph = gPosH;

if( (0 <= pw) && (pw <= SCREEN_WIDTH) && (0 <= ph) && (ph <= SCREEN_HEIGHT) )
{
int edi = (SCREEN_WIDTH * ph + pw) * 2;
char ah = gColor;
char al = c;

asm volatile(
"movl %0, %%edi\n"
"movb %1, %%ah\n"
"movb %2, %%al\n"
"movw %%ax, %%gs:(%%edi)"
"\n"
:
: "r"(edi), "r"(ah), "r"(al)
: "ax", "edi"
);

pw++;

if( pw == SCREEN_WIDTH )
{
pw = 0;
ph = ph + 1;
}

ret = 1;
}

SetPrintPos(pw, ph);
}

return ret;
}

int PrintString(const char* s)
{
int ret = 0;

if( s != NULL )
{
while( *s )
{
ret += PrintChar(*s++);
}
}
else
{
ret = -1;
}

return ret;
}

int PrintIntHex(unsigned int n)
{
char hex[11] = {'0', 'x', 0};
int i = 0;

for(i=9; i>=2; i--)
{
int p = n & 0xF;

if( p < 10 )
{
hex[i] = ('0' + p);
}
else
{
hex[i] = ('A' + p - 10);
}

n = n >> 4;
}

return PrintString(hex);
}

int PrintIntDec(int n)
{
int ret = 0;

if( n < 0 )
{
ret += PrintChar('-');

n = -n;

ret += PrintIntDec(n);
}
else
{
if( n < 10 )
{
ret += PrintChar('0' + n);
}
else
{
ret += PrintIntDec(n/10);
ret += PrintIntDec(n%10);
}
}

return ret;
}