ucore lab5

本文介绍ucoreOS中用户态进程的创建、管理和系统调用机制,涵盖进程状态转换、ELF文件加载、Copy-on-Write机制实现等内容。

ucore lab5

实验目的:

  • 了解第一个用户进程创建过程
  • 了解系统调用框架的实现机制
  • 了解ucore如何实现系统调用sys_fork/sys_exec/sys_exit/sys_wait来进行进程管理

实验内容:

实验4完成了内核线程,但到目前为止,所有的运行都在内核态执行。实验5将创建用户进程,让用户进程在用户态执行,且在需要ucore支持时,可通过系统调用来让ucore提供服务。为此需要构造出第一个用户进程,并通过系统调用sys_fork/sys_exec/sys_exit/sys_wait来支持运行不同的应用程序,完成对用户进程的执行过程的基本管理。相关原理介绍可看附录B。

练习

练习0:填写已有实验

本实验依赖实验1/2/3/4。请把你做的实验1/2/3/4的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”的注释相应部分。注意:为了能够正确执行lab5的测试应用程序,可能需对已完成的实验1/2/3/4的代码进行进一步改进。

这里首先使用meld来对比目录和文件内部的内容,修改在实验1/2/3/4的代码填入实验五中。

  • kdebug.c
  • trap.c
  • default_pmm.c
  • pmm.c
  • swap_fifo.c
  • vmm.c
  • proc.c

有一些代码需要进行进一步改进:

改进的alloc_proc函数:
static struct proc_struct *
alloc_proc(void) {
   
   
 struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
   
   
        proc->state = PROC_UNINIT;
        proc->pid = -1;
        proc->runs = 0;
        proc->kstack = 0;
        proc->need_resched = 0;
        proc->parent = NULL;
        proc->mm = NULL;
        memset(&(proc->context), 0, sizeof(struct context));
        proc->tf = NULL;
        proc->cr3 = boot_cr3;
        proc->flags = 0;
        memset(proc->name, 0, PROC_NAME_LEN);
        proc->wait_state = 0; //PCB新增的条目,初始化进程等待状态
        proc->cptr = proc->optr = proc->yptr = NULL;//设置指针
    }
    return proc;
}

改进的内容为:

这两行代码主要是初始化进程等待状态、和进程的相关指针,例如父进程、子进程、同胞等等。其中的wait_state是进程控制块中新增的条目。避免之后由于未定义或未初始化导致管理用户进程时出现错误。

proc->wait_state = 0; //初始化进程等待状态
proc->cptr = proc->optr = proc->yptr = NULL; //指针初始化
改进的do_fork函数:
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
   
   
    int ret = -E_NO_FREE_PROC;
    struct proc_struct *proc;
    if (nr_process >= MAX_PROCESS) {
   
   
        goto fork_out;
    }
    ret = -E_NO_MEM;
    if ((proc = alloc_proc()) == NULL) {
   
   
        goto fork_out;
    }
    proc->parent = current;
    assert(current->wait_state == 0); //确保进程在等待
    if (setup_kstack(proc) != 0) {
   
   
        goto bad_fork_cleanup_proc;
    }
    if (copy_mm(clone_flags, proc) != 0) {
   
   
        goto bad_fork_cleanup_kstack;
    }
    copy_thread(proc, stack, tf);
    bool intr_flag;
    local_intr_save(intr_flag);
    {
   
   
        proc->pid = get_pid();
        hash_proc(proc);
        set_links(proc); //设置进程链接
    }
    local_intr_restore(intr_flag);
    wakeup_proc(proc);
    ret = proc->pid;
fork_out:
    return ret;
bad_fork_cleanup_kstack:
    put_kstack(proc);
bad_fork_cleanup_proc:
    kfree(proc);
    goto fork_out;
}

改进的内容为:

assert(current->wait_state == 0); //确保进程在等待
set_links(proc); //设置进程链接

第一行代码需要确保当前进程正在等待,我们在alloc_proc中初始化wait_state为0。

改进trap_dispatch函数:

设置每100次时间中断后,当前正在执行的进程准备被调度。同时,注释掉原来的"100ticks"输出

ticks ++;
        if (ticks % TICK_NUM == 0) {
   
   
            assert(current != NULL);
            current->need_resched = 1;
            //原代码中的print_ticks();改成上面两句
        }
        break;

