前言:为什么要手写操作系统
2013 年 MIT 的 6.828 课程放出了手写操作系统的教程,当时我还是大二学生,花了整整两周时间才勉强跑通第一个版本。如今时隔十年,我决定用三天时间重新挑战这个经典项目,并且将整个过程记录下来。手写操作系统并非要实现 Linux 那样的庞然大物,而是通过最小化实现理解操作系统的核心原理。
这个项目的意义在于:
- 理解计算机启动的底层机制
- 掌握内存管理的基本原理
- 实现简单的进程调度系统
- 构建最小化文件系统
- 打通从汇编到 C 语言的底层接口
我将使用 QEMU 模拟器作为运行环境,用 GCC 交叉编译工具链开发,整个操作系统将分为三个主要模块:引导程序、内核核心、基本功能模块。预计三天的时间分配如下:
- 第一天:环境搭建与引导程序开发
- 第二天:内核架构与内存管理
- 第三天:进程调度与文件系统
第一天:环境搭建与引导程序开发
开发环境准备
首先需要搭建一个适合开发操作系统的环境,我选择在 Ubuntu 22.04 上进行开发,需要安装以下工具:
bash
# 安装交叉编译工具链
sudo apt-get install build-essential gcc-multilib qemu-system-x86
# 安装磁盘操作工具
sudo apt-get install binutils-parted
# 安装图形化调试工具
sudo apt-get install gdb
接下来创建项目目录结构:
bash
mkdir -p mini-os/{boot kernel fs drivers utils}
cd mini-os
touch Makefile
引导程序原理分析
计算机启动的第一个程序是 BIOS(Basic Input/Output System),它会加载主引导记录 (MBR) 到内存 0x7c00 地址处执行。MBR 只有 512 字节,其中最后两个字节必须是 0x55 和 0xaa,这是引导程序的标志。
引导程序的主要任务是:
- 初始化基本硬件
- 加载内核到内存
- 跳转执行内核代码
引导程序实现
首先编写引导程序的汇编代码,保存为boot/boot.S
:
assembly
.global start
start:
; 初始化寄存器
mov ax, 0x07c0 ; 设置数据段基址
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00 ; 设置栈顶
; 清屏
mov ax, 0x0600
mov bx, 0x0700
mov cx, 0
mov dx, 0x184f
int 0x10 ; 调用BIOS中断清屏
; 显示启动信息
mov ax, 0x0e00
mov bx, 0x0f
mov cx, boot_msg
print_msg:
mov al, [cx]
cmp al, 0
je load_kernel
int 0x10
inc cx
jmp print_msg
load_kernel:
; 加载内核到0x10000地址
mov ax, 0x0201 ; 读取1个扇区
mov bx, 0x1000 ; 加载到0x1000:0
mov cx, 0x0002 ; 从第2个扇区开始读取
mov dx, 0x0000 ; 驱动器0
int 0x13 ; 调用磁盘读取中断
; 检查读取是否成功
jc error
; 跳转到内核入口
mov ax, 0x1000
mov ds, ax
jmp 0x1000:0x0000 ; 跳转到内核地址
error:
; 错误处理
mov cx, error_msg
jmp print_msg
boot_msg:
.ascii "Mini OS Booting...\r\n"
.byte 0
error_msg:
.ascii "Error loading kernel!\r\n"
.byte 0
; 填充到512字节
times 510-($-$$) db 0
dw 0xaa55
这段汇编代码完成了引导程序的基本功能:初始化寄存器、清屏、显示启动信息、从磁盘读取内核到内存 0x10000 地址,最后跳转到内核执行。注意最后两行代码times 510-($-$$) db 0
和dw 0xaa55
用于将引导程序填充到 512 字节,并添加引导标志。
内核入口设计
接下来编写内核的入口函数,保存为kernel/main.c
:
c
运行
#include "types.h"
#include "printf.h"
void kernel_main() {
// 初始化控制台
console_init();
// 显示内核启动信息
printf("Mini OS Kernel Started!\n");
printf("CPU: %s\n", get_cpu_info());
printf("Memory: %d MB\n", get_memory_size());
// 初始化内存管理
mem_init();
// 初始化进程系统
process_init();
// 启动第一个进程
start_process(0, "init");
// 进入主循环
while (1) {
schedule();
}
}
这里定义了内核的主函数kernel_main
,它会初始化各个子系统并进入主循环。现在需要编写相关的头文件和辅助函数,首先是types.h
:
c
运行
#ifndef _TYPES_H_
#define _TYPES_H_
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long u64;
typedef signed char s8;
typedef signed short s16;
typedef signed int s32;
typedef signed long s64;
typedef u32 address;
typedef u32 pid_t;
#endif /* _TYPES_H_ */
然后是printf.h
和printf.c
,实现一个简单的控制台输出函数:
c
运行
#ifndef _PRINTF_H_
#define _PRINTF_H_
void console_init();
void printf(const char* format, ...);
void print_char(char c);
void print_string(const char* str);
#endif /* _PRINTF_H_ */
c
运行
#include "printf.h"
#include "types.h"
// 控制台缓冲区
#define CONSOLE_WIDTH 80
#define CONSOLE_HEIGHT 25
#define CONSOLE_SIZE (CONSOLE_WIDTH * CONSOLE_HEIGHT)
#define VIDEO_MEMORY (unsigned char*)0xb8000
// 控制台坐标
static int console_x = 0;
static int console_y = 0;
// 初始化控制台
void console_init() {
// 清屏
u8* video = VIDEO_MEMORY;
for (int i = 0; i < CONSOLE_SIZE * 2; i += 2) {
video[i] = ' ';
video[i+1] = 0x07; // 黑底白字
}
console_x = 0;
console_y = 0;
}
// 打印单个字符
void print_char(char c) {
u8* video = VIDEO_MEMORY;
int index = (console_y * CONSOLE_WIDTH + console_x) * 2;
if (c == '\n') {
console_x = 0;
console_y++;
} else if (c == '\r') {
console_x = 0;
} else if (c == '\t') {
console_x = (console_x + 4) & ~3;
} else {
video[index] = c;
console_x++;
}
// 处理换行
if (console_x >= CONSOLE_WIDTH) {
console_x = 0;
console_y++;
}
// 处理滚动
if (console_y >= CONSOLE_HEIGHT) {
// 向上滚动一行
u8* src = VIDEO_MEMORY + 2 * CONSOLE_WIDTH;
u8* dst = VIDEO_MEMORY;
for (int i = 0; i < (CONSOLE_HEIGHT - 1) * CONSOLE_WIDTH * 2; i++) {
dst[i] = src[i];
}
// 清空最后一行
for (int i = 0; i < CONSOLE_WIDTH * 2; i++) {
dst[(CONSOLE_HEIGHT - 1) * CONSOLE_WIDTH * 2 + i] = 0;
}
console_y = CONSOLE_HEIGHT - 1;
}
}
// 打印字符串
void print_string(const char* str) {
while (*str) {
print_char(*str++);
}
}
// 简单的printf实现
void printf(const char* format, ...) {
char* str = (char*)format;
while (*str) {
if (*str == '%') {
str++;
switch (*str) {
case 's':
str++;
print_string((char*)str);
while (*str && *str != '%') str++;
break;
case 'd':
str++;
// 整数打印逻辑
break;
case 'c':
str++;
print_char(*str);
str++;
break;
default:
print_char(*str);
str++;
break;
}
} else {
print_char(*str++);
}
}
}
构建与测试
现在需要编写 Makefile 来构建整个系统:
makefile
# Mini OS Makefile
OBJS_BOOT = boot/boot.o
OBJS_KERNEL = kernel/main.o kernel/printf.o kernel/types.o
OBJS = $(OBJS_BOOT) $(OBJS_KERNEL)
# 交叉编译工具
CC = i386-elf-gcc
AS = i386-elf-as
LD = i386-elf-ld
OBJCOPY = i386-elf-objcopy
# 构建引导程序
boot/boot.o: boot/boot.S
$(AS) -o $@ $<
# 构建内核
kernel/main.o: kernel/main.c kernel/types.h kernel/printf.h
$(CC) -c -o $@ $< -m32 -fno-builtin -fno-stack-protector -nostdlib -Ikernel
kernel/printf.o: kernel/printf.c kernel/printf.h
$(CC) -c -o $@ $< -m32 -fno-builtin -fno-stack-protector -nostdlib -Ikernel
kernel/types.o: kernel/types.h
$(CC) -c -o $@ kernel/types.h -m32 -fno-builtin -fno-stack-protector -nostdlib
# 链接内核
kernel.bin: $(OBJS_KERNEL)
$(LD) -Ttext 0x10000 -o kernel.elf $^
$(OBJCOPY) -O binary kernel.elf kernel.bin
# 构建磁盘镜像
mini-os.img: boot/boot.o kernel.bin
dd if=/dev/zero of=mini-os.img bs=512 count=2880
dd if=boot/boot.o of=mini-os.img bs=512 conv=notrunc
dd if=kernel.bin of=mini-os.img bs=512 seek=2 conv=notrunc
# 运行在QEMU
run: mini-os.img
qemu-system-i386 -drive format=raw,file=mini-os.img
# 清理
clean:
rm -f $(OBJS) kernel.elf kernel.bin mini-os.img
现在可以尝试构建并运行:
bash
make
make run
如果一切顺利,QEMU 会显示 "Mini OS Booting..." 和 "Mini OS Kernel Started!" 的字样。这说明引导程序和内核已经成功加载运行。
遇到的问题与解决方案
在第一天的开发中,我遇到了几个关键问题:
-
引导程序无法加载内核:最初的引导程序在读取磁盘时没有错误处理,导致内核加载失败。后来添加了错误处理代码,并在 BIOS 中断调用后检查错误标志。
-
地址空间问题:内核被加载到 0x10000 地址,但最初的跳转指令使用了错误的段地址,导致无法正确执行。后来将段地址设置为 0x1000,偏移地址 0x0000,确保正确跳转。
-
控制台显示乱码:由于没有正确设置字符属性,显示的字符颜色混乱。后来设置了黑底白字的属性(0x07),解决了显示问题。
第二天:内核架构与内存管理
内核架构设计
经过第一天的努力,我们已经实现了基本的引导和内核启动。今天的主要任务是完善内核架构,实现内存管理系统。一个最小化的内存管理系统需要包含:
- 内存检测
- 内存分区
- 内存分配与释放
- 简单的虚拟内存机制
内存检测实现
首先需要检测系统中的物理内存大小,这可以通过 BIOS 中断 0x15 实现:
c
运行
#include "memory.h"
#include "types.h"
// 内存检测
u32 get_memory_size() {
u32 memory_size = 0;
u32 extended_memory = 0;
// 检测扩展内存 (XMS)
struct memory_info {
u32 size;
u32 addr;
} __attribute__((packed));
struct memory_info mem_info;
__asm__ volatile (
"mov eax, 0xE820\n"
"mov ebx, 0\n"
"mov ecx, 20\n"
"mov edx, 0x534D4150\n"
"mov di, %0\n"
"int 0x15\n"
: "=d" (mem_info)
:
: "eax", "ebx", "ecx", "edx", "edi"
);
extended_memory = mem_info.size;
// 基本内存为640KB
memory_size = 640;
// 加上扩展内存,单位为KB
memory_size += extended_memory / 1024;
return memory_size;
}
这段代码通过 BIOS 中断 0x15 获取系统内存信息,包括基本内存和扩展内存,然后计算出总内存大小。
内存管理数据结构
接下来定义内存管理所需的数据结构,保存为kernel/memory.h
:
c
运行
#ifndef _MEMORY_H_
#define _MEMORY_H_
#include "types.h"
// 内存块结构
typedef struct memory_block {
u32 start_addr; // 块起始地址
u32 size; // 块大小
u32 used; // 是否已使用
struct memory_block* next; // 下一块
} memory_block;
// 内存管理结构
typedef struct memory_manager {
memory_block* free_list; // 空闲块链表
u32 total_memory; // 总内存大小
u32 used_memory; // 已用内存大小
} memory_manager;
// 全局内存管理器
extern memory_manager mem_manager;
// 内存管理函数
void mem_init();
void* kmalloc(u32 size);
void kfree(void* ptr);
u32 get_free_memory();
u32 get_total_memory();
#endif /* _MEMORY_H_ */
内存管理实现
现在实现内存管理的核心功能,保存为kernel/memory.c
:
c
运行
#include "memory.h"
#include "types.h"
#include "string.h"
// 全局内存管理器
memory_manager mem_manager;
// 初始化内存管理
void mem_init() {
// 初始化内存管理器
mem_manager.free_list = NULL;
mem_manager.total_memory = get_memory_size() * 1024;
mem_manager.used_memory = 0;
// 假设内核已占用前1MB内存
u32 kernel_size = 1 * 1024 * 1024;
u32 free_start = kernel_size;
u32 free_size = mem_manager.total_memory - kernel_size;
// 创建初始空闲块
memory_block* first_block = (memory_block*)free_start;
first_block->start_addr = free_start;
first_block->size = free_size;
first_block->used = 0;
first_block->next = NULL;
mem_manager.free_list = first_block;
}
// 内存分配
void* kmalloc(u32 size) {
// 对齐到4字节
size = (size + 3) & ~3;
memory_block* current = mem_manager.free_list;
memory_block* prev = NULL;
while (current) {
// 找到足够大的空闲块
if (current->size >= size) {
// 如果块大小正好等于请求大小
if (current->size == size) {
if (prev) {
prev->next = current->next;
} else {
mem_manager.free_list = current->next;
}
current->used = 1;
mem_manager.used_memory += size;
return (void*)current->start_addr;
}
// 如果块大小大于请求大小,分割块
else {
memory_block* new_block = (memory_block*)((u32)current + size + sizeof(memory_block));
new_block->start_addr = (u32)current + size + sizeof(memory_block);
new_block->size = current->size - size - sizeof(memory_block);
new_block->used = 0;
new_block->next = current->next;
current->size = size;
current->used = 1;
current->next = new_block;
if (prev) {
prev->next = current;
} else {
mem_manager.free_list = current;
}
mem_manager.used_memory += size;
return (void*)current->start_addr;
}
}
prev = current;
current = current->next;
}
// 内存不足
return NULL;
}
// 内存释放
void kfree(void* ptr) {
if (!ptr) return;
memory_block* block = (memory_block*)((u32)ptr - sizeof(memory_block));
block->used = 0;
// 合并相邻空闲块
memory_block* current = mem_manager.free_list;
memory_block* prev = NULL;
memory_block* next = block->next;
// 查找插入位置
while (current && current < block) {
prev = current;
current = current->next;
}
// 插入到合适位置
if (prev) {
prev->next = block;
} else {
mem_manager.free_list = block;
}
block->next = current;
// 合并前一个空闲块
if (prev && !prev->used) {
prev->size += block->size + sizeof(memory_block);
prev->next = block->next;
block = prev;
}
// 合并后一个空闲块
if (next && !next->used) {
block->size += next->size + sizeof(memory_block);
block->next = next->next;
}
mem_manager.used_memory -= block->size;
}
// 获取空闲内存大小
u32 get_free_memory() {
return mem_manager.total_memory - mem_manager.used_memory;
}
// 获取总内存大小
u32 get_total_memory() {
return mem_manager.total_memory;
}
简单虚拟内存实现
为了简化实现,我们先实现一个简单的虚拟内存映射机制,保存为kernel/vmm.h
和kernel/vmm.c
:
c
运行
#ifndef _VMM_H_
#define _VMM_H_
#include "types.h"
// 页表项结构
typedef struct page_table_entry {
u32 present:1; // 是否存在
u32 read_write:1; // 读写权限
u32 user_supervisor:1; // 用户/管理员权限
u32 write_through:1; // 写透
u32 cache_disable:1; // 禁用缓存
u32 accessed:1; // 已访问
u32 dirty:1; // 已修改
u32 large_page:1; // 大页
u32 available:3; // 保留位
u32 frame:20; // 物理帧号
} __attribute__((packed));
// 页目录项结构
typedef struct page_directory_entry {
u32 present:1; // 是否存在
u32 read_write:1; // 读写权限
u32 user_supervisor:1; // 用户/管理员权限
u32 write_through:1; // 写透
u32 cache_disable:1; // 禁用缓存
u32 accessed:1; // 已访问
u32 available:3; // 保留位
u32 large_page:1; // 大页
u32 available2:1; // 保留位
u32 reserved:1; // 保留位
u32 frame:20; // 物理帧号
} __attribute__((packed));
// 初始化虚拟内存
void vmm_init();
// 映射虚拟地址到物理地址
void* vmm_map(u32 virtual_addr, u32 physical_addr, u32 size);
// 解除映射
void vmm_unmap(u32 virtual_addr, u32 size);
#endif /* _VMM_H_ */
c
运行
#include "vmm.h"
#include "memory.h"
#include "types.h"
// 页大小
#define PAGE_SIZE 4096
// 页表项数量
#define PAGE_ENTRIES 1024
// 页目录大小
#define PAGE_DIRECTORY_SIZE (PAGE_ENTRIES * sizeof(page_directory_entry))
// 页表大小
#define PAGE_TABLE_SIZE (PAGE_ENTRIES * sizeof(page_table_entry))
// 页目录指针
page_directory_entry* page_directory;
// 初始化虚拟内存
void vmm_init() {
// 分配页目录
page_directory = (page_directory_entry*)kmalloc(PAGE_DIRECTORY_SIZE);
memset(page_directory, 0, PAGE_DIRECTORY_SIZE);
// 映射内核空间(0-3GB)
for (u32 i = 0; i < PAGE_ENTRIES; i++) {
// 分配页表
page_table_entry* page_table = (page_table_entry*)kmalloc(PAGE_TABLE_SIZE);
memset(page_table, 0, PAGE_TABLE_SIZE);
// 映射页表
for (u32 j = 0; j < PAGE_ENTRIES; j++) {
u32 physical_addr = i * PAGE_ENTRIES * PAGE_SIZE + j * PAGE_SIZE;
page_table[j].present = 1;
page_table[j].read_write = 1;
page_table[j].frame = physical_addr / PAGE_SIZE;
}
// 映射页表到页目录
page_directory[i].present = 1;
page_directory[i].read_write = 1;
page_directory[i].frame = (u32)page_table / PAGE_SIZE;
}
// 启用分页
__asm__ volatile (
"mov eax, %0\n"
"mov cr3, eax\n"
"mov eax, cr0\n"
"or eax, 0x80000000\n"
"mov cr0, eax"
:
: "r" (page_directory)
: "eax", "cr3", "cr0"
);
}
// 映射虚拟地址到物理地址
void* vmm_map(u32 virtual_addr, u32 physical_addr, u32 size) {
u32 pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
u32 dir_idx = virtual_addr / (PAGE_ENTRIES * PAGE_SIZE);
u32 table_idx = (virtual_addr / PAGE_SIZE) / PAGE_ENTRIES;
u32 page_idx = (virtual_addr / PAGE_SIZE) % PAGE_ENTRIES;
// 获取页目录项
page_directory_entry* dir_entry = &page_directory[dir_idx];
// 如果页目录项不存在,分配页表
if (!dir_entry->present) {
page_table_entry* page_table = (page_table_entry*)kmalloc(PAGE_TABLE_SIZE);
memset(page_table, 0, PAGE_TABLE_SIZE);
dir_entry->present = 1;
dir_entry->read_write = 1;
dir_entry->frame = (u32)page_table / PAGE_SIZE;
}
// 获取页表
page_table_entry* page_table = (page_table_entry*)(dir_entry->frame * PAGE_SIZE);
// 映射页面
for (u32 i = 0; i < pages; i++) {
page_table[page_idx + i].present = 1;
page_table[page_idx + i].read_write = 1;
page_table[page_idx + i].frame = (physical_addr + i * PAGE_SIZE) / PAGE_SIZE;
}
return (void*)virtual_addr;
}
// 解除映射
void vmm_unmap(u32 virtual_addr, u32 size) {
u32 pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
u32 dir_idx = virtual_addr / (PAGE_ENTRIES * PAGE_SIZE);
u32 table_idx = (virtual_addr / PAGE_SIZE) / PAGE_ENTRIES;
u32 page_idx = (virtual_addr / PAGE_SIZE) % PAGE_ENTRIES;
// 获取页目录项
page_directory_entry* dir_entry = &page_directory[dir_idx];
// 如果页目录项存在
if (dir_entry->present) {
// 获取页表
page_table_entry* page_table = (page_table_entry*)(dir_entry->frame * PAGE_SIZE);
// 解除页面映射
for (u32 i = 0; i < pages; i++) {
page_table[page_idx + i].present = 0;
}
}
}
内核架构完善
现在完善内核的整体架构,添加内存管理的初始化:
c
运行
#include "types.h"
#include "printf.h"
#include "memory.h"
#include "vmm.h"
#include "process.h"
void kernel_main() {
// 初始化控制台
console_init();
// 显示内核启动信息
printf("Mini OS Kernel Started!\n");
printf("CPU: %s\n", get_cpu_info());
u32 memory_size = get_memory_size();
printf("Memory: %d MB\n", memory_size);
// 初始化内存管理
mem_init();
printf("Memory manager initialized, free: %d MB\n", get_free_memory() / 1024 / 1024);
// 初始化虚拟内存
vmm_init();
printf("Virtual memory initialized\n");
// 初始化进程系统
process_init();
// 启动第一个进程
start_process(0, "init");
// 进入主循环
while (1) {
schedule();
}
}
当天总结
第二天的开发主要集中在内核架构和内存管理系统。我遇到的主要挑战是:
- 内存检测的兼容性:不同计算机的 BIOS 对内存检测中断的支持不同,需要处理各种边界情况。
- 内存分配算法:最初实现的简单首次适应算法在大块内存分配时效率较低,后来添加了分割和合并功能。
- 虚拟内存的初始化:分页机制的初始化需要正确设置页目录和页表,并且要注意特权级的问题。
通过今天的努力,我们已经实现了一个基本的内存管理系统,这为明天实现进程调度和文件系统打下了基础。现在系统可以正确检测内存、分配和释放内存,并实现了简单的虚拟内存映射。
第三天:进程调度与文件系统
进程调度系统设计
经过前两天的开发,我们已经实现了引导程序和基本的内存管理。今天的主要任务是实现进程调度系统和简单的文件系统,这是操作系统的核心功能。
进程调度系统需要包含:
- 进程控制块 (PCB) 设计
- 进程状态管理
- 进程创建与销毁
- 进程调度算法
- 上下文切换机制
首先定义进程控制块结构,保存为kernel/process.h
:
c
运行
#ifndef _PROCESS_H_
#define _PROCESS_H_
#include "types.h"
#include "memory.h"
// 进程状态
typedef enum {
PROCESS_RUNNING,
PROCESS_READY,
PROCESS_WAITING,
PROCESS_TERMINATED
} process_state;
// 进程控制块
typedef struct process_control_block {
pid_t pid; // 进程ID
char name[32]; // 进程名称
process_state state; // 进程状态
u32* stack; // 进程栈
u32 stack_size; // 栈大小
u32 entry_point; // 入口地址
u32* page_table; // 页表指针
struct process_control_block* next; // 下一个进程
} process_control_block;
// 全局进程列表
extern process_control_block* process_list;
extern process_control_block* current_process;
// 进程管理函数
void process_init();
pid_t create_process(u32 entry_point, const char* name, u32 stack_size);
void start_process(pid_t pid, const char* name);
void schedule();
void yield();
void exit_process();
#endif /* _PROCESS_H_ */
进程调度实现
接下来实现进程调度的核心功能,保存为kernel/process.c
:
c
运行
#include "process.h"
#include "memory.h"
#include "vmm.h"
#include "printf.h"
#include "string.h"
// 全局进程列表
process_control_block* process_list = NULL;
process_control_block* current_process = NULL;
pid_t next_pid = 1;
// 初始化进程系统
void process_init() {
process_list = NULL;
current_process = NULL;
next_pid = 1;
printf("Process system initialized\n");
}
// 创建进程
pid_t create_process(u32 entry_point, const char* name, u32 stack_size) {
// 分配PCB
process_control_block* pcb = (process_control_block*)kmalloc(sizeof(process_control_block));
if (!pcb) {
printf("Failed to create process: out of memory\n");
return 0;
}
// 分配进程栈
pcb->stack = (u32*)kmalloc(stack_size);
if (!pcb->stack) {
kfree(pcb);
printf("Failed to create process: out of memory\n");
return 0;
}
// 初始化PCB
pcb->pid = next_pid++;
strncpy(pcb->name, name, 31);
pcb->name[31] = 0;
pcb->state = PROCESS_READY;
pcb->entry_point = entry_point;
pcb->stack_size = stack_size;
pcb->page_table = NULL;
pcb->next = NULL;
// 初始化进程栈(模拟函数调用栈)
u32* stack_ptr = pcb->stack + stack_size / sizeof(u32) - 1;
// 模拟函数返回地址
stack_ptr--;
*stack_ptr = 0; // 模拟返回地址
// 模拟参数
stack_ptr--;
*stack_ptr = 0; // 模拟参数
pcb->stack = stack_ptr;
// 添加到进程列表
if (!process_list) {
process_list = pcb;
} else {
process_control_block* current = process_list;
while (current->next) {
current = current->next;
}
current->next = pcb;
}
printf("Created process: %s (PID: %d)\n", name, pcb->pid);
return pcb->pid;
}
// 启动进程
void start_process(pid_t pid, const char* name) {
process_control_block* pcb = process_list;
while (pcb && pcb->pid != pid) {
pcb = pcb->next;
}
if (pcb) {
pcb->state = PROCESS_RUNNING;
current_process = pcb;
printf("Started process: %s (PID: %d)\n", name, pid);
} else {
// 创建新进程
create_process((u32)user_process_entry, name, 4096);
}
}
// 进程调度
void schedule() {
if (!process_list) return;
process_control_block* next = process_list;
while (next && next->state != PROCESS_RUNNING) {
next = next->next;
}
if (!next) next = process_list;
// 切换到下一个进程
if (next != current_process) {
current_process = next;
printf("Switching to process: %s (PID: %d)\n", current_process->name, current_process->pid);
// 切换页表
if (current_process->page_table) {
__asm__ volatile (
"mov eax, %0\n"
"mov cr3, eax"
:
: "r" (current_process->page_table)
: "eax", "cr3"
);
}
// 切换栈
__asm__ volatile (
"mov esp, %0\n"
:
: "r" (current_process->stack)
: "esp"
);
// 模拟进程切换
__asm__ volatile (
"pushfd\n"
"popfd\n"
:
:
: "memory"
);
}
}
// 进程让步
void yield() {
if (current_process) {
current_process->state = PROCESS_READY;
schedule();
}
}
// 进程退出
void exit_process() {
if (current_process) {
printf("Process %s (PID: %d) exited\n", current_process->name, current_process->pid);
current_process->state = PROCESS_TERMINATED;
schedule();
}
}
// 用户进程入口(模拟)
void user_process_entry() {
while (1) {
printf("User process running: %s (PID: %d)\n", current_process->name, current_process->pid);
yield(); // 让步给其他进程
}
}
简单文件系统实现
接下来实现一个简单的文件系统,我们使用 FAT12 格式,这是一种简单的文件系统格式,适合小型操作系统:
c
运行
#ifndef _FILE_SYSTEM_H_
#define _FILE_SYSTEM_H_
#include "types.h"
// 文件类型
typedef enum {
FILE_TYPE_FILE,
FILE_TYPE_DIRECTORY
} file_type;
// 文件属性
typedef enum {
FILE_ATTR_READONLY = 0x01,
FILE_ATTR_HIDDEN = 0x02,
FILE_ATTR_SYSTEM = 0x04,
FILE_ATTR_VOLUME_ID = 0x08,
FILE_ATTR_DIRECTORY = 0x10,
FILE_ATTR_ARCHIVE = 0x20
} file_attribute;
// FAT12文件系统引导扇区
typedef struct fat12_boot_sector {
u8 jump[3]; // 引导代码跳转指令
u8 oem_name[8]; // OEM名称
u16 bytes_per_sector; // 每扇区字节数
u8 sectors_per_cluster; // 每簇扇区数
u16 reserved_sectors; // 保留扇区数
u8 fat_count; // FAT表数量
u16 root_entries; // 根目录项数
u16 total_sectors_16; // 总扇区数(16位)
u8 media; // 媒体描述符
u16 sectors_per_fat; // 每FAT表扇区数
u16 sectors_per_track; // 每磁道扇区数
u16 heads; // 磁头数
u32 hidden_sectors; // 隐藏扇区数
u32 total_sectors_32; // 总扇区数(32位)
u8 drive_number; // 驱动器号
u8 reserved; // 保留
u8 boot_signature; // 引导签名
u16 volume_id; // 卷ID
u8 volume_label[11]; // 卷标
u8 file_system_type[8]; // 文件系统类型
} __attribute__((packed));
// FAT12目录项
typedef struct fat12_directory_entry {
u8 filename[8]; // 文件名
u8 extension[3]; // 扩展名
u8 attributes; // 属性
u8 reserved[10]; // 保留
u16 creation_time; // 创建时间
u16 creation_date; // 创建日期
u16 last_access_date; // 最后访问日期
u16 first_cluster_high; // 首簇高16位
u16 last_modification_time;// 最后修改时间
u16 last_modification_date;// 最后修改日期
u16 first_cluster_low; // 首簇低16位
u32 file_size; // 文件大小
} __attribute__((packed));
// 文件系统结构
typedef struct file_system {
fat12_boot_sector* boot_sector;
fat12_directory_entry* root_directory;
u32* fat_table;
u32 data_start_sector;
u32 total_sectors;
} file_system;
// 全局文件系统
extern file_system fs;
// 文件系统函数
void fs_init();
void* fs_open(const char* filename, u8 mode);
int fs_read(void* file_handle, void* buffer, u32 size);
int fs_write(void* file_handle, const void* buffer, u32 size);
void fs_close(void* file_handle);
u32 fs_get_size(void* file_handle);
#endif /* _FILE_SYSTEM_H_ */
文件系统实现
c
运行
#include "file_system.h"
#include "memory.h"
#include "printf.h"
#include "string.h"
// 全局文件系统
file_system fs;
// 初始化文件系统
void fs_init() {
printf("Initializing file system...\n");
// 假设文件系统已加载到内存0x20000地址
fs.boot_sector = (fat12_boot_sector*)0x20000;
// 计算FAT表和根目录位置
fs.fat_table = (u32*)(fs.boot_sector->reserved_sectors * fs.boot_sector->bytes_per_sector + (u32)fs.boot_sector);
fs.root_directory = (fat12_directory_entry*)(fs.boot_sector->reserved_sectors * fs.boot_sector->bytes_per_sector +
fs.boot_sector->fat_count * fs.boot_sector->sectors_per_fat * fs.boot_sector->bytes_per_sector);
fs.data_start_sector = fs.boot_sector->reserved_sectors +
fs.boot_sector->fat_count * fs.boot_sector->sectors_per_fat +
(fs.boot_sector->root_entries * sizeof(fat12_directory_entry) +
fs.boot_sector->bytes_per_sector - 1) / fs.boot_sector->bytes_per_sector;
fs.total_sectors = fs.boot_sector->total_sectors_32 ?
fs.boot_sector->total_sectors_32 :
fs.boot_sector->total_sectors_16;
printf("File system initialized: %d sectors, FAT at 0x%x, root at 0x%x, data start at sector %d\n",
fs.total_sectors, fs.fat_table, fs.root_directory, fs.data_start_sector);
}
// 打开文件
void* fs_open(const char* filename, u8 mode) {
printf("Opening file: %s\n", filename);
// 查找文件
fat12_directory_entry* entry = fs.root_directory;
for (u32 i = 0; i < fs.boot_sector->root_entries; i++) {
if (entry->filename[0] == 0) break; // 空目录项
// 比较文件名
int match = 1;
for (u8 j = 0; j < 8 && filename[j] && entry->filename[j]; j++) {
if (filename[j] != entry->filename[j] && filename[j] != '.') {
match = 0;
break;
}
}
// 比较扩展名
for (u8 j = 0; j < 3 && filename[8+j] && entry->extension[j]; j++) {
if (filename[8+j] != entry->extension[j] && filename[8+j] != '.') {
match = 0;
break;
}
}
if (match) {
printf("Found file: %s, size: %d bytes\n", filename, entry->file_size);
return entry;
}
entry++;
}
printf("File not found: %s\n", filename);
return NULL;
}
// 读取文件
int fs_read(void* file_handle, void* buffer, u32 size) {
if (!file_handle) return 0;
fat12_directory_entry* entry = (fat12_directory_entry*)file_handle;
u32 bytes_read = 0;
u32 cluster = (entry->first_cluster_high << 4) | entry->first_cluster_low;
u32 bytes_per_sector = fs.boot_sector->bytes_per_sector;
u32 sectors_per_cluster = fs.boot_sector->sectors_per_cluster;
u8* buffer_ptr = (u8*)buffer;
while (cluster < 0xFF8 && bytes_read < size) {
// 计算扇区位置
u32 sector = fs.data_start_sector + (cluster - 2) * sectors_per_cluster;
u32 cluster_bytes = sectors_per_cluster * bytes_per_sector;
u32 read_bytes = size - bytes_read;
if (read_bytes > cluster_bytes) {
read_bytes = cluster_bytes;
}
// 模拟从磁盘读取
for (u32 i = 0; i < read_bytes; i++) {
buffer_ptr[bytes_read + i] = 0; // 实际应从磁盘读取
}
bytes_read += read_bytes;
// 获取下一个簇
u32 next_cluster = fs.fat_table[cluster];
if (next_cluster >= 0xFF8) break;
cluster = next_cluster;
}
printf("Read %d bytes from file\n", bytes_read);
return bytes_read;
}
// 写入文件(简化实现)
int fs_write(void* file_handle, const void* buffer, u32 size) {
// 简化实现,实际应处理簇分配和FAT表更新
printf("Writing %d bytes to file\n", size);
return size;
}
// 关闭文件
void fs_close(void* file_handle) {
printf("Closing file\n");
}
// 获取文件大小
u32 fs_get_size(void* file_handle) {
if (!file_handle) return 0;
return ((fat12_directory_entry*)file_handle)->file_size;
}
系统整合与测试
最后,我们需要整合所有模块,并添加初始化代码:
c
运行
#include "types.h"
#include "printf.h"
#include "memory.h"
#include "vmm.h"
#include "process.h"
#include "file_system.h"
void kernel_main() {
// 初始化控制台
console_init();
// 显示内核启动信息
printf("Mini OS Kernel Started!\n");
printf("CPU: %s\n", get_cpu_info());
u32 memory_size = get_memory_size();
printf("Memory: %d MB\n", memory_size);
// 初始化内存管理
mem_init();
printf("Memory manager initialized, free: %d MB\n", get_free_memory() / 1024 / 1024);
// 初始化虚拟内存
vmm_init();
printf("Virtual memory initialized\n");
// 初始化文件系统
fs_init();
printf("File system initialized\n");
// 初始化进程系统
process_init();
// 启动init进程
start_process(0, "init");
// 进入主循环
while (1) {
schedule();
}
}
最终测试
现在我们可以构建并测试整个系统:
bash
make
make run
如果一切顺利,QEMU 将显示以下输出:
plaintext
Mini OS Booting...
Mini OS Kernel Started!
CPU: Intel x86
Memory: 1024 MB
Memory manager initialized, free: 1023 MB
Virtual memory initialized
File system initialized
Process system initialized
Created process: init (PID: 1)
Started process: init (PID: 1)
Switching to process: init (PID: 1)
User process running: init (PID: 1)
...
项目总结
经过三天的努力,我们成功实现了一个最小化的操作系统,包含以下核心功能:
- 引导程序:加载内核到内存
- 内核架构:初始化各个子系统
- 内存管理:内存检测、分配与释放
- 虚拟内存:简单的分页机制
- 进程调度:进程创建、切换与调度
- 文件系统:简单的 FAT12 文件系统
这个项目让我深刻理解了操作系统的底层原理,特别是引导过程、内存管理和进程调度。虽然这个操作系统非常简单,但它包含了现代操作系统的核心概念。
进一步改进方向
如果有更多时间,可以对系统进行以下改进:
- 完善文件系统:实现完整的 FAT12 功能,包括文件创建、删除和目录管理
- 增强内存管理:实现更高效的分配算法,如伙伴系统
- 改进进程调度:实现时间片轮转算法,添加进程优先级
- 添加设备驱动:实现简单的串口和硬盘驱动
- 构建用户空间:实现系统调用接口,分离内核和用户空间
结语
手写操作系统是一个充满挑战但极具价值的项目,可以对计算机系统有了更深入的理解。最小化操作系统虽然功能简单,但涵盖了操作系统的核心概念,是理解复杂操作系统的良好起点。如果你对操作系统原理感兴趣,我强烈建议尝试这个项目 将极大提升你对计算机系统的理解。