国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Ucore lab5

這篇具有很好參考價(jià)值的文章主要介紹了Ucore lab5。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

實(shí)驗(yàn)?zāi)康?/h3>
  • 了解第一個(gè)用戶進(jìn)程創(chuàng)建過(guò)程
  • 了解系統(tǒng)調(diào)用框架的實(shí)現(xiàn)機(jī)制
  • 了解ucore如何實(shí)現(xiàn)系統(tǒng)調(diào)用sys_fork/sys_exec/sys_exit/sys_wait來(lái)進(jìn)行進(jìn)程管理

實(shí)驗(yàn)內(nèi)容

練習(xí)0:已有實(shí)驗(yàn)代碼改進(jìn)

?本實(shí)驗(yàn)中完成了用戶進(jìn)程的創(chuàng)建,能夠?qū)τ脩暨M(jìn)程進(jìn)行基本管理,并為用戶進(jìn)程提供了必要的系統(tǒng)調(diào)用。為了支持用戶進(jìn)程,需要對(duì)已完成的實(shí)驗(yàn)代碼進(jìn)行改進(jìn)。

1.proc_struct結(jié)構(gòu)

?為了能夠管理進(jìn)程,進(jìn)程控制塊中新增加了變量,記錄等待狀態(tài)和退出原因,并將相關(guān)進(jìn)程通過(guò)鏈表鏈接起來(lái)。

  • exit_code:記錄進(jìn)程的退出原因,這個(gè)值將傳給等待的父進(jìn)程
  • wait_state:標(biāo)記當(dāng)前進(jìn)程是否處于等待狀態(tài)
  • cptr:當(dāng)前進(jìn)程的子進(jìn)程雙向鏈表結(jié)點(diǎn)
  • yptr/optr:當(dāng)前進(jìn)程的older sibling和younger sibling的雙向鏈表結(jié)點(diǎn)
struct proc_struct {
    enum proc_state state;                      // Process state
    int pid;                                    // Process ID
    int runs;                                   // the running times of Proces
    uintptr_t kstack;                           // Process kernel stack
    volatile bool need_resched;                 // bool value: need to be rescheduled to release CPU?
    struct proc_struct *parent;                 // the parent process
    struct mm_struct *mm;                       // Process's memory management field
    struct context context;                     // Switch here to run process
    struct trapframe *tf;                       // Trap frame for current interrupt
    uintptr_t cr3;                              // CR3 register: the base addr of Page Directroy Table(PDT)
    uint32_t flags;                             // Process flag
    char name[PROC_NAME_LEN + 1];               // Process name
    list_entry_t list_link;                     // Process link list 
    list_entry_t hash_link;                     // Process hash list
    int exit_code;                              // exit code (be sent to parent proc)
    uint32_t wait_state;                        // waiting state
    struct proc_struct *cptr, *yptr, *optr;     // relations between processes
};
2.alloc_proc

?由于進(jìn)程控制塊增加了新的變量,分配進(jìn)程控制塊時(shí)也需要進(jìn)行相應(yīng)的初始化。只需要在alloc_proc添加,將等待狀態(tài)設(shè)為0,鏈表節(jié)點(diǎn)設(shè)為NULL。

    ... 
	proc->wait_state=0;
    proc->cptr = proc->optr = proc->yptr = NULL;
	...
3.do_fork

?do_fork中進(jìn)行了進(jìn)程的復(fù)制,對(duì)新的進(jìn)程控制塊的設(shè)置也需要補(bǔ)充。確認(rèn)等待狀態(tài)為0,只有在wait狀態(tài)時(shí)進(jìn)程的wait_state才會(huì)被設(shè)置為等待,一旦被喚醒,在wakeup_proc就會(huì)重新設(shè)置為0,此時(shí)應(yīng)該不在等待狀態(tài)。調(diào)用set_links將新進(jìn)程和相關(guān)進(jìn)程建立聯(lián)系。補(bǔ)充后的do_fork如下:

    if((proc=alloc_proc())==NULL) {
    	goto fork_out;
    }
    assert(current->wait_state == 0);	//確保等待狀態(tài)為0
    proc->parent = current;
    if(setup_kstack(proc)) {
    	goto bad_fork_cleanup_proc;
    }
    if(copy_mm(clone_flags,proc)) {
    	goto bad_fork_cleanup_kstack;
    }
    copy_thread(proc, stack, tf);		//復(fù)制上下文和中斷幀
    //設(shè)置pid,加入進(jìn)程列表,設(shè)置為可運(yùn)行
    bool intr_flag=0;
    local_intr_save(intr_flag);
    {
    	proc->pid = get_pid();
    	hash_proc(proc);
    	set_links(proc);				//設(shè)置鏈表
    }
    local_intr_restore(intr_flag);
    wakeup_proc(proc);
    ret=proc->pid;

?set_links會(huì)將進(jìn)程加入進(jìn)程鏈表,設(shè)置父進(jìn)程的子進(jìn)程為自己,找到自己的older sibling進(jìn)程,最后將進(jìn)程數(shù)+1。

static void
set_links(struct proc_struct *proc) {
    list_add(&proc_list, &(proc->list_link));
    proc->yptr = NULL;
    if ((proc->optr = proc->parent->cptr) != NULL) {
        proc->optr->yptr = proc;
    }
    proc->parent->cptr = proc;
    nr_process ++;
}
4.idt_init

?引入用戶進(jìn)程后,需要用戶進(jìn)程能夠進(jìn)行系統(tǒng)調(diào)用,即可以發(fā)起中斷,進(jìn)行特權(quán)級(jí)切換。系統(tǒng)調(diào)用的中斷號(hào)是128,需要單獨(dú)設(shè)置該中斷向量的特權(quán)級(jí)為用戶特權(quán)級(jí),這樣用戶就可以通過(guò)該中斷發(fā)起系統(tǒng)調(diào)用。

void
idt_init(void) {
      extern uintptr_t __vectors[];
      int num=sizeof(idt)/sizeof(struct gatedesc);
      for(int i=0;i<num;i++){
      		SETGATE(idt[i],1,GD_KTEXT,__vectors[i],DPL_KERNEL);
      }
      //為T_SYSCALL設(shè)置用戶態(tài)權(quán)限
      SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);	
      lidt(&idt_pd);
}
5.trap_disptach

?為了操作系統(tǒng)能正常進(jìn)行進(jìn)程調(diào)度,需要在時(shí)鐘中斷處,將進(jìn)程的need_schedule設(shè)置為1,表示該進(jìn)程時(shí)間配額已用完,需要調(diào)度運(yùn)行其他程序。在trap調(diào)用trap_dispatch完成中斷服務(wù)例程后,會(huì)判斷這個(gè)值是否為1,然后調(diào)用need_schedule進(jìn)行進(jìn)程調(diào)度。

//trap_disptach
		case IRQ_OFFSET + IRQ_TIMER:
        ticks++;
        if(ticks%TICK_NUM==0) {
            assert(current != NULL);
            current->need_resched = 1;
        }
        break;
//trap中最后進(jìn)行進(jìn)程調(diào)度
void trap(struct trapframe *tf) {
    // dispatch based on what type of trap occurred
    // used for previous projects
    if (current == NULL) {
        trap_dispatch(tf);
    }
    else {
        // keep a trapframe chain in stack
        struct trapframe *otf = current->tf;
        current->tf = tf;
    
        bool in_kernel = trap_in_kernel(tf);			//是否是內(nèi)核產(chǎn)生的中斷
    
        trap_dispatch(tf);
    
        current->tf = otf;
        if (!in_kernel) {
            if (current->flags & PF_EXITING) {
                do_exit(-E_KILLED);
            }
            if (current->need_resched) {				//判斷是否需要調(diào)度
                schedule();
            }
        }
    }
}

