手写操作系统 15.7 execve实现

李述铜老师的手写操作系统,复习笔记。

我们看看execve( const char *name, char * const *argv, char * const *env ) 函数,name函数是需要运行函数的文件名,第二个参数是运行文件需要的参数,env是环境变量,储存着地址。

execve函数的作用就是覆盖当前程序的代码,eip重新指向新函数的地址入口。

和fork函数一样,这个函数都需要操作系统在内核状态执行。我们看系统调用的sys_fork()函数。

1、创建新的页表

    //创建新的页表
    uint32_t old_page_dir=current_task->tss.cr3;
    uint32_t new_page_dir=memory_create_uvm();
    if(new_page_dir==0){
        goto execve_fail;
    }

这里memory_create_uvm,就是用地址分配器新建一个一级页表,然后把0--0x80000000地址段的一级页表指向操作系统的二级页表,这块代码是共享的。

uint32_t memory_create_uvm (void){
    //为进程创建一级页表,返回新的一级页表地址,出错返回0
    pde_t * pde_paddr =(pde_t *)addr_alloc_page(&paddr_alloc,1);
    if(!pde_paddr){
        //创建页表失败
        return 0;
    }

    //清空数据
    kernel_memset((void *)pde_paddr,0,MEM_PAGE_SIZE);

    //将操作系统的数据搬过来
    // 这个是取操作系统最大内存的页表项
    uint32_t user_pde_start=pde_index(MEMORY_TASK_BASE);
    for(int i=0;i<user_pde_start;i++){
        pde_paddr[i].v=kernel_page_dir[i].v;
    }
    return (uint32_t )pde_paddr;

};

2、加载elf文件

    //加载elf文件
    uint32_t entry=load_elf_file(current_task,name,new_page_dir);
    if(entry==0){
        goto execve_fail;
    }

模拟的就是操作系统加载文件,因为这里还没有实现文件系统,就利用编译器,放在操作系统的内存里,然后再加载。

这个函数就是读取二进制elf头,返回的是函数的起始地址。

static uint32_t load_elf_file (task_t * task, const char * name, uint32_t page_dir){
    //将elf文件加载到内存中,返回入口地址
    Elf32_Ehdr elf_hdr;
    Elf32_Phdr elf_phdr;

    // 以只读方式打开
    int file = sys_open(name, 0);   // todo: flags暂时用0替代
    if (file < 0) {
        log_printf("open file failed.%s", name);
        goto load_failed;
    }

    // 先读取文件头
    int cnt = sys_read(file, (char *)&elf_hdr, sizeof(Elf32_Ehdr));
    if (cnt < sizeof(Elf32_Ehdr)) {
        log_printf("elf hdr too small. size=%d", cnt);
        goto load_failed;
    }

    // 做点必要性的检查。当然可以再做其它检查
    if ((elf_hdr.e_ident[0] != ELF_MAGIC) || (elf_hdr.e_ident[1] != 'E')
        || (elf_hdr.e_ident[2] != 'L') || (elf_hdr.e_ident[3] != 'F')) {
        log_printf("check elf indent failed.");
        goto load_failed;
    }

    // 必须是可执行文件和针对386处理器的类型,且有入口
    if ((elf_hdr.e_type != ET_EXEC) || (elf_hdr.e_machine != ET_386) || (elf_hdr.e_entry == 0)) {
        log_printf("check elf type or entry failed.");
        goto load_failed;
    }

    // 必须有程序头部
    if ((elf_hdr.e_phentsize == 0) || (elf_hdr.e_phoff == 0)) {
        log_printf("none programe header");
        goto load_failed;
    }

    // 然后从中加载程序头,将内容拷贝到相应的位置
    uint32_t e_phoff = elf_hdr.e_phoff;
    for (int i = 0; i < elf_hdr.e_phnum; i++, e_phoff += elf_hdr.e_phentsize) {
        if (sys_lseek(file, e_phoff, 0) < 0) {
            log_printf("read file failed");
            goto load_failed;
        }

        // 读取程序头后解析,这里不用读取到新进程的页表中,因为只是临时使用下
        cnt = sys_read(file, (char *)&elf_phdr, sizeof(Elf32_Phdr));
        if (cnt < sizeof(Elf32_Phdr)) {
            log_printf("read file failed");
            goto load_failed;
        }

        // 简单做一些检查,如有必要,可自行加更多
        // 主要判断是否是可加载的类型,并且要求加载的地址必须是用户空间
        if ((elf_phdr.p_type != PT_LOAD) || (elf_phdr.p_vaddr < MEMORY_TASK_BASE)) {
           continue;
        }

        // 加载当前程序头
        int err = load_phdr(file, &elf_phdr, page_dir);
        if (err < 0) {
            log_printf("load program hdr failed");
            goto load_failed;
        }

        // 简单起见,不检查了,以最后的地址为bss的地址
        task->heap_start = elf_phdr.p_vaddr + elf_phdr.p_memsz;
        task->heap_end = task->heap_start;

   }

    sys_close(file);
    return elf_hdr.e_entry;

load_failed:
    if (file >= 0) {
        sys_close(file);
    }

    return 0;
}

