构建操作系统不仅仅是编写代码,更是对计算机底层运行机制的深度探索。通过亲手实现每一个核心模块,我们能够真正理解软硬件之间的交互方式。
一、 引导程序的启动与模式切换
操作系统的第一步是从 BIOS 将控制权移交给我们的代码。我们需要编写汇编引导程序,完成实模式到保护模式的关键切换,为后续的内核运行搭建好最基础的硬件环境。
; 初始化 GDT 全局描述符表
lgdt [gdt_ptr]
; 开启 A20 地址线,以便访问 1MB 以上的内存
in al, 0x92
or al, 00000010b
out 0x92, al
; 设置 CR0 寄存器的 PE 位,进入保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 通过远跳转刷新流水线,进入 32 位保护模式
jmp dword Selector_Code32:ProtectedModeEntry
二、 内存管理机制的实现
内存管理是内核的基石。我们需要设计物理内存管理器和虚拟内存管理器,实现页表的映射,将线性地址转换为物理地址,确保每个进程都有独立的内存空间。
// 页目录项与页表项的数据结构定义
typedef struct {
unsigned int present : 1; // 存在位
unsigned int rw : 1; // 读写位
unsigned int user : 1; // 用户/ supervisor 位
unsigned int pwt : 1; // 页级直写
unsigned int pcd : 1; // 页级缓存禁用
unsigned int accessed : 1; // 访问位
unsigned int dirty : 1; // 脏位
unsigned int pat : 1; // 页属性表
unsigned int global : 1; // 全局位
unsigned int available : 3; // 可用位
unsigned int frame : 20; // 帧地址
} page_entry_t;
// 映射虚拟地址到物理地址的核心函数
void map_page(void *phys_addr, void *virt_addr, unsigned int flags) {
unsigned int page_index = (unsigned int)virt_addr / 0x1000;
unsigned int table_index = page_index / 1024;
unsigned int entry_index = page_index % 1024;
// 获取或创建页表
unsigned int *table = get_or_create_page_table(table_index);
// 设置页表项
table[entry_index] = ((unsigned int)phys_addr) | flags;
}
三、 进程调度与中断处理
多任务环境的实现依赖于高效的进程调度和中断处理机制。通过时钟中断触发调度器,在不同的进程上下文之间进行切换,实现看似并行运行的假象。
// 进程控制块 (PCB) 结构
struct task_struct {
volatile unsigned int state; // 进程状态
unsigned long pid; // 进程标识符
struct pt_regs *regs; // 寄存器保存区域
struct task_struct *next; // 链表指针
unsigned long kernel_stack; // 内核栈顶
};
// 简单的调度器函数
void schedule() {
struct task_struct *prev = current;
struct task_struct *next = get_next_task();
if (prev == next) return;
// 切换进程上下文
switch_to(prev, next);
}
// 时钟中断处理程序
void timer_handler(struct pt_regs *regs) {
// 更新系统时间
jiffies++;
// 当前进程时间片递减
if (current->time_slice > 0) {
current->time_slice--;
}
// 触发调度
schedule();
}