練習(xí)1: 加載應(yīng)用程序并執(zhí)行

1.從內(nèi)核線程到用戶進(jìn)程

?Lab4中已經(jīng)實(shí)現(xiàn)了內(nèi)核線程的創(chuàng)建,能夠在內(nèi)核態(tài)運(yùn)行線程。在Lab5中需要實(shí)現(xiàn)用戶進(jìn)程的創(chuàng)建,并為用戶進(jìn)程提供一些系統(tǒng)調(diào)用,并對(duì)用戶進(jìn)程的執(zhí)行進(jìn)行基本的管理。

?進(jìn)程運(yùn)行在用戶態(tài),有自己的地址空間。與內(nèi)核相比,進(jìn)程管理和內(nèi)存管理這兩個(gè)部分有很大的不同。

?進(jìn)程管理

?在進(jìn)程管理方面,操作系統(tǒng)主要需要實(shí)現(xiàn)的有:建立進(jìn)程的頁(yè)表和維護(hù)進(jìn)程可訪問(wèn)空間;加載ELF格式的程序到進(jìn)程控制塊管理的內(nèi)存中的方法;在進(jìn)程復(fù)制(fork)過(guò)程中,把父進(jìn)程的內(nèi)存空間拷貝到子進(jìn)程內(nèi)存空間的技術(shù)。此外還需要實(shí)現(xiàn)與用戶態(tài)進(jìn)程生命周期管理相關(guān)的:讓進(jìn)程放棄CPU而睡眠等待某事件;讓父進(jìn)程等待子進(jìn)程結(jié)束;一個(gè)進(jìn)程殺死另一個(gè)進(jìn)程;給進(jìn)程發(fā)消息;建立進(jìn)程的關(guān)系鏈表。

?內(nèi)存管理

?在內(nèi)存管理方面,操作系統(tǒng)主要是需要維護(hù)進(jìn)程的地址空間,即維護(hù)用戶進(jìn)程的頁(yè)表,維護(hù)地址空間到物理內(nèi)存的映射。不同的進(jìn)程有各自的頁(yè)表,即便不同進(jìn)程的用戶態(tài)虛擬地址相同,由于頁(yè)表把虛擬頁(yè)映射到了不同的物理頁(yè)幀,不同進(jìn)程的地址空間也不同,且相互隔離開。此外,在用戶態(tài)內(nèi)存空間和內(nèi)核態(tài)內(nèi)核空間之間需要拷貝數(shù)據(jù),讓CPU處在內(nèi)核態(tài)才能完成對(duì)用戶空間的讀或?qū)?,為此需要設(shè)計(jì)專門的拷貝函數(shù)(copy_from_user和copy_to_user)完成。

?從內(nèi)核線程到用戶進(jìn)程

?在proc_init中,會(huì)建立第1個(gè)內(nèi)核線程idle_proc,這個(gè)線程總是調(diào)度運(yùn)行其他線程。然后proc_init會(huì)調(diào)用kernel_thread建立init_main線程,接著在init_main中將調(diào)用kernel_thread建立user_main線程。user_main仍然是一個(gè)內(nèi)核線程,但他的任務(wù)是創(chuàng)建用戶進(jìn)程。在user_main中將調(diào)用KERNEL_EXECVE,從而調(diào)用kernel_execve來(lái)把某一具體程序(exit)的執(zhí)行內(nèi)容放入內(nèi)存,覆蓋user_main線程,此后就可以調(diào)度執(zhí)行程序,該程序在用戶態(tài)運(yùn)行,此時(shí)也就完成了用戶進(jìn)程的創(chuàng)建。

//在user_main中調(diào)用KERNEL_EXECVE,覆蓋掉user_main,創(chuàng)建用戶進(jìn)程
static int
user_main(void *arg) {
#ifdef TEST
    KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
    KERNEL_EXECVE(exit);
#endif
    panic("user_main execve failed.\n");
}
2.加載應(yīng)用程序
(1).產(chǎn)生中斷

?在user_main中,將調(diào)用KERNEL_EXECVE2加載用戶程序,將該程序的內(nèi)存空間替換掉當(dāng)前線程的內(nèi)存空間,將當(dāng)前內(nèi)核線程轉(zhuǎn)變?yōu)橐獔?zhí)行的進(jìn)程。加載過(guò)程的第一步是由KERNEL_EXECVE2調(diào)用kernel_execve,發(fā)起系統(tǒng)調(diào)用。

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

?由于此時(shí)還沒(méi)有建立文件系統(tǒng),需要執(zhí)行的用戶程序是隨ucore的kernel直接加載到內(nèi)存中的,并使用全局變量記錄了這段用戶程序代碼的起始位置和大小。從宏定義調(diào)用kernel_execve會(huì)將程序名,位置和大小都傳入。kernel_execve將這些信息保存到指定的寄存器中,發(fā)起中斷,進(jìn)行系統(tǒng)調(diào)用,具體的細(xì)節(jié)在練習(xí)三中進(jìn)行分析。中斷進(jìn)行系統(tǒng)調(diào)用時(shí)的調(diào)用順序如下:

vector128(vectors.S)--\>
\_\_alltraps(trapentry.S)--\>trap(trap.c)--\>trap\_dispatch(trap.c)--
--\>syscall(syscall.c)--\>sys\_exec(syscall.c)--\>do_execve(proc.c)

?最終系統(tǒng)調(diào)用將通過(guò)do_execve完成用戶程序的加載。

//syscall.c,exec系統(tǒng)調(diào)用
static int
sys_exec(uint32_t arg[]) {
    const char *name = (const char *)arg[0];
    size_t len = (size_t)arg[1];
    unsigned char *binary = (unsigned char *)arg[2];
    size_t size = (size_t)arg[3];
    return do_execve(name, len, binary, size);
}
(2).do_execve

?接下來(lái)分析do_execve是怎樣加載處于內(nèi)存中的程序并建立好用戶內(nèi)存空間,并設(shè)置中斷幀,完成用戶進(jìn)程創(chuàng)建并執(zhí)行用戶程序的。傳入的參數(shù)為用戶程序名和長(zhǎng)度,用戶程序代碼位置和大小。do_execve完整的實(shí)現(xiàn)如下:

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);
	/*清空內(nèi)存空間*/
    if (mm != NULL) {
        lcr3(boot_cr3);
        if (mm_count_dec(mm) == 0) {
            exit_mmap(mm);
            put_pgdir(mm);
            mm_destroy(mm);
        }
        current->mm = NULL;
    }
    int ret;
    if ((ret = load_icode(binary, size)) != 0) {	//調(diào)用load_icode加載用戶程序并完成后續(xù)工作
        goto execve_exit;
    }
    set_proc_name(current, local_name);
    return 0;

execve_exit:
    do_exit(ret);
    panic("already exit: %e.\n", ret);
}

?進(jìn)入do_execve后先進(jìn)行程序名字長(zhǎng)度的調(diào)整,然后就開始使用新進(jìn)程覆蓋原進(jìn)程。首先,由于新進(jìn)程將使用新的用戶內(nèi)存空間,原進(jìn)程的內(nèi)存空間需要進(jìn)行清空。如果mm_struct為空,則原進(jìn)程是內(nèi)核線程,不需要處理。如果mm_struct不為空,設(shè)置頁(yè)表為內(nèi)核空間頁(yè)表,將引用計(jì)數(shù)-1,如果引用計(jì)數(shù)為0則根據(jù)mm_struct記錄的信息對(duì)原進(jìn)程的內(nèi)存空間進(jìn)行釋放。

//do_execve中將原內(nèi)存空間清空
	if (mm != NULL) {
        lcr3(boot_cr3);
        if (mm_count_dec(mm) == 0) {
            exit_mmap(mm);
            put_pgdir(mm);
            mm_destroy(mm);
        }
        current->mm = NULL;
    }
