操作系统实验五实验报告

实验五:用户进程管理


练习0:填写已有实验

使用meld可以简单地将前几个lab的代码填入lab5中,但是要注意在这次实验中,部分代码需要做出修改,如下,主要是idt_inittrap_dispatchalloc_procdo_fork这四个函数

  • kern/trap/trap.c中lab1的部分代码
/* LAB5 YOUR CODE */
//you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore
//so you should setup the syscall interrupt gate in here
extern uintptr_t __vectors[];
for(int 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);
...
/* LAB5 YOUR CODE */
/* you should upate you lab1 code (just add ONE or TWO lines of code):
 *    Every TICK_NUM cycle, you should set current process's current->need_resched = 1
 */
ticks++;
if(ticks % TICK_NUM == 0){
    assert(current != NULL);         //注意,LAB1中这里还有一行print_ticks(),必须去掉,否在在make grade中会由于spin和waitkill函数耗时过长导致这里print_ticks()输出的信息被当作错误处理
    current->need_resched = 1;       //每当时钟走过TICK_NUM次,就将当前进程切换掉(设置被需要被调度),意为时间片已用完
}
  • kern/process/proc.c中lab4的部分代码
//LAB5 YOUR CODE : (update LAB4 steps)
/*
 * below fields(add in LAB5) in proc_struct need to be initialized
 *       uint32_t wait_state;                        // waiting state
 *       struct proc_struct *cptr, *yptr, *optr;     // relations between processes
 */
proc->wait_state = 0;                             //初始化等待状态为0
proc->cptr = proc->yptr = proc->optr = NULL;      //相关进程设置为NULL,cptr为子线程children,yptr和optr为兄弟线程younger/older
...
//LAB5 YOUR CODE : (update LAB4 steps)
/* Some Functions
 *    set_links:  set the relation links of process.  ALSO SEE: remove_links:  lean the relation links of process
 *    -------------------
 *    update step 1: set child proc's parent to current process, make sure current process's wait_state is 0
 *    update step 5: insert proc_struct into hash_list && proc_list, set the relation links of process
 */
proc->parent = current;                           //设置进程为当前进程的子进程
assert(current->wait_state == 0);                 //确保当前进程在等待
bool intr_flag;                                   //锁的作用参考lab4实验报告
local_intr_save(intr_flag);
{
    proc->pid = get_pid();
    hash_proc(proc);
    set_links(proc);                              //单独的计数不满足调度的要求,使用set_links来设置进程关系(父子/兄弟)并加入到链表中
}
local_intr_restore(intr_flag);

练习1:加载应用程序并执行

1、应用程序的组成和编译

由于文件系统尚未实现,在本实验中make的最后一步通过一个ld命令将应用程序的执行码在生成kernel时直接链接到kernel的末端,并且将程序的位置和大小记录在两个全局变量中,供内核后续通过全局变量加载并执行应用程序

2、虚拟地址空间

tools/user.ld中记录了用户虚拟空间的执行入口虚拟地址,tools/kernel.ld中记录了内核虚拟空间的起始入口虚拟地址,kern\mm\memlayout.h中描述了虚拟地址空间的分配

