如何手撸一个最小化操作系统:从 0 到 1 掌握汇编-文件管理-内存页表-文件系统-上下文切换算法 MIT 经典教程 结合豆包ai
前言:为什么要手写操作系统
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 startstart:; 初始化寄存器mov ax, 0x07c0 ; 设置数据段基址mov ds, axmov es, axmov ss, axmov sp, 0x7c00 ; 设置栈顶; 清屏mov ax, 0x0600mov bx, 0x0700mov cx, 0mov dx, 0x184fint 0x10 ; 调用BIOS中断清屏; 显示启动信息mov ax, 0x0e00mov bx, 0x0fmov cx, boot_msg
print_msg:mov al, [cx]cmp al, 0je load_kernelint 0x10inc cxjmp print_msgload_kernel:; 加载内核到0x10000地址mov ax, 0x0201 ; 读取1个扇区mov bx, 0x1000 ; 加载到0x1000:0mov cx, 0x0002 ; 从第2个扇区开始读取mov dx, 0x0000 ; 驱动器0int 0x13 ; 调用磁盘读取中断; 检查读取是否成功jc error; 跳转到内核入口mov ax, 0x1000mov ds, axjmp 0x1000:0x0000 ; 跳转到内核地址error:; 错误处理mov cx, error_msgjmp print_msgboot_msg:.ascii "Mini OS Booting...\r\n".byte 0error_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 MakefileOBJS_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 -Ikernelkernel/printf.o: kernel/printf.c kernel/printf.h$(CC) -c -o $@ $< -m32 -fno-builtin -fno-stack-protector -nostdlib -Ikernelkernel/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.bindd if=/dev/zero of=mini-os.img bs=512 count=2880dd if=boot/boot.o of=mini-os.img bs=512 conv=notruncdd if=kernel.bin of=mini-os.img bs=512 seek=2 conv=notrunc# 运行在QEMU
run: mini-os.imgqemu-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;// 基本内存为640KBmemory_size = 640;// 加上扩展内存,单位为KBmemory_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; // 进程IDchar 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) {// 分配PCBprocess_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;}// 初始化PCBpcb->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; // 卷IDu8 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 功能,包括文件创建、删除和目录管理
- 增强内存管理:实现更高效的分配算法,如伙伴系统
- 改进进程调度:实现时间片轮转算法,添加进程优先级
- 添加设备驱动:实现简单的串口和硬盘驱动
- 构建用户空间:实现系统调用接口,分离内核和用户空间
结语
手写操作系统是一个充满挑战但极具价值的项目,可以对计算机系统有了更深入的理解。最小化操作系统虽然功能简单,但涵盖了操作系统的核心概念,是理解复杂操作系统的良好起点。如果你对操作系统原理感兴趣,我强烈建议尝试这个项目 将极大提升你对计算机系统的理解。