//exit_mmap調(diào)用unmap_range,exit_range取消地址映射
void exit_mmap(struct mm_struct *mm) {
    assert(mm != NULL && mm_count(mm) == 0);
    pde_t *pgdir = mm->pgdir;
    list_entry_t *list = &(mm->mmap_list), *le = list;
    while ((le = list_next(le)) != list) {
        struct vma_struct *vma = le2vma(le, list_link);
        unmap_range(pgdir, vma->vm_start, vma->vm_end);
    }
    while ((le = list_next(le)) != list) {
        struct vma_struct *vma = le2vma(le, list_link);
        exit_range(pgdir, vma->vm_start, vma->vm_end);
    }
}
//put_pgdir釋放頁(yè)目錄占用的內(nèi)存空間
static void put_pgdir(struct mm_struct *mm) {
    free_page(kva2page(mm->pgdir));
}
//mm_destroy銷毀mm_struct
void mm_destroy(struct mm_struct *mm) {
    assert(mm_count(mm) == 0);

    list_entry_t *list = &(mm->mmap_list), *le;
    while ((le = list_next(list)) != list) {
        list_del(le);
        kfree(le2vma(le, list_link));  //kfree vma        
    }
    kfree(mm); //kfree mm
    mm=NULL;
}

?接下來(lái)的工作就是加載elf格式的用戶程序,申請(qǐng)新的用戶內(nèi)存空間,并設(shè)置中斷幀,使用戶進(jìn)程最終可以運(yùn)行。這些工作都是由load_icode函數(shù)完成的。

(3).load_icode

?load_icode完成了以下6個(gè)工作:

  • 為新進(jìn)程創(chuàng)建mm結(jié)構(gòu)
  • 創(chuàng)建新的頁(yè)目錄,并把內(nèi)核頁(yè)表復(fù)制到新創(chuàng)建的頁(yè)目錄,這樣新進(jìn)程能夠正確映射內(nèi)核空間
  • 分配內(nèi)存,從elf文件中復(fù)制代碼和數(shù)據(jù),初始化.bss段
  • 建立用戶??臻g
  • 將新進(jìn)程的mm結(jié)構(gòu)設(shè)置為剛剛創(chuàng)建的mm
  • 構(gòu)造中斷幀,使用戶進(jìn)程最終能夠正確在用戶態(tài)運(yùn)行

?前兩個(gè)工作比較簡(jiǎn)單,只需要調(diào)用mm_create與setup_pgdir,完成mm結(jié)構(gòu)的創(chuàng)建與新的頁(yè)目錄的創(chuàng)建,創(chuàng)建失敗則需要將已創(chuàng)建的mm和頁(yè)目錄進(jìn)行銷毀。

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;
    //創(chuàng)建一個(gè)mm_struct給用戶程序使用
    if ((mm = mm_create()) == NULL) {
        goto bad_mm;
    }
    //創(chuàng)建新的PDT,并把內(nèi)核頁(yè)表的內(nèi)容復(fù)制到新的頁(yè)目錄
    if (setup_pgdir(mm) != 0) {
        goto bad_pgdir_cleanup_mm;
    }

?第三步主要是創(chuàng)建虛擬內(nèi)存空間vma,根據(jù)elf文件頭的信息復(fù)制代碼段和數(shù)據(jù)段的數(shù)據(jù),并將vma插入mm結(jié)構(gòu)中,表示合法的用戶虛擬空間。

    //(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process
    struct Page *page;
    //elf文件的ELF頭部
    struct elfhdr *elf = (struct elfhdr *)binary;
    //確定elf文件的program section headers
    struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff);
    //確認(rèn)為有效的elf文件
    if (elf->e_magic != ELF_MAGIC) {
        ret = -E_INVAL_ELF;
        goto bad_elf_cleanup_pgdir;
    }
	//開始創(chuàng)建虛擬空間并復(fù)制數(shù)據(jù)
    uint32_t vm_flags, perm;
    struct proghdr *ph_end = ph + elf->e_phnum;
    for (; ph < ph_end; ph ++) {
    	//遍歷每個(gè)program section headers
        if (ph->p_type != ELF_PT_LOAD) {
            continue ;						//不是需要加載的段跳過(guò)
        }
        if (ph->p_filesz > ph->p_memsz) {
            ret = -E_INVAL_ELF;				//大小不正確
            goto bad_cleanup_mmap;
        }
        if (ph->p_filesz == 0) {			//段大小為0,跳過(guò)
            continue ;
        }
    	//調(diào)用mm_map進(jìn)行vma的建立
        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;
        //建立合法vma并插入mm結(jié)構(gòu)維護(hù)的鏈表
        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;
		//已建立了合法的vma,接下來(lái)分配物理內(nèi)存
        end = ph->p_va + ph->p_filesz;
    	//加載elf文件中的數(shù)據(jù)
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {		//分配頁(yè)
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memcpy(page2kva(page) + off, from, size);							//數(shù)據(jù)復(fù)制
            start += size, from += size;
        }

      	//設(shè)置.bss段
        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);								//bss段清0
            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);								//bss段清0
            start += size;
        }
    }

?接下來(lái)的第四步和第五步是建立用戶棧,為用戶棧設(shè)立合法虛擬空間,然后將已經(jīng)設(shè)置好的mm設(shè)置為當(dāng)前進(jìn)程的mm。

    //建立用戶棧,設(shè)置合法虛擬空間
    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);
    //設(shè)置當(dāng)前的mm,cr3
    mm_count_inc(mm);
    current->mm = mm;
    current->cr3 = PADDR(mm->pgdir);
    lcr3(PADDR(mm->pgdir));

?最后一步就是構(gòu)造用戶進(jìn)程的中斷幀,在load_icode、sys_exec函數(shù)返回,中斷結(jié)束后,從中斷幀恢復(fù)寄存器后回到用戶態(tài),降低特權(quán)級(jí),能夠執(zhí)行用戶進(jìn)程的程序。中斷幀中,cs,ds,ss,es寄存器設(shè)置為用戶代碼段和數(shù)據(jù)段的段寄存器,esp設(shè)置為用戶棧的棧頂,eip設(shè)置為用戶程序的入口,最后設(shè)置標(biāo)志位,使用戶進(jìn)程可以被中斷,這樣中斷幀就設(shè)置好了用戶態(tài)下用戶進(jìn)程運(yùn)行的環(huán)境。這一步是練習(xí)一中要求補(bǔ)全的部分,代碼如下:

    struct trapframe *tf = current->tf;
    memset(tf, 0, sizeof(struct trapframe));
    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; 
    ret = 0;
3.應(yīng)用程序的運(yùn)行

?通過(guò)上述do_execve中的操作,原來(lái)的user_main已經(jīng)被用戶進(jìn)程所替換掉了。此時(shí)處于RUNNABLE狀態(tài)的是已經(jīng)創(chuàng)建完成了的用戶進(jìn)程,系統(tǒng)調(diào)用已經(jīng)完成,將按照調(diào)用的順序一路返回到__trapret,從中斷幀中恢復(fù)寄存器的值,通過(guò)iret回到用戶進(jìn)程exit的第一條語(yǔ)句(initcode.S中的_start)開始執(zhí)行。