SECTIONS {
    /* Load programs at this address: "." means the current address */
    . = 0x800020;

SECTIONS {
    /* Load the kernel at this address: "." means the current address */
    . = 0xC0100000;

/* *
 * Virtual memory map:                                          Permissions
 *                                                              kernel/user
 *
 *     4G ------------------> +---------------------------------+
 *                            |                                 |
 *                            |         Empty Memory (*)        |
 *                            |                                 |
 *                            +---------------------------------+ 0xFB000000
 *                            |   Cur. Page Table (Kern, RW)    | RW/-- PTSIZE
 *     VPT -----------------> +---------------------------------+ 0xFAC00000
 *                            |        Invalid Memory (*)       | --/--
 *     KERNTOP -------------> +---------------------------------+ 0xF8000000
 *                            |                                 |
 *                            |    Remapped Physical Memory     | RW/-- KMEMSIZE
 *                            |                                 |
 *     KERNBASE ------------> +---------------------------------+ 0xC0000000
 *                            |        Invalid Memory (*)       | --/--
 *     USERTOP -------------> +---------------------------------+ 0xB0000000
 *                            |           User stack            |
 *                            +---------------------------------+
 *                            |                                 |
 *                            :                                 :
 *                            |         ~~~~~~~~~~~~~~~~        |
 *                            :                                 :
 *                            |                                 |
 *                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                            |       User Program & Heap       |
 *     UTEXT ---------------> +---------------------------------+ 0x00800000
 *                            |        Invalid Memory (*)       | --/--
 *                            |  - - - - - - - - - - - - - - -  |
 *                            |    User STAB Data (optional)    |
 *     USERBASE, USTAB------> +---------------------------------+ 0x00200000
 *                            |        Invalid Memory (*)       | --/--
 *     0 -------------------> +---------------------------------+ 0x00000000
 * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
 *     "Empty Memory" is normally unmapped, but user programs may map pages
 *     there if desired.
 *
 * */

3、创建并执行用户进程

在实验四练习3中详细介绍了线程的创建、切换和执行过程,这里第一个用户进程是由第二个内核线程initproc通过把应用程序执行码覆盖到initproc的用户虚拟内存空间来创建的,前面过程参考

http://blog.csdn.net/jasonyuchen/article/details/76622612

init_main调用kernel_thread加载user_main,利用宏及在链接时确定的程序位置和大小的全局变量,进而调用kernel_execve来调用SYS_exec系统调用,让内核来创建此用户进程,调用顺序为

...->init_main(proc.c)->...->user_main(proc.c)->kernel_execve(proc.c)
->vector128(vectors.S)->__alltraps(trapentry.S)->trap(trap.c)->trap_dispatch(trap.c)
->syscall(syscall.c)->sys_exec(syscall.c)->do_execve(proc.c)
// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread
static int
kernel_execve(const char *name, unsigned char *binary, size_t size) {
    int ret, len = strlen(name);
    asm volatile (
        "int %1;"
        : "=a" (ret)
        : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (len), "b" (binary), "D" (size)
        : "memory");
    return ret;
}

#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);         \
        })

#define __KERNEL_EXECVE2(x, xstart, xsize) ({                           \
            extern unsigned char xstart[], xsize[];                     \
            __KERNEL_EXECVE(#x, xstart, (size_t)xsize);                 \
        })

#define KERNEL_EXECVE2(x, xstart, xsize)        __KERNEL_EXECVE2(x, xstart, xsize)

// user_main - kernel thread used to exec a user program
static int
user_main(void *arg) {
#ifdef TEST
    KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
    KERNEL_EXECVE(hello);
#endif
    panic("user_main execve failed.\n");
}

do_execve过程

  • 将旧的内存空间清除,但保留PID(保留壳)
if (mm != NULL) {
    lcr3(boot_cr3);             //将页表基址指向内核页表boot_cr3
    if (mm_count_dec(mm) == 0) {//假如引用次数归零
        exit_mmap(mm);          //取消映射
        put_pgdir(mm);          //释放PDT占用的空间
        mm_destroy(mm);         //释放mm占用的空间
    }
    current->mm = NULL;         //释放指向mm的指针
}
  • 利用load_icode填入新的内容
if ((ret = load_icode(binary, size)) != 0) {
    goto execve_exit;
}
  • 返回

load_icode过程

  • 创建新的内存空间,调用mm_create函数来申请进程的内存管理数据结构并初始化,调用setup_pgdir函数来申请一个页目录表所需的一个页并让mm->pgdir指向此页目录表
  • 解析ELF格式的代码,找到头和各个段,调用mm_map根据ELF格式的执行程序说明的各个段的起始位置和大小建立相应的vma结构,完成合法空间的建立
  • 根据各个段的起始位置确定虚拟地址,建立好物理地址和虚拟地址的映射关系,将各个段拷贝进内核虚拟地址中或是清空(BSS段)
  • 设置用户栈,调用mm_mmap建立用户栈的vma结构,并分配一定数量的物理内存建立栈的物理地址和虚拟地址的映射关系
  • 内存管理建立完成,将mm->pgdir赋值给cr3寄存器
  • 清空并重新设置中断帧,使得执行中断返回iret后转到用户态特权级并回到用户态内存空间,且能够跳转到用户进程的第一跳指令执行,并确保能够响应中断

