当前位置: 首页 > news >正文

如何手撸一个最小化操作系统:从 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,这是引导程序的标志。

引导程序的主要任务是:

  1. 初始化基本硬件
  2. 加载内核到内存
  3. 跳转执行内核代码

引导程序实现

首先编写引导程序的汇编代码,保存为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 0dw 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.hprintf.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!" 的字样。这说明引导程序和内核已经成功加载运行。

遇到的问题与解决方案

在第一天的开发中,我遇到了几个关键问题:

  1. 引导程序无法加载内核:最初的引导程序在读取磁盘时没有错误处理,导致内核加载失败。后来添加了错误处理代码,并在 BIOS 中断调用后检查错误标志。

  2. 地址空间问题:内核被加载到 0x10000 地址,但最初的跳转指令使用了错误的段地址,导致无法正确执行。后来将段地址设置为 0x1000,偏移地址 0x0000,确保正确跳转。

  3. 控制台显示乱码:由于没有正确设置字符属性,显示的字符颜色混乱。后来设置了黑底白字的属性(0x07),解决了显示问题。

第二天:内核架构与内存管理

内核架构设计

经过第一天的努力,我们已经实现了基本的引导和内核启动。今天的主要任务是完善内核架构,实现内存管理系统。一个最小化的内存管理系统需要包含:

  1. 内存检测
  2. 内存分区
  3. 内存分配与释放
  4. 简单的虚拟内存机制

内存检测实现

首先需要检测系统中的物理内存大小,这可以通过 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.hkernel/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();}
}

当天总结

第二天的开发主要集中在内核架构和内存管理系统。我遇到的主要挑战是:

  1. 内存检测的兼容性:不同计算机的 BIOS 对内存检测中断的支持不同,需要处理各种边界情况。
  2. 内存分配算法:最初实现的简单首次适应算法在大块内存分配时效率较低,后来添加了分割和合并功能。
  3. 虚拟内存的初始化:分页机制的初始化需要正确设置页目录和页表,并且要注意特权级的问题。

通过今天的努力,我们已经实现了一个基本的内存管理系统,这为明天实现进程调度和文件系统打下了基础。现在系统可以正确检测内存、分配和释放内存,并实现了简单的虚拟内存映射。

第三天:进程调度与文件系统

进程调度系统设计

经过前两天的开发,我们已经实现了引导程序和基本的内存管理。今天的主要任务是实现进程调度系统和简单的文件系统,这是操作系统的核心功能。

进程调度系统需要包含:

  1. 进程控制块 (PCB) 设计
  2. 进程状态管理
  3. 进程创建与销毁
  4. 进程调度算法
  5. 上下文切换机制

首先定义进程控制块结构,保存为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)
...

项目总结

经过三天的努力,我们成功实现了一个最小化的操作系统,包含以下核心功能:

  1. 引导程序:加载内核到内存
  2. 内核架构:初始化各个子系统
  3. 内存管理:内存检测、分配与释放
  4. 虚拟内存:简单的分页机制
  5. 进程调度:进程创建、切换与调度
  6. 文件系统:简单的 FAT12 文件系统

这个项目让我深刻理解了操作系统的底层原理,特别是引导过程、内存管理和进程调度。虽然这个操作系统非常简单,但它包含了现代操作系统的核心概念。

进一步改进方向

如果有更多时间,可以对系统进行以下改进:

  1. 完善文件系统:实现完整的 FAT12 功能,包括文件创建、删除和目录管理
  2. 增强内存管理:实现更高效的分配算法,如伙伴系统
  3. 改进进程调度:实现时间片轮转算法,添加进程优先级
  4. 添加设备驱动:实现简单的串口和硬盘驱动
  5. 构建用户空间:实现系统调用接口,分离内核和用户空间

结语

手写操作系统是一个充满挑战但极具价值的项目,可以对计算机系统有了更深入的理解。最小化操作系统虽然功能简单,但涵盖了操作系统的核心概念,是理解复杂操作系统的良好起点。如果你对操作系统原理感兴趣,我强烈建议尝试这个项目  将极大提升你对计算机系统的理解。

相关文章:

  • win操作系统安装C++语言开发环境之一, vscode +MinGW ,流程
  • 【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
  • Visual Studio Code 扩展
  • 图表类系列各种样式PPT模版分享
  • 使用 C# 将 Word、Excel、PDF 和 PPT文档转换为 Markdown 格式
  • 如何使用 Ansible 在 Ubuntu 24.04 上安装和设置 LNMP
  • 莫兰迪高级灰总结计划简约商务通用PPT模版
  • VmWare Ubuntu 16.04 搭建DPDK 19.08.2
  • 【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
  • 从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
  • React 第五十八节 Router中StaticRouterProvider的使用详解及案例
  • React第五十七节 Router中RouterProvider使用详解及注意事项
  • React核心概念:State是什么?如何用useState管理组件自己的数据?
  • 大模型智能体核心技术:CoT与ReAct深度解析
  • 操作系统的概念,功能和目标
  • React入门第一步:如何用Vite创建你的第一个React项目?
  • 深入理解 React 样式方案
  • 机器学习监督学习实战六:五种算法对新闻组英文文档进行文本分类(20类),词频统计和TF-IDF 转换特征提取方法理论和对比解析
  • RAG 文档解析难点1:多栏布局的 PDF 如何解析
  • 关于我对各开发语言的看法与接下来的文章内容
  • 网站开发学费/泰州seo平台
  • 网站的虚拟人怎么做的/品牌策划方案怎么写
  • 做网站内容/网站seo哪家做的好
  • 做网站可以赚钱吗知乎/uc信息流广告投放
  • 南京市建设工程交易中心网站/免费换友情链接
  • 青海省建设监理协会网站/今日国际新闻最新消息大事