?綜上所述,一個(gè)用戶進(jìn)程創(chuàng)建到執(zhí)行第一條指令的完整過(guò)程如下:

  • 父進(jìn)程通過(guò)fork系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程。通過(guò)do_fork進(jìn)行進(jìn)程資源的分配,創(chuàng)建出新的進(jìn)程
  • fork返回0,子進(jìn)程創(chuàng)建完成,等待調(diào)度。fork中將進(jìn)程設(shè)置為RUNNABLE,該進(jìn)程可以運(yùn)行schedule函數(shù)進(jìn)行調(diào)度,調(diào)用proc_run運(yùn)行該進(jìn)程
  • 該進(jìn)程調(diào)用kernel_execve,產(chǎn)生中斷并進(jìn)行exec系統(tǒng)調(diào)用
  • do_execve將當(dāng)前進(jìn)程替換為需要運(yùn)行的用戶進(jìn)程,加載程序并設(shè)置好中斷幀
  • 從中斷幀返回到用戶態(tài),根據(jù)中斷幀中設(shè)置的eip,跳轉(zhuǎn)執(zhí)行用戶程序的第一條指令

練習(xí)2:復(fù)制父進(jìn)程的內(nèi)存

1.復(fù)制父進(jìn)程的內(nèi)存

?在Lab4中已經(jīng)分析過(guò)了do_fork函數(shù)對(duì)創(chuàng)建的進(jìn)程的資源的分配,其中內(nèi)存資源的分配是由copy_mm完成的,Lab4創(chuàng)建內(nèi)核線程,因此沒(méi)有進(jìn)行內(nèi)存的復(fù)制,在本實(shí)驗(yàn)中,copy_mm將為新進(jìn)程分配內(nèi)存空間,并將父進(jìn)程的內(nèi)存資源復(fù)制到新進(jìn)程的內(nèi)存空間。

(1)copy_mm

?對(duì)于共享內(nèi)存的線程或進(jìn)程,不需要進(jìn)行復(fù)制,根據(jù)clone_flags判斷是共享時(shí),可以直接返回父進(jìn)程的mm。而不共享的情況下,首先創(chuàng)建一個(gè)mm_struct,調(diào)用setup_pgdir創(chuàng)建新的頁(yè)目錄,并將內(nèi)核頁(yè)目錄復(fù)制到新的頁(yè)目錄,然后調(diào)用dup_mmap進(jìn)行內(nèi)存資源的復(fù)制。

static int
copy_mm(uint32_t clone_flags, struct proc_struct *proc) {
    struct mm_struct *mm, *oldmm = current->mm;
    /* current is a kernel thread */
    if (oldmm == NULL) {
        return 0;
    }
    if (clone_flags & CLONE_VM) {
        mm = oldmm;
        goto good_mm;
    }
    int ret = -E_NO_MEM;
    if ((mm = mm_create()) == NULL) {
        goto bad_mm;
    }
    if (setup_pgdir(mm) != 0) {
        goto bad_pgdir_cleanup_mm;
    }
    lock_mm(oldmm);						//定義在vmm.h中,進(jìn)行上鎖
    {
        ret = dup_mmap(mm, oldmm);		//內(nèi)存資源的復(fù)制
    }
    unlock_mm(oldmm);
    if (ret != 0) {
        goto bad_dup_cleanup_mmap;
    }

good_mm:
    mm_count_inc(mm);
    proc->mm = mm;
    proc->cr3 = PADDR(mm->pgdir);
    return 0;
bad_dup_cleanup_mmap:
    exit_mmap(mm);
    put_pgdir(mm);
bad_pgdir_cleanup_mm:
    mm_destroy(mm);
bad_mm:
    return ret;
}
(2)dup_mmap

?dup_mmap中,通過(guò)遍歷mm_struct中的鏈表,給子進(jìn)程分配所有父進(jìn)程擁有的vma虛擬空間,將創(chuàng)建的vma插入mm中,并調(diào)用copy_range將父進(jìn)程vma中的數(shù)據(jù)復(fù)制到子進(jìn)程新創(chuàng)建的vma中。

//vmm.c中定義的dup_mmap
int
dup_mmap(struct mm_struct *to, struct mm_struct *from) {
    assert(to != NULL && from != NULL);
    list_entry_t *list = &(from->mmap_list), *le = list;
    while ((le = list_prev(le)) != list) {
        struct vma_struct *vma, *nvma;
        vma = le2vma(le, list_link);
        nvma = vma_create(vma->vm_start, vma->vm_end, vma->vm_flags);	//創(chuàng)建vma
        if (nvma == NULL) {
            return -E_NO_MEM;
        }
        insert_vma_struct(to, nvma);									//將新創(chuàng)建的vma插入mm
		//進(jìn)行復(fù)制
        bool share = 0;
        if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share) != 0) {
            return -E_NO_MEM;
        }
    }
    return 0;
}
(3)copy_range

?copy_range中會(huì)把父進(jìn)程的vma中的內(nèi)容復(fù)制給子進(jìn)程的vma。父進(jìn)程和子進(jìn)程的vma相同,但映射到的物理頁(yè)不同。首先找到父進(jìn)程的vma對(duì)應(yīng)的頁(yè)表項(xiàng),從該頁(yè)表項(xiàng)可以找到父進(jìn)程的vma對(duì)應(yīng)的物理頁(yè),然后為子進(jìn)程創(chuàng)建頁(yè)表項(xiàng)并分配新的一頁(yè),接下來(lái)將父進(jìn)程vma物理頁(yè)中的數(shù)據(jù)復(fù)制到子進(jìn)程新分配出的一頁(yè),再調(diào)用page_insert將子進(jìn)程的新頁(yè)的頁(yè)表項(xiàng)設(shè)置好,建立起虛擬地址到物理頁(yè)的映射,這個(gè)vma的復(fù)制工作就完成了。具體的數(shù)據(jù)復(fù)制使用memcpy函數(shù),需要傳入虛擬地址,但此時(shí)處于內(nèi)核態(tài),因此還要把物理頁(yè)的地址轉(zhuǎn)換為該頁(yè)對(duì)應(yīng)內(nèi)核的虛擬地址。這是練習(xí)二需要完成的部分,最終copy_range完整的實(shí)現(xiàn)如下:

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));
    //按頁(yè)復(fù)制
    do {
        //找到父進(jìn)程的頁(yè)表項(xiàng)
        pte_t *ptep = get_pte(from, start, 0), *nptep;
        if (ptep == NULL) {
            start = ROUNDDOWN(start + PTSIZE, PTSIZE);
            continue ;
        }
        //建立新進(jìn)程的頁(yè)表項(xiàng)
        if (*ptep & PTE_P) {
            if ((nptep = get_pte(to, start, 1)) == NULL) {
                return -E_NO_MEM;
            }
        	uint32_t perm = (*ptep & PTE_USER);
        	//獲取父進(jìn)程的物理頁(yè)
        	struct Page *page = pte2page(*ptep);
        	//為新進(jìn)程分配物理頁(yè)
        	struct Page *npage=alloc_page();
        	assert(page!=NULL);
        	assert(npage!=NULL);
     		int ret=0;
			//得到頁(yè)的內(nèi)核虛擬地址后使用memcopy復(fù)制
	 		void *src_kva=page2kva(page);
	 		void *dst_kva=page2kva(npage);			
	 		memcpy(dst_kva,src_kva,PGSIZE);
	 		ret = page_insert(to,npage,start,perm);
            assert(ret == 0);
        }
        start += PGSIZE;
    } while (start != 0 && start < end);
    return 0;
}
2.Copy-on-Write

?Copy on Write 是讀時(shí)共享,寫時(shí)復(fù)制機(jī)制。多個(gè)進(jìn)程可以讀同一部分?jǐn)?shù)據(jù),需要對(duì)數(shù)據(jù)進(jìn)行寫時(shí)再?gòu)?fù)制一份到自己的內(nèi)存空間。具體的實(shí)現(xiàn)為,在fork時(shí),直接將父進(jìn)程的地址空間即虛擬地址復(fù)制給子進(jìn)程,不分配實(shí)際的物理頁(yè)給子進(jìn)程,并將父進(jìn)程所有的頁(yè)都設(shè)置為只讀。父子進(jìn)程都可以讀取該頁(yè),當(dāng)父子進(jìn)程寫該頁(yè)時(shí),就會(huì)觸發(fā)頁(yè)訪問(wèn)異常,發(fā)生中斷,調(diào)用中斷服務(wù)例程,在中斷服務(wù)例程中,將觸發(fā)異常的虛擬地址所在的頁(yè)復(fù)制,分配新的一頁(yè)存放數(shù)據(jù),這樣父子進(jìn)程寫該部分?jǐn)?shù)據(jù)時(shí)就各自可以擁有一份自己的數(shù)據(jù)。