最后一条是本次练习的实现部分,load_icode完整程序如下

/* load_icode - load the content of binary program(ELF format) as the new content of current process
 * @binary:  the memory addr of the content of binary program
 * @size:  the size of the content of binary program
 */
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;
    }
    //(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
    if (setup_pgdir(mm) != 0) {
        goto bad_pgdir_cleanup_mm;
    }
    //(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process
    struct Page *page;
    //(3.1) get the file header of the bianry program (ELF format)
    struct elfhdr *elf = (struct elfhdr *)binary;
    //(3.2) get the entry of the program section headers of the bianry program (ELF format)
    struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff);
    //(3.3) This program is valid?
    if (elf->e_magic != ELF_MAGIC) {
        ret = -E_INVAL_ELF;
        goto bad_elf_cleanup_pgdir;
    }

    uint32_t vm_flags, perm;
    struct proghdr *ph_end = ph + elf->e_phnum;
    for (; ph < ph_end; ph ++) {
    //(3.4) find every program section headers
        if (ph->p_type != ELF_PT_LOAD) {
            continue ;
        }
        if (ph->p_filesz > ph->p_memsz) {
            ret = -E_INVAL_ELF;
            goto bad_cleanup_mmap;
        }
        if (ph->p_filesz == 0) {
            continue ;
        }
    //(3.5) call mm_map fun to setup the new vma ( ph->p_va, ph->p_memsz)
        vm_flags = 0, perm = PTE_U;
        if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
        if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
        if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ;
        if (vm_flags & VM_WRITE) perm |= PTE_W;
        if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) {
            goto bad_cleanup_mmap;
        }
        unsigned char *from = binary + ph->p_offset;
        size_t off, size;
        uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);

        ret = -E_NO_MEM;

     //(3.6) alloc memory, and  copy the contents of every program section (from, from+end) to process's memory (la, la+end)
        end = ph->p_va + ph->p_filesz;
     //(3.6.1) copy TEXT/DATA section of bianry program
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memcpy(page2kva(page) + off, from, size);
            start += size, from += size;
        }

      //(3.6.2) build BSS section of binary program
        end = ph->p_va + ph->p_memsz;
        if (start < la) {
            /* ph->p_memsz == ph->p_filesz */
            if (start == end) {
                continue ;
            }
            off = start + PGSIZE - la, size = PGSIZE - off;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size);
            start += size;
            assert((end < la && start == end) || (end >= la && start == la));
        }
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size);
            start += size;
        }
    }
    //(4) build user stack memory
    vm_flags = VM_READ | VM_WRITE | VM_STACK;
    if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) {
        goto bad_cleanup_mmap;
    }
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL);

    //(5) set current process's mm, sr3, and set CR3 reg = physical addr of Page Directory
    mm_count_inc(mm);
    current->mm = mm;
    current->cr3 = PADDR(mm->pgdir);
    lcr3(PADDR(mm->pgdir));

    //(6) setup trapframe for user environment
    struct trapframe *tf = current->tf;
    memset(tf, 0, sizeof(struct trapframe));
    /* LAB5:EXERCISE1 YOUR CODE
     * should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags
     * NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So
     *          tf_cs should be USER_CS segment (see memlayout.h)
     *          tf_ds=tf_es=tf_ss should be USER_DS segment
     *          tf_esp should be the top addr of user stack (USTACKTOP)
     *          tf_eip should be the entry point of this binary program (elf->e_entry)
     *          tf_eflags should be set to enable computer to produce Interrupt
     */
    tf->tf_cs = USER_CS;                           //用户代码段为USER_CS(memlayout.h)
    tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;   //用户数据段为USER_DS(memlayout.h)
    tf->tf_esp = USTACKTOP;                        //用户栈栈顶USTACKTOP=0xB0000000见2中的虚拟内存空间结构
    tf->tf_eip = elf->e_entry;
    tf->tf_eflags = FL_IF;                         //FL_IF为中断允许标记(mmu.h)
    ret = 0;
