内核的雏形(上) -- 创建属于 kernel 的堆栈与 GDT

2022-06-27 14:58:01 浏览数 (1)

1. 引言

经过 20 多篇文章的一步步走来,我们已经从开机启动的 BIOS 执行跳转进入到自己编写的起始扇区,又从起始扇区跳转进入到 loader,时至今日,我们终于进入到内核了,海阔凭鱼跃,天高任鸟飞,我们已经打开了操作系统真正的核心组件 — 内核,那么,就让我们赶紧扩充内核,让他成为一个真正的操作系统吧。 本文,我们就来实现内核最为初步的工作:

  1. 从 loader 切换堆栈到内核
  2. 切换 GDT 到内核
  3. 添加中断处理

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 的工作主要分两个步骤:

  1. 通过 sgdt 指令获取当前 gdtr 寄存器存储的 loader 的 GDT 存储空间首地址与界限
  2. 创建属于 kernel 的新的 GDT 存储空间
  3. 将 loader 的 GDT 拷贝到新的 GDT 存储空间中
  4. 通过 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] = '';  
break;  
} else {  
blank[i] = ' ';  
}  
}  
for (i = 0; i < 80;   i) {  
disp_str(blank);  
}  
disp_pos = 0;  
}

4.4. 加载新的 GDT

接下来,我们要在 kernel.asm 中调用 copy_gdt 并且通过 lgdt 指令加载新的 gdt 起始地址与界限到 gdtr。

代码语言:javascript复制
extern    gdt_ptr  

sgdt    [gdt_ptr]    ; cstart() 中将会用到 gdt_ptr  
call    copy_gdt    ; 在此函数中改变了gdt_ptr,让它指向新的GDT  
lgdt    [gdt_ptr]    ; 使用新的GDT

4.5. 长跳转,进入新的 GDT

程序执行中,段选择子被加载到 cs 寄存器中,除非进行长跳转,否则 cs 寄存器的值是不会发生变化的。 我们虽然通过上面的指令实现了 gdtr 寄存器的更新,但我们紧接着必须通过长跳转把新的段选择子更新到 cs 寄存器中:

代码语言:javascript复制
SELECTOR_KERNEL_CS    equ    8  

jmp    SELECTOR_KERNEL_CS:csinit  
csinit:                 ; 长跳转,让 GDT 切换生效

这里我们创建了一个段选择子,他的值为 8,表示他是 GDT 中的首个段,且选择子属性位为 0,即 GDT、Ring0 段选择子。

5. 运行 kernel

运行 kernel,我们就可以看到下图了:

6. 完整代码

本项目已开源:https://github.com/zeyu203/techlogOS。

0 人点赞