第一篇里面主要还是入门,对基础知识的补充与尝试,这一次要逐渐深入,主要环节是对实模式与保护模式的学习。

专注到内核实现

第一篇里面可以看到在步入到真正的内核处理前有许多操作,尤其是从主引导记录到bootloader会有不少的操作,实现一个bootloader是比较困难且容易偏离主题的,因此会直接使用grub引导的镜像,直接使用的james的添加了grub的镜像

使用grub引导的话就要遵循其规范,内核入口点位于./boot/boot.s

核心代码

1
2
3
4
5
6
7
8
9
10
start:
cli ; 关闭中断
mov esp, STACK_TOP ; 设置内核栈地址
mov ebp, 0 ; 帧指针修改为 0
and esp, 0FFFFFFF0H ; 栈地址按照16字节对齐
mov [glb_mboot_ptr], ebx ; 将 ebx 中存储的指针存入全局变量
call kern_entry ; 调用内核入口函数
stop:
hlt
jmp stop

主要还是调用了kern_entry函数。从该函数开始,就可以使用C语言开始内核的相关实现了。

实模式下的屏幕显示

方便起见,屏幕显示全部采用的是25*80分辨率模式。为了之后的功能实现,先对相关函数进行了实现。

为此逐步实现了:

  1. 屏幕字符串显示
  2. 字符串处理相关函数
  3. 格式化字符串处理显示
  4. 内核出错时的处理

屏幕字符串显示

相关API,主要定义了图像显示的实际地址,相关的屏幕输出函数,清屏函数。

1
2
3
4
5
6
7
8
9
10
11
12
typedef
enum real_color {
...
} real_color_t;

static uint16_t *video_memory = (uint16_t *)0xB8000;

void console_clear();

void console_write(char *cstr);

void console_write_color(char *cstr, real_color_t back, real_color_t fore);

函数的具体实现

需要关注的点:

  1. 关于video_memory地址的问题

按原始的8086处理器,只有1M的寻址空间,然后又要做到屏幕显示之类的,因此对1M的内存空间进行了分段,具体分段信息如下表

起始 结束 大小 用途
FFFF0 FFFFF 16B BIOS入口地址,此地址也属于BIOS代码同样属于顶部的640KB字节。只是为了强调其入口地址才单独贴出来。此处16字节的内容是跳转指令jmp f000:e05b
F0000 FFFEF 64KB-16B 系统BIOS范围是F0000~FFFFF共640KB,为说明入口地址,将最上面的16字节从此处去掉了,所以此处终止地址是0XFFFEF
C8000 EFFFF 160KB 映射硬件适配器的ROM或内存映射式I/O
C0000 C7FFF 32KB 显示适配器BIOS
B8000 BFFFF 32KB 用于文本模式显示适配器
B0000 B7FFF 32KB 用于黑白显示适配器
A0000 AFFFF 64KB 用于彩色显示适配器
9FC00 9FFFF 1KB EBDA(Extended BIOS Data Area)扩展BIOS数据区
7E00 9FBFF 622080B 约608KB可用区域
7C00 7DFF 512B MBR被BIOS加载到此处,共512字节
500 7BFF 30464B 约30KB可用区域
400 4FF 256B BIOS Data Area(BIOS数据区)
000 3FF 1KB Interrupt Vector Table(中断向量表)

可以看到B800h处用于文本模式显示适配器,也有之前的7C00h处为我们之前尝试写过的的MBR。

字符串处理相关函数

在之后的过程中会用到许多相关信息打印,所以实现了相关的字符串处理函数,相关API如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
void memcpy(uint8_t *dest, const uint8_t *src, uint32_t len);

void memset(void *dest, uint8_t val, uint32_t len);

void bzero(void *dest, uint32_t len);

int strcmp(const char *str1, const char *str2);

char *strcpy(char *dest, const char *src);

char *strcat(char *dest, const char *src);

int strlen(const char *src);

函数的具体实现

格式化字符串处理显示

内核态的打印函数是printk,这里仿照printf进行简单实现,API如下。

1
int printk(const char *format, ...);

这里主要是对任意个参数的处理,使用的GCC内置的对多个参数的处理方式。

1
2
3
4
5
typedef __builtin_va_list va_list;

#define va_start(ap, last) (__builtin_va_start(ap, last))
#define va_arg(ap, type) (__builtin_va_arg(ap, type))
#define va_end(ap)

函数的具体实现

(偷懒目前只实现了%d,%x,%s的格式化输出,还有点bug)

内核出错时的处理

实模式与保护模式