out:
    return ret;
bad_cleanup_mmap:
    exit_mmap(mm);
bad_elf_cleanup_pgdir:
    put_pgdir(mm);
bad_pgdir_cleanup_mm:
    mm_destroy(mm);
bad_mm:
    goto out;
}

用户进程的环境搭建完毕后,initproc按产生系统调用的函数调用路径原理返回到trapentry.S中的call trap下继续执行__trapretiret时将切换到用户进程的第一条语句的位置_start继续执行


练习2:父进程复制自己的内存空间给子进程

创建子进程的do_fork函数在执行中将拷贝当前进程的用户内存地址空间中的合法内容到新进程,即lab4中被搁置的copy_mm函数完成的功能,调用关系如下,copy_range函数的功能是本练习需要实现的

do_fork(proc.c)->copy_mm(proc.c)->dup_mmap(vmm.c)->copy_range(pmm.c)

copy_range函数完成的功能很简单,就是将父进程的一段内存内容复制到子进程中,实现如下

/* copy_range - copy content of memory (start, end) of one process A to another process B
 * @to:    the addr of process B's Page Directory
 * @from:  the addr of process A's Page Directory
 * @share: flags to indicate to dup OR share. We just use dup method, so it didn't be used.
 *
 * CALL GRAPH: copy_mm-->dup_mmap-->copy_range
 */
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;
        /* LAB5:EXERCISE2 YOUR CODE
         * replicate content of page to npage, build the map of phy addr of nage with the linear addr start
         *
         * Some Useful MACROs and DEFINEs, you can use them in below implementation.
         * MACROs or Functions:
         *    page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h)
         *    page_insert: build the map of phy addr of an Page with the linear addr la
         *    memcpy: typical memory copy function
         *
         * (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
         */
        void * src_kvaddr = page2kva(page); 
        void * dst_kvaddr = page2kva(npage);
        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;
}

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

在练习1的分析过程中已经可以初步看出利用系统调用实现用户进程的创建执行过程,下面简单分析(参考kern/syscall/syscall.c

1、fork

fork使用了系统调用SYS_fork,由do_fork来完成,fork的过程在lab4中有非常详细的介绍,参考实验报告

http://blog.csdn.net/jasonyuchen/article/details/76622612

2、exec

exec使用了系统调用SYS_exec,由do_execve来完成,exec的过程在练习1中有非常详细的介绍,参考上文练习1的部分

3、wait

wait使用了系统调用SYS_wait,由do_wait来完成,do_wait源码如下,主要完成了等待指定的一个或任意一个处于僵死状态的子进程,并且释放掉其占用的空间

// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack
//         - proc struct of this child.
// NOTE: only after do_wait function, all resources of the child proces are free.
int
do_wait(int pid, int *code_store) {
    struct mm_struct *mm = current->mm;
    if (code_store != NULL) {
        if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) {
            return -E_INVAL;
        }
    }

    struct proc_struct *proc;
    bool intr_flag, haskid;
repeat:
    haskid = 0;
    if (pid != 0) {              //如果pid不为0,则寻找指定pid的僵死子进程
        proc = find_proc(pid);
        if (proc != NULL && proc->parent == current) {
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
                goto found;
            }
        }
    }
    else {                       //如果pid为0,则寻找任意一个僵死子进程
        proc = current->cptr;
        for (; proc != NULL; proc = proc->optr) {
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
                goto found;
            }
        }
    }
    if (haskid) {                //如果找到的子进程不是僵死状态,说明还未退出,设置当前进程状态为睡眠状态,等待状态为等待子进程退出状态,然后调度新进程
        current->state = PROC_SLEEPING;     //睡眠状态
        current->wait_state = WT_CHILD;     //等待子进程退出状态
        schedule();                         //调度新进程运行
        if (current->flags & PF_EXITING) {  //如果正在关闭,则退出进程(PF_EXITING定义在proc.h中表示getting shutdown)
            do_exit(-E_KILLED);
        }
        goto repeat;
    }
    return -E_BAD_PROC;

