如何手撸一个最小化操作系统:从 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 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 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 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!" 的字样。这说明引导程序和内核已经成功加载运行。

遇到的问题与解决方案

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

  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;
    
    // 基本内存为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.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;                  // 进程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)
...

项目总结

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

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

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

进一步改进方向

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

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

结语

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值