跳至主要内容

muxOS 开发日志(一)—— 引导加载程序、GDT、IDT 和 PIC

引导加载程序

1.1 什么是引导加载程序?

加载引导程序是计算机或设备启动时的关键步骤,它负责从硬件初始化到操作系统内核加载的全过程。简单来说,就是让设备从“通电”状态进入“可运行操作系统”的状态。

常见的引导加载程序有很多,例如:

  • GRUB2
  • Syslinux
  • U-Boot
  • systemd-boot

为什么 muxOS 选择 GRUB2?

在操作系统的开发中,一个常见的讨论是:

是否需要从零编写引导加载程序?

这其实是风格问题,可以重头开始,直接编写引导扇区,再写一个最小内核,然后再构建。也可以跳过,直接采取现成的引导加载程序。(关于自己开发程序是否有价值还是浪费时间,这点值得讨论)。

但是在实践中,这一阶段成本非常高,且与内核核心机制关联较弱。

muxOS的目标是:

优先实现一个具备基本进程管理和系统调用能力的内核

所以在技术路线的选择上做出了取舍。

GRUB 工作流程

grub.cfg → 找到 kernel.elf → 验证 multiboot magic → 加载到 1MB → 跳到 _start → 调用 kernel_main(magic, mbi)

关于 GRUB 详细的介绍文档,请参考 GRUB - OSDev 维基 多重启动规范版本 0.6.96

VGA

muxOS 封装了一些基础函数以用于正确显示 VGA 画面。

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
// vga.h

#ifndef VGA_H
#define VGA_H

#include <stdint.h>
/* Hardware text mode color constants. */
enum vga_color {
VGA_COLOR_BLACK = 0,
VGA_COLOR_BLUE = 1,
VGA_COLOR_GREEN = 2,
VGA_COLOR_CYAN = 3,
VGA_COLOR_RED = 4,
VGA_COLOR_MAGENTA = 5,
VGA_COLOR_BROWN = 6,
VGA_COLOR_LIGHT_GREY = 7,
VGA_COLOR_DARK_GREY = 8,
VGA_COLOR_LIGHT_BLUE = 9,
VGA_COLOR_LIGHT_GREEN = 10,
VGA_COLOR_LIGHT_CYAN = 11,
VGA_COLOR_LIGHT_RED = 12,
VGA_COLOR_LIGHT_MAGENTA = 13,
VGA_COLOR_LIGHT_BROWN = 14,
VGA_COLOR_WHITE = 15,
};
/* 退格 */
static void vga_backspace();
/* 输出打印一行文字,*/
void print(const char *str, unsigned char color);
/* 刷新光标 */
void update_hw_cursor();
/* 在屏幕指定位置输出一行文字 */
void print_at(const char *str, const int x, const int y, unsigned char color);
/* 清屏 */
void clear_screen();
/* 输出十进制数字 */
void print_hex(uint32_t val) ;
#endif

关于介绍 VGA 的详细文档,请参考 文本模式光标 - OSDev 维基 VGA 硬件 - OSDev 维基

GDT

2.1 什么是 GDT?

一张描述“内存段”的表,CPU 用它来解释地址和权限

在 IA-32 和 x86-64 架构上,更准确地说,在**保护模式长模式下中断服务例程和大量内存管理通过描述符表来控制。每个描述符存储 CPU 可能在某个时刻需要的单个对象(例如服务例程、任务、代码或数据块)的信息。例如,如果你尝试向段寄存**器加载新值,CPU 需要执行安全和访问控制检查,以确认你是否有权访问该特定内存区域。检查完成后,有用的值(如最低和最高地址)会缓存在不可见的CPU寄存器中。

介绍

muxOS 采取 GDT 平坦模式将内存划分为受保护区域,以及分页来保护内存。

Index 描述 Base Limit Access Flags 特权级 说明
0 Null Descriptor 0x00000000 0x00000000 0x00 0x00 - 必须为空(CPU 保留)
1 Kernel Code 0x00000000 0xFFFFFFFF 0x9A 0xCF Ring 0 内核代码段(可执行、可读)
2 Kernel Data 0x00000000 0xFFFFFFFF 0x92 0xCF Ring 0 内核数据段(可读写)
3 User Code 0x00000000 0xFFFFFFFF 0xFA 0xCF Ring 3 用户代码段(可执行、可读)
4 User Data 0x00000000 0xFFFFFFFF 0xF2 0xCF Ring 3 用户数据段(可读写)
5 TSS (占位) 0x00000000 0x00000000 0x00 0x00 - 任务状态段(尚未初始化)

关于 GDT 的详细介绍,请参考 GDT 教程 - OSDev 维基

IDT

3.1 什么是 IDT?

一张“中断/异常 → 处理函数”的映射表

中断描述符表(Interrupt Descriptor Table,IDT) 是保护模式下的重要数据结构,用于将中断或异常向量与其对应的处理程序关联起来。IDT 是一个包含 256 个描述符的数组,每个描述符占用 8 字节,存储了中断处理程序的地址及相关属性。

3.2 介绍

muxOS 在目前的开发版本 (截至 2026/4/26) 中,实现了以下处理:

向量号 类型 名称 处理函数 说明
0x00 Exception Divide Error isr0 除零异常(CPU自动触发)
0x06 Exception Invalid Opcode isr6 无效指令异常
0x0D Exception General Protection isr13 通用保护错误(权限/段错误)
0x0E Exception Page Fault isr14 页错误(分页机制核心异常)
0x21 Hardware IRQ Keyboard Interrupt keyboard_handler 键盘中断(IRQ1)
0x20 Hardware IRQ Timer Interrupt irq0_stub 时钟中断(IRQ0,调度核心)
0x80 Software Int System Call syscall_stub 用户态系统调用入口(int 0x80)

关于 IDT 详细介绍, 请参考中断描述符表 - OSDev 维基

PIC

4.1 什么是PIC ?

它负责把“外设中断”转发给 CPU,并决定优先级

PIC(Programmable Interrupt Controller)是早期 x86 架构中用于管理外设中断的硬件模块,其中最经典的实现是Intel 8259

4.2 介绍

muxOS 启动时首先对 PIC 进行重新映射(remap),避免与 CPU 异常向量冲突:

  • IRQ0–IRQ7 → 0x20–0x27
  • IRQ8–IRQ15 → 0x28–0x2F

以及处理了键盘中断的情况,处理流程如下

  • 1.键盘按下(硬件触发)
  • 2.IRQ1(外设中断请求)
  • 3.Intel 8259 PIC 进行中断转发
  • 4.映射为 CPU 中断向量 INT 0x21
  • 5.CPU 根据 IDT 查询 IDT[0x21]
  • 6.跳转执行 keyboard_handler()
  • 7.读取键盘扫描码并处理输入
  • 8.调用 pic_eoi(1) 通知 PIC 该中断已完成

关于 PIC 8259 的介绍,请参考 PIC 8259 - OSDev 维基

关于本文

由 Latos 撰写,采用 CC BY-NC 4.0 许可协议。

#OS #muxOS #C