前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >内核的雏形(上) -- 创建属于 kernel 的堆栈与 GDT

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

作者头像
用户3147702
发布2022-06-27 14:58:01
4100
发布2022-06-27 14:58:01
举报
文章被收录于专栏:小脑斧科技博客

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 -----\0");  
disp_str("\n----- start to copy gdt ... -----\0");  

/* 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 -----\0");  
}  

void clear_screen() {  
char blank[50], i;  
for (i = 0; i < 50; ++i) {  
if (i <mark> 48) {  
blank[i] = '\n';  
blank[i + 1] = '\0';  
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。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 切换堆栈
  • 3. 初始化 EFLAGS
  • 4. 切换 GDT
    • 4.1. 内存拷贝函数
      • 4.2. 开辟内存空间存储 kernel GDT
        • 4.3. 拷贝 GDT 到内核
          • 4.4. 加载新的 GDT
            • 4.5. 长跳转,进入新的 GDT
            • 5. 运行 kernel
            • 6. 完整代码
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档