?大概的實(shí)現(xiàn)思路為:

  • 復(fù)制父進(jìn)程內(nèi)存時(shí)直接將父進(jìn)程的物理頁(yè)映射到子進(jìn)程的虛擬頁(yè),且父子進(jìn)程的該頁(yè)表項(xiàng)均修改為只讀。(修改copy_range)
  • 當(dāng)父子進(jìn)程需要寫時(shí),會(huì)觸發(fā)頁(yè)訪問(wèn)異常,在頁(yè)訪問(wèn)異常中進(jìn)行內(nèi)存頁(yè)的分配和復(fù)制(修改do_pgfault)

練習(xí)3:fork/exec/wait/exit 的實(shí)現(xiàn)及系統(tǒng)調(diào)用

1.系統(tǒng)調(diào)用

?用戶進(jìn)程在用戶態(tài)下運(yùn)行,不能執(zhí)行特權(quán)指令,如果需要執(zhí)行特權(quán)指令,只能通過(guò)系統(tǒng)調(diào)用切換到內(nèi)核態(tài),交給操作系統(tǒng)來(lái)完成。

? 用戶庫(kù)

?為了簡(jiǎn)化應(yīng)用程序進(jìn)行系統(tǒng)調(diào)用方式,用戶庫(kù)中提供了對(duì)系統(tǒng)調(diào)用的封裝。即只需要在程序中通過(guò)調(diào)用如exit,fork,wait等庫(kù)函數(shù),庫(kù)函數(shù)將進(jìn)行系統(tǒng)調(diào)用的發(fā)起。在ucore中,這部分封裝放在user/libs/ulib.c中

void exit(int error_code) {
    sys_exit(error_code);
    cprintf("BUG: exit failed.\n");
    while (1);
}

int fork(void) {
    return sys_fork();
}

int wait(void) {
    return sys_wait(0, NULL);
}
......

?最終這些庫(kù)函數(shù)都會(huì)調(diào)用syscall.c中的syscall,只是傳入的參數(shù)不同,在該函數(shù)中使用內(nèi)聯(lián)匯編直接發(fā)起中斷,中斷號(hào)為定義的T_SYSCALL(0x80),即系統(tǒng)調(diào)用為128號(hào)中斷。進(jìn)行中斷調(diào)用時(shí)會(huì)向eax寄存器傳入?yún)?shù),這個(gè)參數(shù)表示發(fā)生了具體哪個(gè)系統(tǒng)調(diào)用,同時(shí)還可以根據(jù)需要傳入最多5個(gè)參數(shù),分別傳入edx,ecx,ebx,edi和esi寄存器中。

libs/unistd.h:#define T_SYSCALL           0x80
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 ++) {
        a[i] = va_arg(ap, uint32_t);
    }
    va_end(ap);

    asm volatile (
        "int %1;"
        : "=a" (ret)
        : "i" (T_SYSCALL),
          "a" (num),
          "d" (a[0]),
          "c" (a[1]),
          "b" (a[2]),
          "D" (a[3]),
          "S" (a[4])
        : "cc", "memory");
    return ret;
}

int sys_exit(int error_code) {
    return syscall(SYS_exit, error_code);
}

? 系統(tǒng)中斷

?用戶態(tài)下發(fā)起中斷后,就可以跳轉(zhuǎn)執(zhí)行對(duì)應(yīng)的中斷服務(wù)例程,而中斷服務(wù)例程的地址保存在idt表中,idt表在內(nèi)核啟動(dòng)后的kern_init中調(diào)用idt_init進(jìn)行初始化,這是在lab1中已經(jīng)完成的。設(shè)置中斷服務(wù)例程時(shí),進(jìn)入中斷服務(wù)例程的特權(quán)級(jí)均設(shè)置為內(nèi)核特權(quán)級(jí),本實(shí)驗(yàn)中,系統(tǒng)調(diào)用由用戶發(fā)起,因此需要單獨(dú)設(shè)置中斷表idt中128號(hào)中斷描述符的特權(quán)級(jí)為用戶特權(quán)級(jí),這樣用戶就可以通過(guò)中斷提升特權(quán)級(jí),進(jìn)行系統(tǒng)調(diào)用。

void
idt_init(void) {
	extern uintptr_t __vectors[];
	int i = 0;
	for (i = 0; i < (sizeof(idt) / sizeof(struct gatedesc)); i++) {
		SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
	}
    //為T_SYSCALL設(shè)置用戶態(tài)權(quán)限,類型為系統(tǒng)調(diào)用
	SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
	lidt(&idt_pd);
}

?根據(jù)設(shè)置好的中斷描述符就可以切換特權(quán)級(jí),進(jìn)入系統(tǒng)調(diào)用對(duì)應(yīng)的中斷服務(wù)例程了。在進(jìn)行特權(quán)級(jí)切換及進(jìn)入中斷服務(wù)歷程之前,首先在__alltraps完成中斷幀trapframe的建立,在執(zhí)行完trap后會(huì)回到__alltraps,在__trapret中從中斷幀恢復(fù)寄存器的值,中斷返回。

.globl vector128
vector128:
  pushl $0
  pushl $128
  jmp __alltraps
//建立trapframe
.globl __alltraps
__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

?接下來(lái)將調(diào)用trap,并在trap中根據(jù)情況調(diào)用trap_dispatch,trap_dispatch中根據(jù)trapframe中的tf_trapno進(jìn)行相應(yīng)的處理,這個(gè)值是一開始就被壓入棧中的中斷號(hào)128,將調(diào)用syscall。

//trap_dispatch
switch (tf->tf_trapno) {
	...
    case T_SYSCALL:
        syscall();
        break;
    ...
}

?在syscall中,將根據(jù)發(fā)出中斷調(diào)用時(shí)傳入eax寄存器的值判斷系統(tǒng)調(diào)用具體類型,調(diào)用對(duì)應(yīng)的函數(shù)。

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

?ucore一共提供了以下這些系統(tǒng)調(diào)用:

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,
};

?而這些函數(shù)最終會(huì)調(diào)用do_fork,do_exit等函數(shù)完成需要完成的任務(wù),然后返回值存放在eax寄存器中,一路返回到__trapret,從中斷?;謴?fù)寄存器的值,回到用戶態(tài),中斷結(jié)束,繼續(xù)正常運(yùn)行進(jìn)程。

2.fork

?fork用于創(chuàng)建新的進(jìn)程。進(jìn)程調(diào)用fork函數(shù),將通過(guò)系統(tǒng)調(diào)用,創(chuàng)建一個(gè)與原進(jìn)程相同的進(jìn)程,該進(jìn)程與原進(jìn)程內(nèi)存相同,執(zhí)行相同的代碼,但有自己的地址空間。對(duì)于父進(jìn)程,fork返回子進(jìn)程的pid,創(chuàng)建出的子進(jìn)程從fork返回0。一次具體的fork調(diào)用從調(diào)用fork用戶庫(kù)函數(shù)開始,調(diào)用包裝好的fork。

int fork(void) {
    return sys_fork();
}

?包裝好的用戶庫(kù)函數(shù)將進(jìn)一步調(diào)用sys_fork,在sys_fork中將調(diào)用syscall,傳入SYS_fork,即系統(tǒng)調(diào)用類型:

int
sys_fork(void) {
    return syscall(SYS_fork);
}

?在syscall中將發(fā)起中斷,傳入相關(guān)參數(shù),通過(guò)int指令發(fā)起128號(hào)中斷。發(fā)生中斷首先進(jìn)行用戶棧到特權(quán)棧的切換,在__alltraps函數(shù)中建立trapframe,然后call trap,進(jìn)行中斷處理,中斷處理將使用中斷號(hào)128,從中斷表中進(jìn)入對(duì)應(yīng)的中斷服務(wù)例程即syscall系統(tǒng)調(diào)用,在syscall中,根據(jù)傳入的SYS_fork確定系統(tǒng)調(diào)用的具體類型,然后就將進(jìn)入對(duì)應(yīng)的系統(tǒng)調(diào)用函數(shù):

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,
};

?最終將進(jìn)入do_fork進(jìn)行進(jìn)程的復(fù)制,該函數(shù)在Lab4中已經(jīng)完成。該函數(shù)將創(chuàng)建一個(gè)新的進(jìn)程控制塊管理新的進(jìn)程,然后調(diào)用copy_mm,copy_thread等函數(shù)給新的進(jìn)程分配資源,并復(fù)制父進(jìn)程的內(nèi)存,在copy_thread中復(fù)制父進(jìn)程的上下文和中斷幀時(shí),設(shè)置中斷幀的eax值為0,這樣復(fù)制出的子進(jìn)程在將來(lái)返回時(shí)將返回0,且eip設(shè)置為forkret,調(diào)度運(yùn)行子進(jìn)程時(shí),會(huì)進(jìn)行上下文切換進(jìn)入forkret,然后從中斷幀恢復(fù)寄存器,返回0。

static void
copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) {
	proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; //內(nèi)核棧頂
	*(proc->tf) = *tf;
	proc->tf->tf_regs.reg_eax = 0; 				//子進(jìn)程返回0
	proc->tf->tf_esp = esp; 					//父進(jìn)程的用戶棧指針
	proc->tf->tf_eflags |= FL_IF; 				//設(shè)置能夠響應(yīng)中斷
	proc->context.eip = (uintptr_t)forkret; 	//返回
	proc->context.esp = (uintptr_t)(proc->tf); 	//trapframe
}

?而父進(jìn)程將返回子進(jìn)程的pid。

//do_fork返回
	...
	ret=proc->pid;
fork_out:
    return ret;
	...

?完成子進(jìn)程的創(chuàng)建工作之后,將從do_fork按調(diào)用順序返回至__trapret,從trapframe恢復(fù)狀態(tài),返回到用戶庫(kù)的syscall,最后返回用戶程序調(diào)用fork處繼續(xù)執(zhí)行下一條語(yǔ)句。此時(shí),父子進(jìn)程同時(shí)存在,此后如果發(fā)生調(diào)度,子進(jìn)程也將通過(guò)上下文切換,從forkret返回__trapret,最后返回到用戶程序的下一條語(yǔ)句。

?完整的一次fork調(diào)用的調(diào)用順序如下:

fork-->sys_fork-->syscall-->int 0x80發(fā)起128號(hào)中斷-->__alltraps-->trap_dispatch-->syscall-->sys_fork
-->do_fork

?完成調(diào)用后,父進(jìn)程狀態(tài)不變,子進(jìn)程創(chuàng)建成功,為可運(yùn)行狀態(tài),等待調(diào)度。

3.exec

?在本實(shí)驗(yàn)的ucore代碼中,沒(méi)有提供用戶庫(kù)包裝的exec,如果編寫類似fork的包裝,調(diào)用情況與fork是完全相同的。本實(shí)驗(yàn)中,直接在user_main中使用宏定義發(fā)起中斷,最終調(diào)用do_execve,將user_main替換為exit.c中的用戶程序,調(diào)用順序如下:

user_main-->KERNEL_EXECVE-->__KERNEL_EXECVE-->kernel_execve-->int 0x80發(fā)起128號(hào)中斷-->__alltraps-->trap_dispatch-->syscall-->sys_exec-->do_execve

?在do_execve調(diào)用的load_icode中,對(duì)中斷幀進(jìn)行了設(shè)置,將eip設(shè)置為了elf文件中給出的程序入口,即用戶程序的入口,_start。接下來(lái)將調(diào)用umain,從umain進(jìn)入exit.c的main中開始執(zhí)行程序。

//_start
.text
.globl _start
_start:
    # set ebp for backtrace
    movl $0x0, %ebp

    # move down the esp register
    # since it may cause page fault in backtrace
    subl $0x20, %esp

    # call user-program function
    call umain
//umain
int main(void);
void
umain(void) {
    int ret = main();
    exit(ret);
}

?通過(guò)exec(在本實(shí)驗(yàn)中是KERNEL_EXECVE),當(dāng)前正在執(zhí)行的進(jìn)程可以發(fā)起系統(tǒng)調(diào)用,然后通過(guò)do_execve,創(chuàng)建一個(gè)新的進(jìn)程,建立完全不同的地址空間,從elf文件中加載代碼和數(shù)據(jù)信息,進(jìn)行好加載工作后,設(shè)置中斷幀,使中斷返回時(shí)能返回到新的進(jìn)程的程序入口,這樣返回后就開始執(zhí)行新的程序。通過(guò)這一系列工作,這個(gè)新的程序就可以將原來(lái)的程序替換掉??雌饋?lái)進(jìn)程只是發(fā)生了一次系統(tǒng)調(diào)用,但系統(tǒng)調(diào)用結(jié)束后,進(jìn)程的地址空間,代碼,數(shù)據(jù)等已經(jīng)完全被替換掉,然后開始正常運(yùn)行新的程序。

4.wait

?wait函數(shù)用于讓當(dāng)前進(jìn)程等待他的子進(jìn)程結(jié)束。ucore提供了用戶庫(kù)包裝后的wait和waitpid,wait是使用默認(rèn)參數(shù)的waitpid,即等待任意進(jìn)程結(jié)束。這里對(duì)waitpid進(jìn)行分析,waitpid調(diào)用的過(guò)程與fork類似:

waitpid-->sys_wait-->syscall-->int 0x80發(fā)起128號(hào)中斷-->__alltraps-->trap_dispatch-->syscall-->sys_wait-->do_wait

?最終將調(diào)用系統(tǒng)調(diào)用函數(shù)do_wait,do_wait中會(huì)尋找是否有子進(jìn)程為僵尸態(tài)(PROC_ZOMBIE),如果沒(méi)有則將運(yùn)行schedule調(diào)度其他進(jìn)程運(yùn)行,當(dāng)前進(jìn)程睡眠(PROC_SLEEPING),當(dāng)有子進(jìn)程運(yùn)行結(jié)束轉(zhuǎn)變?yōu)榻┦瑧B(tài),這個(gè)進(jìn)程將被喚醒,從進(jìn)程鏈表刪除子進(jìn)程,并將子進(jìn)程的進(jìn)程控制塊也釋放,徹底結(jié)束子進(jìn)程,然后返回。傳入的參數(shù)為0則等待任意子進(jìn)程結(jié)束,否則等待指定的子進(jìn)程結(jié)束。

int
do_wait(int pid, int *code_store) {
    struct mm_struct *mm = current->mm;
    if (code_store != NULL) {							//存放導(dǎo)致子進(jìn)程退出的退出碼
        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的子進(jìn)程
        proc = find_proc(pid);								
        if (proc != NULL && proc->parent == current) {
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
                goto found;
            }
        }
    }
    else {
        proc = current->cptr;							//等待任意子進(jìn)程
        for (; proc != NULL; proc = proc->optr) {
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
                goto found;
            }
        }
    }
    if (haskid) {
        current->state = PROC_SLEEPING;					//進(jìn)入睡眠狀態(tài)
        current->wait_state = WT_CHILD;					//等待狀態(tài)-等待子進(jìn)程
        schedule();										//調(diào)度
        if (current->flags & PF_EXITING) {				//如果當(dāng)前進(jìn)程已經(jīng)結(jié)束,do_exit
            do_exit(-E_KILLED);
        }
        goto repeat;
    }
    return -E_BAD_PROC;