found:                                      //找到僵死子进程,开始回收资源
    if (proc == idleproc || proc == initproc) {
        panic("wait idleproc or initproc.\n");
    }
    if (code_store != NULL) {
        *code_store = proc->exit_code;
    }
    local_intr_save(intr_flag);             //注意加锁使得线程安全
    {
        unhash_proc(proc);
        remove_links(proc);
    }
    local_intr_restore(intr_flag);
    put_kstack(proc);
    kfree(proc);
    return 0;
}

4、exit

exit使用了系统调用SYS_exit,由do_exit来完成,do_exit源码如下,主要完成了退出当前进程并回收资源,然后唤醒其父进程的工作

// do_exit - called by sys_exit
//   1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process
//   2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself.
//   3. call scheduler to switch to other process
int
do_exit(int error_code) {
    if (current == idleproc) {
        panic("idleproc exit.\n");
    }
    if (current == initproc) {
        panic("initproc exit.\n");
    }

    struct mm_struct *mm = current->mm;
    if (mm != NULL) {                       //回收用户虚拟内存空间
        lcr3(boot_cr3);
        if (mm_count_dec(mm) == 0) {
            exit_mmap(mm);
            put_pgdir(mm);
            mm_destroy(mm);
        }
        current->mm = NULL;
    }
    current->state = PROC_ZOMBIE;           //设置进程状态为僵死状态
    current->exit_code = error_code;        //设置进程退出码为`error_code`

    bool intr_flag;                         //使用锁使得线程安全
    struct proc_struct *proc;
    local_intr_save(intr_flag);
    {
        proc = current->parent;
        if (proc->wait_state == WT_CHILD) { //若当前进程的父进程正在等待当前进程结束,则唤醒父进程
            wakeup_proc(proc);
        }
        while (current->cptr != NULL) {     //若当前进程还有子进程,则将这些进程的父进程设置为内核线程
            proc = current->cptr;
            current->cptr = proc->optr;

            proc->yptr = NULL;
            if ((proc->optr = initproc->cptr) != NULL) {
                initproc->cptr->yptr = proc;
            }
            proc->parent = initproc;
            initproc->cptr = proc;
            if (proc->state == PROC_ZOMBIE) {               //如果子进程是僵死进程(此时它的父进程已经被改为内核线程)
                if (initproc->wait_state == WT_CHILD) {     //如果内核线程等待子进程结束,则唤醒内核线程
                    wakeup_proc(initproc);
                }
            }
        }
    }
    local_intr_restore(intr_flag);

    schedule();                             //调度执行新进程
    panic("do_exit will not return!! %d.\n", current->pid);
}

5、系统调用的实现

  • 初始化系统调用对应的中断描述符

这个工作在lab1中就已涉及,在本实验中对代码进行了修改,参考练习0

  • 建立系统调用的用户库准备

在用户态中建立中间层对系统调用进行封装,实现在user/libs/ulib.[ch]user/libs/syscall.[ch]

static inline int
syscall(int num, ...) {
    va_list ap;
    va_start(ap, num);
    uint32_t a[MAX_ARGS];
    int i, ret;
    for (i = 0; i < MAX_ARGS; i ++) {   //最多6个寄存器来传递系统调用,MAX_ARGS=5,5个参数1个系统调用号
        a[i] = va_arg(ap, uint32_t);
    }
    va_end(ap);

    asm volatile (
        "int %1;"
        : "=a" (ret)                    //返回值放在EAX
        : "i" (T_SYSCALL),              //系统调用
          "a" (num),                    //系统调用号放在EAX
          "d" (a[0]),                   //参数a[0]放在EDX
          "c" (a[1]),                   //参数a[1]放在ECX
          "b" (a[2]),                   //参数a[2]放在EBX
          "D" (a[3]),                   //参数a[3]放在EDI
          "S" (a[4])                    //参数a[4]放在ESI
        : "cc", "memory");
    return ret;
}
  • 与用户进程相关的系统调用
