3步实现操作系统用户程序加载:从ELF解析到进程创建

3步实现操作系统用户程序加载:从ELF解析到进程创建

【免费下载链接】How-to-Make-a-Computer-Operating-System How to Make a Computer Operating System in C++ 【免费下载链接】How-to-Make-a-Computer-Operating-System 项目地址: https://gitcode.com/gh_mirrors/ho/How-to-Make-a-Computer-Operating-System

你还在为操作系统如何加载用户程序而困惑吗?本文基于How-to-Make-a-Computer-Operating-System项目,带你从0到1掌握用户程序加载全流程。读完你将学会:解析ELF文件结构、创建用户进程、实现内存映射加载,最终运行自己的helloworld程序。

ELF文件解析:用户程序的"身份证"

操作系统加载用户程序的第一步是解析可执行文件格式。该项目采用ELF(Executable and Linkable Format,可执行与可链接格式)作为用户程序标准格式,相关实现位于src/kernel/core/elf_loader.h

ELF文件头部包含关键信息:

  • 魔数(0x7f454c46):快速识别ELF格式
  • 程序入口点:用户程序开始执行的内存地址
  • 程序头表:描述如何将文件内容加载到内存

ELF文件结构

核心解析代码在src/kernel/core/elf_loader.ccload_elf函数中实现:

u32 load_elf(char *file, process_st *proc) {
    Elf32_Ehdr *hdr = (Elf32_Ehdr *)file;
    Elf32_Phdr *p_entry = (Elf32_Phdr *)(file + hdr->e_phoff);
    
    for (int pe = 0; pe < hdr->e_phnum; pe++, p_entry++) {
        if (p_entry->p_type == PT_LOAD) {  // 加载可执行段
            u32 v_begin = p_entry->p_vaddr;
            u32 v_end = p_entry->p_vaddr + p_entry->p_memsz;
            
            // 验证内存区域是否在用户空间
            if (v_begin < USER_OFFSET || v_end > USER_STACK) {
                io.print("Invalid memory range: %x-%x\n", v_begin, v_end);
                return 0;
            }
            
            // 复制文件内容到内存
            memcpy((char *)v_begin, file + p_entry->p_offset, p_entry->p_filesz);
            // 初始化BSS段为0
            if (p_entry->p_memsz > p_entry->p_filesz) {
                memset((char *)v_begin + p_entry->p_filesz, 0, 
                       p_entry->p_memsz - p_entry->p_filesz);
            }
        }
    }
    return hdr->e_entry;  // 返回程序入口地址
}

进程创建:为用户程序分配"独立王国"

解析完ELF文件后,需要创建独立的进程环境。进程管理实现位于src/kernel/core/process.cc,关键步骤包括:

  1. 进程结构体初始化:分配进程控制块(PCB),设置进程ID、状态和资源
  2. 地址空间隔离:通过页表实现用户程序内存空间与内核隔离
  3. 上下文切换:保存/恢复CPU寄存器状态,实现进程切换

进程创建流程:

int execv(char* file, int argc, char** argv) {
    // 1. 读取ELF文件到内存
    File* fp = fsm.path(file);
    char* map_elf = (char*)kmalloc(fp->getSize());
    fp->read(0, (u8*)map_elf, fp->getSize());
    
    // 2. 创建新进程
    Process* proc = new Process(argv[0] ? argv[0] : __default_proc_name);
    
    // 3. 加载程序并初始化执行环境
    proc->create(map_elf, argc, argv);
    
    kfree(map_elf);
    return proc->getPid();
}

内存映射:构建用户程序的"运行舞台"

用户程序需要在独立的内存空间运行,项目通过分页机制实现。内存管理代码位于src/kernel/arch/x86/vmm.cc,采用二级页表结构:

页目录项结构

  • 页目录(Page Directory):每个进程一个,指向页表
  • 页表(Page Table):映射4MB内存空间,每页4KB
  • 物理内存:通过alloc_frame函数分配物理页框

用户程序内存布局严格限制在USER_OFFSETUSER_STACK之间,确保内核空间安全:

// 用户空间内存范围定义
#define USER_OFFSET 0x00400000  // 用户程序起始地址
#define USER_STACK  0x08000000  // 用户栈顶地址

实战:运行第一个用户程序

项目提供了完整的用户程序示例src/userland/helloworld/main.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("hello world ! \n");
    return 0;
}

编译配置在src/userland/helloworld/Makefile中定义,通过SDK工具链编译为ELF格式:

SDKDIR=../../sdk
TARGET = hello
OBJS=main.o
include $(SDKDIR)/build.mak

执行流程:

  1. 内核通过execv系统调用加载hello程序
  2. load_elf解析ELF文件并加载到内存
  3. 创建进程并分配资源
  4. 跳转到程序入口点执行

总结与进阶

本文介绍的用户程序加载流程包含三大核心步骤:

  1. ELF文件解析:验证格式并加载程序段到内存
  2. 进程创建:初始化PCB并设置执行环境
  3. 内存映射:通过分页机制构建独立地址空间

进阶方向:

完整实现请参考项目文档:README.md,更多技术细节可查阅各章节指南:Chapter-1/README.mdChapter-2/README.md

通过本文学习,你已掌握操作系统加载用户程序的核心原理。下一篇将深入讲解进程调度与内存管理优化,敬请关注!

【免费下载链接】How-to-Make-a-Computer-Operating-System How to Make a Computer Operating System in C++ 【免费下载链接】How-to-Make-a-Computer-Operating-System 项目地址: https://gitcode.com/gh_mirrors/ho/How-to-Make-a-Computer-Operating-System

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值