这里主要是将时间片设置为需要调度,说明当前进程的时间片已经用完了。

改进 idt_init 函数:
void idt_init(void) {
   
   
    extern uintptr_t __vectors[];
    int i;
    for (i = 0;i<sizeof(idt)/sizeof(struct gatedesc);i ++) {
   
   
        SETGATE(idt[i],0,GD_KTEXT,__vectors[i], DPL_KERNEL);
    }
    SETGATE(idt[T_SYSCALL], 1, GD_KTEXT,    __vectors[T_SYSCALL], DPL_USER);
    lidt(&idt_pd);
}

改进的内容为:

SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);////这里主要是设置相应的中断门

设置一个特定中断号的中断门,专门用于用户进程访问系统调用,即设置中断T_SYSCALL的触发特权级为DPL_USER。

练习1: 加载应用程序并执行(需要编码)

do_execv函数调用load_icode(位于kern/process/proc.c中)来加载并解析一个处于内存中的ELF执行文件格式的应用程序,建立相应的用户内存空间来放置应用程序的代码段、数据段等,且要设置好proc_struct结构中的成员变量trapframe中的内容,确保在执行此进程后,能够从应用程序设定的起始执行地址开始执行。需设置正确的trapframe内容。

请在实验报告中简要说明你的设计实现过程。

请在实验报告中描述当创建一个用户态进程并加载了应用程序后,CPU是如何让这个应用程序最终在用户态执行起来的。即这个用户态进程被ucore选择占用CPU执行(RUNNING态)到具体执行应用程序第一条指令的整个经过。

首先,我们看到do_execv函数:

do_execve函数主要做的工作就是先回收自身所占用户空间,然后调用load_icode,用新的程序覆盖内存空间,形成一个执行新程序的新进程。

//主要目的在于清理原来进程的内存空间,为新进程执行准备好空间和资源
int do_execve(const char *name, size_t len, unsigned char *binary, size_t size) 
{
   
   
    struct mm_struct *mm = current->mm;
    if (!user_mem_check(mm, (uintptr_t)name, len, 0)) {
   
   
        return -E_INVAL;
    }
    if (len > PROC_NAME_LEN) {
   
   
        len = PROC_NAME_LEN;
    }

    char local_name[PROC_NAME_LEN + 1];
    memset(local_name, 0, sizeof(local_name));
    memcpy(local_name, name, len);
//如果mm不为NULL,则不执行该过程
    if (mm != NULL) 
    {
   
   
        //将cr3页表基址指向boot_cr3,即内核页表
        lcr3(boot_cr3);//转入内核态
        if (mm_count_dec(mm) == 0) 
        {
   
     
            //下面三步实现将进程的内存管理区域清空
            exit_mmap(mm);//清空内存管理部分和对应页表
            put_pgdir(mm);//清空页表
            mm_destroy(mm);//清空内存
        }
        current->mm = NULL;//最后让它当前的页表指向空,方便放入自己的东西
    }
    int ret;
    //填入新的内容,load_icode会将执行程序加载,建立新的内存映射关系,从而完成新的执行
    if ((ret = load_icode(binary, size)) != 0) {
   
   
        goto execve_exit;
    }
    //给进程新的名字
    set_proc_name(current, local_name);
    return 0;

execve_exit:
    do_exit(ret);
    panic("already exit: %e.\n", ret);
}
接下来看到load_icode函数:

下面是loab_icode函数的主要实现思路:

  1. 为用户进程创建新的mm结构
  2. 创建页目录表
  3. 校验ELF文件的魔数是否正确
  4. 创建虚拟内存空间,即往mm结构体添加vma结构
  5. 分配内存,并拷贝ELF文件的各个program section到新申请的内存上
  6. 为BSS section分配内存,并初始化为全0
  7. 分配用户栈内存空间
  8. 设置当前用户进程的mm结构、页目录表的地址及加载页目录表地址到cr3寄存器
  9. 设置当前用户进程的tf结构
static int
load_icode(unsigned char *binary, size_t size) {
   
   
    if (current->mm != NULL) {
   
    //当前进程的内存为空
        panic("load_icode: current->mm must be empty.\n");
    }

    int ret = -E_NO_MEM; //记录错误信息:未分配内存
    struct mm_struct *mm;
    //(1) create a new mm for current process
    if ((mm = mm_create()) == NULL) {
   
    //分配内存
        goto bad_mm; <
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值