found:
    if (proc == idleproc || proc == initproc) {
        panic("wait idleproc or initproc.\n");			//不可以等待init_proc和idle_proc結(jié)束
    }
    if (code_store != NULL) {
        *code_store = proc->exit_code;
    }
    local_intr_save(intr_flag);
    {
        unhash_proc(proc);								//從進(jìn)程鏈表中刪除
        remove_links(proc);
    }
    local_intr_restore(intr_flag);
    put_kstack(proc);									//釋放內(nèi)核棧
    kfree(proc);										//釋放進(jìn)程塊
    return 0;
}

?調(diào)用waitpid,當(dāng)前進(jìn)程將等待子進(jìn)程運(yùn)行結(jié)束,未結(jié)束時(shí),當(dāng)前進(jìn)程將進(jìn)入睡眠狀態(tài),直到子進(jìn)程結(jié)束。等到了子進(jìn)程的結(jié)束,do_wait中會(huì)將子進(jìn)程從進(jìn)程鏈表刪除,讓子進(jìn)程徹底結(jié)束。

5.exit

?exit用于退出并結(jié)束當(dāng)前進(jìn)程,也已經(jīng)進(jìn)行了包裝,用戶程序可以直接調(diào)用。

void exit(int error_code) {
    sys_exit(error_code);
    cprintf("BUG: exit failed.\n");
    while (1);
}

?調(diào)用過(guò)程和fork,waitpid類似,最終調(diào)用do_exit。

exit-->sys_exit-->syscall-->int 0x80發(fā)起128號(hào)中斷-->__alltraps-->trap_dispatch-->syscall-->sys_exit-->do_exit

?在do_exit中,該進(jìn)程的內(nèi)存資源將被釋放,同時(shí)狀態(tài)將被設(shè)置為PROC_ZOMBIE,最后從進(jìn)程鏈表刪除該進(jìn)程由他的父進(jìn)程來(lái)完成,因此會(huì)判斷其父進(jìn)程是否在等待,如果等待則將父進(jìn)程喚醒。最后還要處理該進(jìn)程的子進(jìn)程,因?yàn)樗Y(jié)束后無(wú)法處理自己的子進(jìn)程,就遍歷鏈表將子進(jìn)程全部設(shè)置為init_proc的子進(jìn)程,讓init_proc完成PROC_ZOMBIE狀態(tài)的子進(jìn)程最后的處理。

int
do_exit(int error_code) {
    if (current == idleproc) {
        panic("idleproc exit.\n");
    }
    if (current == initproc) {
        panic("initproc exit.\n");
    }
    //釋放內(nèi)存
    struct mm_struct *mm = current->mm;
    if (mm != NULL) {									//mm==NULL是內(nèi)核線程
        lcr3(boot_cr3);
        if (mm_count_dec(mm) == 0) {
            exit_mmap(mm);								//取消映射
            put_pgdir(mm);								//刪除頁(yè)表
            mm_destroy(mm);								//刪除mm
        }
        current->mm = NULL;
    }
    current->state = PROC_ZOMBIE;						//PROC_ZOMBIE狀態(tài)
    current->exit_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);							//喚醒父進(jìn)程(父進(jìn)程等待)
        }
        while (current->cptr != NULL) {					//將退出進(jìn)程的子進(jìn)程的父進(jìn)程設(shè)置為init_proc
            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;
           	//如果子進(jìn)程已經(jīng)為PROC_ZOMBIE且init_proc在等待,喚醒init_proc
            if (proc->state == PROC_ZOMBIE) {			
                if (initproc->wait_state == WT_CHILD) {
                    wakeup_proc(initproc);
                }
            }
        }
    }
    local_intr_restore(intr_flag);
    
    schedule();											//調(diào)度其他進(jìn)程
    panic("do_exit will not return!! %d.\n", current->pid);
}

?綜上所述,調(diào)用exit會(huì)讓當(dāng)前進(jìn)程結(jié)束,釋放所有的內(nèi)存資源,但這個(gè)進(jìn)程將仍以PROC_ZOMBIE狀態(tài)存在,等待父進(jìn)程做最后的處理,并且該進(jìn)程結(jié)束前也會(huì)把自己的子進(jìn)程交給init_proc,確保自己的子進(jìn)程也可以最終被徹底結(jié)束,然后就調(diào)用schedule,調(diào)度運(yùn)行其他進(jìn)程。

6.用戶態(tài)進(jìn)程的生命周期

?用戶態(tài)進(jìn)程的生命周期可用下圖表示:

Ucore lab5

一個(gè)進(jìn)程首先由父進(jìn)程fork產(chǎn)生,狀態(tài)會(huì)由剛分配進(jìn)程控制塊的UNINIT狀態(tài)轉(zhuǎn)變?yōu)镽UNNABLE狀態(tài),為就緒狀態(tài)。當(dāng)發(fā)生調(diào)度選中次進(jìn)程時(shí),調(diào)度程序調(diào)用proc_run切換到該進(jìn)程,該進(jìn)程進(jìn)入運(yùn)行態(tài)。此后子進(jìn)程可以通過(guò)execve發(fā)起系統(tǒng)調(diào)用,將自己替換為用戶程序,但進(jìn)程狀態(tài)不會(huì)發(fā)生改變。父進(jìn)程可以通過(guò)wait發(fā)起系統(tǒng)調(diào)用,將自己轉(zhuǎn)變?yōu)镾LEEPING休眠態(tài),等待子進(jìn)程的完成。子進(jìn)程運(yùn)行結(jié)束后將變?yōu)閆OMBIE僵尸態(tài),而父進(jìn)程將被喚醒,進(jìn)行子進(jìn)程資源的回收。此后父進(jìn)程回到運(yùn)行態(tài)(或就緒態(tài)),而子進(jìn)程已徹底結(jié)束。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-431302.html

實(shí)驗(yàn)總結(jié)

重要知識(shí)點(diǎn)

  • 用戶進(jìn)程的創(chuàng)建過(guò)程
  • 加載用戶程序的過(guò)程
  • fork對(duì)父進(jìn)程內(nèi)存資源的復(fù)制
  • copy-on-write機(jī)制
  • 系統(tǒng)調(diào)用

