ucore Lab5 用户进程管理

练习0:填写已有实验

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

改进的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, sizeof(proc->name));
        //以下是增加条目
        proc->wait_state = 0;
        proc->cptr = proc->yptr = proc->optr = NULL;
    }
    return proc;
}

idt_init 在初始化IDT的时候,设置系统调用对应的中断描述符,使其能够在用户态下被调用,并且设置为trap类型。(事实上这个部分已经在LAB1的实验中顺手被完成了)
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);
    }
    /**
     * 【注意】除了系统调用中断(T_SYSCALL)使用陷阱门描述符且权限为用户态权限以外
     * ,其它中断均使用特权级(DPL)为0的中断门描述符,权限为内核态权限;而ucore的
     * 应用程序处于特权级3,需要采用`int 0x80`指令操作(这种方式称为软中断,软件
     * 中断,Tra中断,在lab5会碰到)来发出系统调用请求,并要能实现从特权级3到特权
     * 级0的转换,所以系统调用中断(T_SYSCALL)所对应的中断门描述符中的特权级(DPL)
     * 需要设置为3。【lab1给出的实验说明】
     */
    SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
    // this is different than  answer
    lidt(&idt_pd);
}
trap_dispatch(struct trapframe *tf) 在时钟中断的处理部分,每过TICK_NUM个中断,就将当前的进程设置为可以被重新调度的,这样使得当前的线程可以被换出,从而实现多个线程的并发执行;
        ticks++;
        if (ticks % TICK_NUM == 0)
        {
            assert(current != NULL);
            current->need_resched = 1;
        }
在do_fork函数中,使用set_links函数来完成将fork的线程添加到线程链表中的过程,值得注意的是,该函数中就包括了对进程总数加1这一操作,因此需要将原先的这个操作给删除掉;

下面只给出部分代码

int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf)
{
 ...
	parent = current;//设置parent
    assert(current->wait_state == 0);//检测是否为零

    if (setup_kstack(proc) != 0)
    { //    2. call setup_kstack to allocate a kernel stack for child process
        goto bad_fork_cleanup_proc;
    }
    if (copy_mm(clone_flags, proc) != 0)
    { //    3. call copy_mm to dup OR share mm according clone_flag
        goto bad_fork_cleanup_kstack;
    }
    copy_thread(proc, stack, tf); //    4. call copy_thread to setup tf & context in proc_struct

    bool intr_flag;
    local_intr_save(intr_flag);
    {
        proc->pid = get_pid();
        hash_proc(proc);
        set_links(proc);//已经对nr_process增加了
        // list_add(&proc_list, &proc->list_link); //    5. insert proc_struct into hash_list && proc_list
        // nr_process++;
    }
...

简要描述一下进程创建过程:

1 程序进入kern_init开始执行

2 proc_init(); 在调用该函数时创建了俩个内核线程

void proc_init(void)
//为idleproc分配内存
    if ((idleproc = alloc_proc()) == NULL)
    {
        panic("cannot alloc idleproc.\n");
    }
//设置idleproc内核线程信息
...
    current = idleproc; //设置当前为idleproc
...
    //又创建了另一个内核线程,其中(init_main 是该线程的执行流)
    int pid = kernel_thread(init_main, NULL, 0);
...
    set_proc_name(initproc, "init");


3 查看 init_main 发现init线程只是创建了个子线程,然后就进入循环调度

init_main(void *arg)
...
    int pid = kernel_thread(user_main, NULL, 0);
    if (pid <= 0)
    {
        panic("create user_main failed.\n");
    }

    while (do_wait(0, NULL) == 0)
    {
        schedule();
    }
...

4 查看user_main发现调用了KERNEL_EXECVE宏

static int
user_main(void *arg)
{
#ifdef TEST
    KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
    KERNEL_EXECVE(exit);
#endif
    panic("user_main execve failed.\n");
}

5 查看该宏最终调用kernel_execve创建进程

#define __KERNEL_EXECVE(name, binary, size) ({           \
    cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \
            current->pid, name);                         \
    kernel_execve(name, binary, (size_t)(size));         \
})
#define KERNEL_EXECVE(x) ({                                    \
    extern unsigned char _binary_obj___user_##x##_out_start[], \
        _binary_obj___user_##x##_out_size[];                   \
    __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start,    \
                    _binary_obj___user_##x##_out_size);        \
})

6 而kernel_execve执行的是系统调用

