1. 引言
经过 20 多篇文章的一步步走来,我们已经从开机启动的 BIOS 执行跳转进入到自己编写的起始扇区,又从起始扇区跳转进入到 loader,时至今日,我们终于进入到内核了,海阔凭鱼跃,天高任鸟飞,我们已经打开了操作系统真正的核心组件 — 内核,那么,就让我们赶紧扩充内核,让他成为一个真正的操作系统吧。 本文,我们就来实现内核最为初步的工作:
- 从 loader 切换堆栈到内核
- 切换 GDT 到内核
- 添加中断处理
2. 切换堆栈
首先,我们需要创建堆栈空间,nasm 中,resb 伪指令用来生成未经初始化的一段空间。
代码语言:javascript复制[SECTION .bss]
StackSpace resb 2 * 1024 * 1024
StackTop:
[section .text]
global _start
_start:
mov esp, StackTop ; 堆栈在 bss 段中
这里我们创建了一个堆栈段,StackTop 标签指向栈顶。 接下来,我们将 StackTop 赋值给 esp 就完成了堆栈的切换。
3. 初始化 EFLAGS
进入内核,我们希望一切都从头开始,包括最为重要的标志位寄存器是必须要进行初始化的,此时,我们先暂时初始化为 0 :
代码语言:javascript复制push 0
popfd
4. 切换 GDT
切换 GDT 的工作主要分两个步骤:
- 通过 sgdt 指令获取当前 gdtr 寄存器存储的 loader 的 GDT 存储空间首地址与界限
- 创建属于 kernel 的新的 GDT 存储空间
- 将 loader 的 GDT 拷贝到新的 GDT 存储空间中
- 通过 lgdt 指令将 kernel 的 GDT 存储空间首地址与界限载入到 gdtr 寄存器中
相对于堆栈切换,这部分的工作略微多了一些,而此时,我们已经可以通过将 C 语言代码编译为 ELF 文件来供 kernel 调用了,接下来我们就用 C 语言来实现这部分功能。
4.1. 内存拷贝函数
首先,我们用汇编实现一下供 C 语言调用的 memcpy 函数,我们此前的文章中曾经写过这个函数: 实战操作系统 loader 编写(下) — 进军内核
代码语言:javascript复制[SECTION .text]
global memcpy
; ------------------------------------------------------------------------
; void* memcpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
memcpy:
push ebp
mov ebp, esp
push esi
push edi
push ecx
mov edi, [ebp 8] ; Destination
mov esi, [ebp 12] ; Source
mov ecx, [ebp 16] ; Counter
.1:
cmp ecx, 0 ; 判断计数器
jz .2 ; 计数器为零时跳出
; 逐字节移动
mov al, [ds:esi]
inc esi
mov byte [es:edi], al
inc edi
dec ecx ; 计数器减一
jmp .1 ; 循环
.2:
mov eax, [ebp 8] ; 返回值
pop ecx
pop edi
pop esi
mov esp, ebp
pop ebp
ret
4.2. 开辟内存空间存储 kernel GDT
首先,我们需要在拷贝前开辟一段空间来存储新的 GDT,那么,开辟多大的空间呢,这里我们就需要声明一个段描述符的结构。
代码语言:javascript复制#define GDT_SIZE 128
/* 段描述符 */
typedef struct s_descriptor
{
unsigned short limit_low; /* Limit */
unsigned short base_low; /* Base */
unsigned char base_mid; /* Base */
unsigned char attr1; /* P(1) DPL(2) DT(1) TYPE(4) */
unsigned char limit_high_attr2; /* G(1) D(1) 0(1) AVL(1) LimitHigh(4) */
unsigned char base_high; /* Base */
} DESCRIPTOR;
4.3. 拷贝 GDT 到内核
接下来,我们就要将 loader 中的 GDT 拷贝到 kernel 了。
代码语言:javascript复制unsigned char gdt_ptr[6]; /* 0~15:Limit 16~47:Base */
DESCRIPTOR gdt[GDT_SIZE];
void copy_gdt()
{
clear_screen();
disp_str("----- welcome to the kernel by techlog.cn ----- ");
disp_str("n----- start to copy gdt ... ----- ");
/* gdt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sgdt/lgdt 的参数。*/
unsigned short* p_gdt_limit = (unsigned short*)(&gdt_ptr[0]);
unsigned int* p_gdt_base = (unsigned int*)(&gdt_ptr[2]);
/* 将 LOADER 中的 GDT 复制到新的 GDT 中 */
memcpy(&gdt, (void*)(*p_gdt_base), *p_gdt_limit 1);
*p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1;
*p_gdt_base = (unsigned int)&gdt;
disp_str("n----- finish to copy gdt ----- ");
}
void clear_screen() {
char blank[50], i;
for (i = 0; i < 50; i) {
if (i <mark> 48) {
blank[i] = 'n';
blank[i 1] = '