到了這里,關(guān)于Ucore lab5的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 操作系統(tǒng)實(shí)驗(yàn) 2.3系統(tǒng)調(diào)用:linux-0.11-lab “為版本0內(nèi)核增加一個(gè)系統(tǒng)調(diào)用getjiffies” 和 “在用戶程序中使用新增的系統(tǒng)調(diào)用”

    操作系統(tǒng)實(shí)驗(yàn) 2.3系統(tǒng)調(diào)用:linux-0.11-lab “為版本0內(nèi)核增加一個(gè)系統(tǒng)調(diào)用getjiffies” 和 “在用戶程序中使用新增的系統(tǒng)調(diào)用”

    打開 vscode ,在如圖所示位置打開 ~/os/linux-0.11-lab/0 文件夾 1.定義getjiffies系統(tǒng)調(diào)用 題目中給的提示:進(jìn)入到 unistd.h 文件中 閱讀代碼,可以發(fā)現(xiàn)上圖劃線處有個(gè)系統(tǒng)調(diào)用名為 getpid :返回當(dāng)前進(jìn)程號(hào)——這與我們期望實(shí)現(xiàn)的功能類似:通過(guò)系統(tǒng)調(diào)用返回jiffies值。 于是此時(shí)希望

    2023年04月08日
    瀏覽(109)
  • powershell 接收一個(gè)端口tcp數(shù)據(jù)復(fù)制轉(zhuǎn)發(fā)到多個(gè)目的

    在 PowerShell 中,你可以使用? New-Object ?來(lái)創(chuàng)建? System.Net.Sockets.TcpListener ?和? System.Net.Sockets.TcpClient ?對(duì)象,從而接收一個(gè) TCP 端口的數(shù)據(jù)并將其轉(zhuǎn)發(fā)到多個(gè)目的地。下面是一個(gè) PowerShell 腳本示例,該腳本展示了如何從一個(gè) TCP 端口接收數(shù)據(jù)并將其復(fù)制到多個(gè)目標(biāo)地址: 在這個(gè)

    2024年02月21日
    瀏覽(15)
  • 全面了解文件上傳漏洞, 通關(guān)upload-labs靶場(chǎng)

    全面了解文件上傳漏洞, 通關(guān)upload-labs靶場(chǎng)

    upload-labs是一個(gè)專門用于學(xué)習(xí)文件上傳漏洞攻擊和防御的靶場(chǎng)。它提供了一系列模擬文件上傳漏洞的實(shí)驗(yàn)環(huán)境,用于幫助用戶了解文件上傳漏洞的原理和防御技術(shù)。 這個(gè)靶場(chǎng)包括了常見(jiàn)的文件上傳漏洞類型,如文件名欺騙、文件類型欺騙、文件上傳功能繞過(guò)等。通過(guò)練習(xí)不同

    2024年02月04日
    瀏覽(18)
  • 【信息安全】seed-labs實(shí)驗(yàn)-TCP/IP Attack Lab

    【信息安全】seed-labs實(shí)驗(yàn)-TCP/IP Attack Lab

    Install SEED VM on VirtualBox 上面完成了一臺(tái)虛擬機(jī)的基本配置,然后clone兩臺(tái)虛擬機(jī),和原來(lái)的虛擬機(jī)一起,分別是attacker、victim和observer。 attacker是發(fā)起攻擊的機(jī)器、victim是遭受攻擊的機(jī)器和observer是觀察用的機(jī)器,同時(shí)后面也將victim作為客戶端、observer作為服務(wù)器。 Oracle Virtua

    2024年02月02日
    瀏覽(22)
  • CSAPP Shell Lab 實(shí)驗(yàn)報(bào)告

    CSAPP Shell Lab 實(shí)驗(yàn)報(bào)告

    前言:強(qiáng)烈建議先看完csapp第八章再做此實(shí)驗(yàn),完整的tsh.c代碼貼在文章末尾了 進(jìn)程的概念、狀態(tài)以及控制進(jìn)程的幾個(gè)函數(shù)(fork,waitpid,execve)。 信號(hào)的概念,會(huì)編寫正確安全的信號(hào)處理程序。 shell的概念,理解shell程序是如何利用進(jìn)程管理和信號(hào)去執(zhí)行一個(gè)命令行語(yǔ)句。 sh

    2024年02月04日
    瀏覽(18)
  • H3C-Cloud Lab實(shí)驗(yàn)-鏈路聚合實(shí)驗(yàn)

    H3C-Cloud Lab實(shí)驗(yàn)-鏈路聚合實(shí)驗(yàn)

    實(shí)驗(yàn)拓?fù)鋱D: 實(shí)驗(yàn)需求: 1、按照?qǐng)D示配置PC3和PC4的IP地址 2、在SW1和SW2的兩條直連鏈路上配置鏈路聚合,實(shí)現(xiàn)鏈路冗余,并可以增加傳輸帶寬 3、SW1和SW2之間的直連鏈路要配置為Trunk類型,允許所有vlan通過(guò) 4、中斷SW1和SW2之間的一條直連鏈路,測(cè)試PC3和PC4是否仍然能夠繼續(xù)訪

    2024年02月16日
    瀏覽(21)
  • H3C-Cloud Lab實(shí)驗(yàn)-OSPF配置實(shí)驗(yàn)

    H3C-Cloud Lab實(shí)驗(yàn)-OSPF配置實(shí)驗(yàn)

    一、實(shí)驗(yàn)拓?fù)鋱D 實(shí)驗(yàn)需求: 1、按照?qǐng)D示配置 IP 地址 2、按照?qǐng)D示分區(qū)域配置 OSPF ,實(shí)現(xiàn)全網(wǎng)互通 3、為了路由結(jié)構(gòu)穩(wěn)定,要求路由器使用環(huán)回口作為 Router-id,ABR 的環(huán)回口宣告進(jìn)骨干區(qū)域 4、掌握OSPF初始化流程、路由表學(xué)習(xí)的過(guò)程 二、實(shí)驗(yàn)步驟 1)CRT連接設(shè)備 2)依次對(duì)5臺(tái)設(shè)

    2024年02月16日
    瀏覽(22)
  • 使用OSERDESE2原語(yǔ)實(shí)現(xiàn)多個(gè)dds合成一個(gè)波形,達(dá)到面積換速度的目的

    使用OSERDESE2原語(yǔ)實(shí)現(xiàn)多個(gè)dds合成一個(gè)波形,達(dá)到面積換速度的目的

    要實(shí)現(xiàn)一個(gè)高頻波形的數(shù)字呈現(xiàn)時(shí),可以將其拆分成4個(gè)甚至8個(gè)相同頻率不同初始相位的低頻波形,多個(gè)低頻dds生成的波形使用OSERDESE2原語(yǔ)合成最終的高頻波形,這樣占用了更多資源,但是降低了運(yùn)行速度。 如圖所示彩色的波形由四個(gè)不同顏色構(gòu)成,一共由36個(gè)點(diǎn)構(gòu)成一個(gè)完

    2024年01月18日
    瀏覽(20)
  • 啟動(dòng) AWS Academy Learner Lab【教學(xué)】(Hadoop實(shí)驗(yàn))

    啟動(dòng) AWS Academy Learner Lab【教學(xué)】(Hadoop實(shí)驗(yàn))

    ?? 博客主頁(yè) :?A_SHOWY ?? 系列專欄 :力扣刷題總結(jié)錄?數(shù)據(jù)結(jié)構(gòu)??云計(jì)算? ? ? ?首先,需要 創(chuàng)建3臺(tái)EC2, 一臺(tái)作主節(jié)點(diǎn) (master node),兩臺(tái)作從節(jié)點(diǎn) (slaves node)。 EC2 (彈性計(jì)算云):是AWS提供的最基本的云計(jì)算產(chǎn)品:虛擬專用服務(wù)器。這些“實(shí)例”可以運(yùn)行大多數(shù)操作系統(tǒng)

    2024年02月03日
    瀏覽(40)
  • 清華大學(xué)操作系統(tǒng)rCore實(shí)驗(yàn)-第零章-Lab環(huán)境搭建

    清華大學(xué)操作系統(tǒng)rCore實(shí)驗(yàn)-第零章-Lab環(huán)境搭建

    一直想動(dòng)手寫一個(gè)操作系統(tǒng),但是沒(méi)有能力從零開始寫,故跟著清華大學(xué)操作系統(tǒng)實(shí)驗(yàn),完成這個(gè)目標(biāo),這一篇文章開始,將進(jìn)行清華大學(xué)操作系統(tǒng) rCore 實(shí)驗(yàn)。本章是環(huán)境搭建,有一些個(gè)人特色,完成實(shí)驗(yàn)的基礎(chǔ)上,嘗試回答實(shí)驗(yàn)后的 編程題 和 問(wèn)答題 ,如有錯(cuò)誤,請(qǐng)大家

    2024年01月19日
    瀏覽(35)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包