3、为进程创建栈空间

    //为进程创建新的栈空间
    uint32_t stack_top = MEM_TASK_STACK_TOP - MEM_TASK_ARG_SIZE;    // 预留一部分参数空间
    
    uint32_t err=memory_alloc_for_page_dir(new_page_dir,MEM_TASK_STACK_TOP - MEM_TASK_STACK_SIZE,MEM_TASK_STACK_SIZE,PTE_P | PTE_U | PTE_W);
    if(err==0){
        goto execve_fail;
    }

进程需要有自己的栈空间的

4、复制程序运行的参数

    // 复制参数,写入到栈顶的后边
    int argc = strings_count(argv);
    int err1 = copy_args((char *)stack_top, new_page_dir, argc, argv);
    if (err1 < 0) {
        goto execve_fail;
    }

这里的有一个难点,就是两个进程页表不一样,不能通过虚拟地址直接复制,需要通过页表,获取当前的物理地址。

static int copy_args (char * to, uint32_t page_dir, int argc, char **argv) {
    // 在stack_top中依次写入argc, argv指针,参数字符串
    task_args_t task_args;
    task_args.argc = argc;
    task_args.argv = (char **)(to + sizeof(task_args_t));

    // 复制各项参数, 跳过task_args和参数表
    // 各argv参数写入的内存空间
    char * dest_arg = to + sizeof(task_args_t) + sizeof(char *) * (argc);   // 留出结束符
    
    // argv表
    char ** dest_argv_tb = (char **)memory_get_paddr(page_dir, (uint32_t)(to + sizeof(task_args_t)));
    ASSERT(dest_argv_tb != 0);

    for (int i = 0; i < argc; i++) {
        char * from = argv[i];

        // 不能用kernel_strcpy,因为to和argv不在一个页表里
        int len = kernel_strlen(from) + 1;   // 包含结束符
        int err = memory_copy_uvm_data((uint32_t)dest_arg, page_dir, (uint32_t)from, len);
        ASSERT(err >= 0);

        // 关联ar
        dest_argv_tb[i] = dest_arg;

        // 记录下位置后,复制的位置前移
        dest_arg += len;
    }

     // 写入task_args
    return memory_copy_uvm_data((uint32_t)to, page_dir, (uint32_t)&task_args, sizeof(task_args_t));
}

5、修改新进程的寄存器

    //修改新进程的寄存器
    syscall_frame_t * frame = (syscall_frame_t *)(current_task->tss.esp0 - sizeof(syscall_frame_t));
    frame->eip = entry;
    frame->eax = frame->ebx = frame->ecx = frame->edx = 0;
    frame->esi = frame->edi = frame->ebp = 0;
    frame->eflags = EFLAGS_DEFAULT| EFLAGS_IF;  // 段寄存器无需修改
    // 内核栈不用设置,保持不变,后面调用memory_destroy_uvm并不会销毁内核栈的映射。
    // 但用户栈需要更改, 同样要加上调用门的参数压栈空间
    frame->esp = stack_top - sizeof(uint32_t)*SYSCALL_PARAM_COUNT;

6、设置新的cr3 页表

    //设置新的cr3 页表
    current_task->tss.cr3=new_page_dir;
    mmu_set_page_dir(new_page_dir);

    //释放旧页表
    memory_destroy_uvm(old_page_dir);
    return 0;

这是释放用户的页表

void memory_destroy_uvm (uint32_t page_dir){
    //释放用户的页表
    
    uint32_t user_pde_index=pde_index(MEMORY_TASK_BASE);
    pde_t *user_pde=(pde_t*)page_dir+user_pde_index;

    //0x80000000之后的,就是进程自己的页表
    for(int i=user_pde_index;i<PDE_CNT;i++,user_pde++){
        if(!user_pde->present){
            continue;
        }

        pte_t *user_pte=(pte_t*)pde_paddr(user_pde);
        for(int j=0;j<PTE_CNT;j++){
            if(!user_pde->present){
                continue;
            }

            addr_free_page(&paddr_alloc, pte_paddr(user_pte), 1);
        }
        addr_free_page(&paddr_alloc, (uint32_t)pde_paddr(user_pde), 1);
    }
    // 页目录表
    addr_free_page(&paddr_alloc, page_dir, 1);
};

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值