SYS_exit        : process exit,                           -->do_exit
SYS_fork        : create child process, dup mm            -->do_fork-->wakeup_proc
SYS_wait        : wait process                            -->do_wait
SYS_exec        : after fork, process execute a program   -->load a program and refresh the mm
SYS_clone       : create child thread                     -->do_fork-->wakeup_proc
SYS_yield       : process flag itself need resecheduling  -->proc->need_sched=1, then scheduler will rescheule this process
SYS_sleep       : process sleep                           -->do_sleep 
SYS_kill        : kill process                            -->do_kill-->proc->flags |= PF_EXITING-->wakeup_proc-->do_wait-->do_exit
SYS_getpid      : get the process's pid
  • 系统调用的执行过程

与函数调用的区别主要有四点:

1、不是通过CALL指令而是通过INT指令调用
2、不是通过RET指令而是通过IRET指令返回
3、到达内核后严格检查传递的参数,确保安全
4、执行系统调用可导致进程等待某事件发生,从而引起进程切换

SYS_getpid系统调用为例,当用户系统调用时,执行到INT T_SYSCALL指令,根据操作系统建立的中断描述符转入内核态,并跳转到vector128处,开始系统调用执行过程

vector128(vectors.S)->__alltraps(trapentry.S)->trap(trap.c)->trap_dispatch(trap.c)
->syscall(syscall.c)->sys_getpid(syscall.c)->...->__trapret(trapentry.S)

执行trap前软件进一步保存执行系统调用的执行现场(见kern/trap/trapentry.S),将相关内容保存到当前进程的中断帧中
随后操作系统开始完成具体的系统调用,在SYS_getpid中将pid值返回就是具体的系统调用服务,按路径原路返回到__alltraps中,然后继续执行恢复现场操作,直到IRET将EIP指向tf_eipINT_SYSCALL后的指令,继续执行,系统调用结束

__alltraps:
    # push registers to build a trap frame
    # therefore make the stack look like a struct trapframe
    pushl %ds
    pushl %es
    pushl %fs
    pushl %gs
    pushal

    # load GD_KDATA into %ds and %es to set up data segments for kernel
    movl $GD_KDATA, %eax
    movw %ax, %ds
    movw %ax, %es

    # push %esp to pass a pointer to the trapframe as an argument to trap()
    pushl %esp

    # call trap(tf), where tf=%esp
    call trap                                     
    #从系统调用中返回到这里,下面的过程可以参考lab4实验报告中练习3的第7步
    # pop the pushed stack pointer
    popl %esp

    # return falls through to trapret...
.globl __trapret
__trapret:
    # restore registers from stack
    popal

    # restore %ds, %es, %fs and %gs
    popl %gs
    popl %fs
    popl %es
    popl %ds

    # get rid of the trap number and error code
    addl $0x8, %esp
    iret

总结

全部编码完成后,运行make grade可以获得输出如下,可以看出实验成功

注意若在spinwaitkill出现错误,可以尝试将kern/trap/trap.c中的trap_dispatch里lab1完成的代码部分注释掉print_ticks()

make --quiet --no-print-directory clean
sh tools/grade.sh
badsegment:              (3.4s)
  -check result:                             OK
  -check output:                             OK
divzero:                 (1.7s)
  -check result:                             OK
  -check output:                             OK
softint:                 (1.7s)
  -check result:                             OK
  -check output:                             OK
faultread:               (1.7s)
  -check result:                             OK
  -check output:                             OK
faultreadkernel:         (1.7s)
  -check result:                             OK
  -check output:                             OK
hello:                   (1.7s)
  -check result:                             OK
  -check output:                             OK
testbss:                 (1.7s)
  -check result:                             OK
  -check output:                             OK
pgdir:                   (1.7s)
  -check result:                             OK
  -check output:                             OK
yield:                   (1.7s)
  -check result:                             OK
  -check output:                             OK
badarg:                  (1.5s)
  -check result:                             OK
  -check output:                             OK
exit:                    (1.6s)
  -check result:                             OK
  -check output:                             OK
spin:                    (4.7s)
  -check result:                             OK
  -check output:                             OK
waitkill:                (13.7s)
  -check result:                             OK
  -check output:                             OK
forktest:                (1.7s)
  -check result:                             OK
  -check output:                             OK
forktree:                (1.7s)
  -check result:                             OK
  -check output:                             OK
Total Score: 150/150

你可能感兴趣的:(OperatingSystem)