kernel_execve(const char *name, unsigned char *binary, size_t size)
{
    int ret, len = strlen(name);
    asm volatile(
        "int %1;"
        : "=a"(ret)//执行SYS_exec系统调用
        : "i"(T_SYSCALL), "0"(SYS_exec), "d"(name), "c"(len), "b"(binary), "D"(size)
        : "memory");
    return ret;
}	

7 系统调用在syscall.c中给出,通过设置了个函数指针数组进行绑定系统调用与对应函数

static int (*syscalls[])(uint32_t arg[]) = {
    [SYS_exit]              sys_exit,
    [SYS_fork]              sys_fork,
    [SYS_wait]              sys_wait,
    [SYS_exec]              sys_exec,
    [SYS_yield]             sys_yield,
    [SYS_kill]              sys_kill,
    [SYS_getpid]            sys_getpid,
    [SYS_putc]              sys_putc,
    [SYS_pgdir]             sys_pgdir,
};
void
syscall(void) {
    struct trapframe *tf = current->tf;
    uint32_t arg[5];
    int num = tf->tf_regs.reg_eax;
    if (num >= 0 && num < NUM_SYSCALLS) {
        if (syscalls[num] != NULL) {
            arg[0] = tf->tf_regs.reg_edx;
            arg[1] = tf->tf_regs.reg_ecx;
            arg[2] = tf->tf_regs.reg_ebx;
            arg[3] = tf->tf_regs.reg_edi;
            arg[4] = tf->tf_regs.reg_esi;
            tf->tf_regs.reg_eax = syscalls[num](arg);
            return ;
        }
    }
    print_trapframe(tf);
    panic("undefined syscall %d, pid = %d, name = %s.\n",
            num, current->pid, current->name);
}

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

load_icode

由于最终是在用户态下运行的,所以需要将段寄存器初始化为用户态的代码段、数据段、堆栈段;

esp应当指向先前的步骤中创建的用户栈的栈顶;

eip应当指向ELF可执行文件加载到内存之后的入口处;

eflags中应当初始化为中断使能,注意eflags的第1位是恒为1的;

设置ret为0,表示正常返回;

  tf->tf_cs = USER_CS;
  tf->tf_ds = tf->tf_es = tf->tf_ss =  USER_DS;
  tf->tf_esp = USTACKTOP;
  tf->tf_eip = elf->e_entry;
  tf->tf_eflags = FL_IF |0x00000002;//参考AmadeusChan 不加 |0x00000002 make grade有点问题
  ret = 0;

练习2: 父进程复制自己的内存空间给子进程(需要编码)

int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share)
{
    assert(start % PGSIZE == 0 && end % PGSIZE == 0);
    assert(USER_ACCESS(start, end));
    // copy content by page unit.
    do
    {
        // call get_pte to find process A's pte according to the addr start
        pte_t *ptep = get_pte(from, start, 0), *nptep;
        if (ptep == NULL)
        {
            start = ROUNDDOWN(start + PTSIZE, PTSIZE);
            continue;
        }
        // call get_pte to find process B's pte according to the addr start. If pte is NULL, just alloc a PT
        if (*ptep & PTE_P)
        {
            if ((nptep = get_pte(to, start, 1)) == NULL)
            {
                return -E_NO_MEM;
            }
            uint32_t perm = (*ptep & PTE_USER);
            // get page from ptep
            struct Page *page = pte2page(*ptep);
            // alloc a page for process B
            struct Page *npage = alloc_page();
            assert(page != NULL);
            assert(npage != NULL);
            int ret = 0;
            /* 
             * (1) find src_kvaddr: the kernel virtual address of page
             * (2) find dst_kvaddr: the kernel virtual address of npage
             * (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE
             * (4) build the map of phy addr of  nage with the linear addr start
             */
            uintptr_t *dst_kvaddr = page2kva(npage);
            uintptr_t *src_kvaddr = page2kva(page);
            memcpy(dst_kvaddr, src_kvaddr, PGSIZE);
            ret = page_insert(to, npage, start, perm);
            assert(ret == 0);
        }
        start += PGSIZE;
    } while (start != 0 && start < end);
    return 0;
}

copy_range函数就是调用一个memcpy将父进程的内存直接复制给子进程即可。

练习3: 阅读分析源代码,理解进程执行 fork/exec/wait/exit 的实现,以及系统调用的实现(不需要编码)

请分析fork/exec/wait/exit在实现中是如何影响进程的执行状态的?

​ 系统调用分析如上
运行截图
ucore Lab5 用户进程管理_第1张图片

你可能感兴趣的:(操作系统,c语言)