操作系統(tǒng)實(shí)驗(yàn):Lab4 內(nèi)核線程管理

清華大學(xué)操作系統(tǒng)Lab4實(shí)驗(yàn)報(bào)告
課程主頁:http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring
實(shí)驗(yàn)指導(dǎo)書:https://chyyuu.gitbooks.io/ucore_os_docs/content/
github:https://github.com/chyyuu/ucore_os_lab

實(shí)驗(yàn)?zāi)康?/h3>
  • 了解內(nèi)核線程創(chuàng)建/執(zhí)行的管理過程
  • 了解內(nèi)核線程的切換和基本調(diào)度過程

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

實(shí)驗(yàn)二、三完成了無力和虛擬內(nèi)存管理,這給創(chuàng)建內(nèi)核線程(內(nèi)核線程是一種特殊的進(jìn)程)打下了提供內(nèi)存管理的基礎(chǔ)。當(dāng)一個(gè)程序加載到內(nèi)存中運(yùn)行時(shí),首先通過ucore OS的內(nèi)存管理子系統(tǒng)分配合適的空間,然后就需要考慮如何分時(shí)使用CPU來“并發(fā)”執(zhí)行多個(gè)程序,讓每個(gè)運(yùn)行的程序(這里用線程或進(jìn)程表示)“感到”它們各自擁有“自己”的CPU。
本次實(shí)驗(yàn)將首先接觸的是內(nèi)核線程的管理。內(nèi)核線程是一種特殊的進(jìn)程,內(nèi)核線程與用戶進(jìn)程的區(qū)別有兩個(gè):

  • 內(nèi)核線程只運(yùn)行在內(nèi)核態(tài)
  • 用戶進(jìn)程會(huì)在用戶態(tài)和內(nèi)核態(tài)交替運(yùn)行
  • 所有內(nèi)核線程共用ucore內(nèi)核內(nèi)存空間,不需為每個(gè)內(nèi)核線程維護(hù)單獨(dú)的內(nèi)存空間
  • 而用戶線程需要維護(hù)各自的用戶內(nèi)存空間

練習(xí)1:分配并初始化一個(gè)進(jìn)程控制塊

alloc_proc函數(shù)中,初始化進(jìn)程控制塊。

// alloc_proc - alloc a proc_struct and init all fields of proc_struct
static struct proc_struct *
alloc_proc(void) {
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
    //LAB4:EXERCISE1 2015011345
        proc -> state = PROC_UNINIT;
        proc -> pid = -1;
        proc -> cr3 = boot_cr3;  // 內(nèi)核線程,頁表使用boot_cr3
        proc -> mm = NULL;
        proc -> runs = 0;
        proc -> kstack = 0;
        proc -> need_resched = 0;
        proc -> parent = NULL;
        memset(&(proc -> context), 0, sizeof(struct context));
        proc -> tf = NULL;
        proc -> flags = 0;
        memset(proc -> name, 0, PROC_NAME_LEN);
    }
    return proc;
}
請(qǐng)說明proc_struct中struct context contextstruct trapframe *tf成員變量含義和在本實(shí)驗(yàn)中的作用。
  • proc_struct中的context:進(jìn)程的上下文,用于進(jìn)程切換(switch.S中)。在uCore中,所有的進(jìn)程在內(nèi)核中也是相對(duì)獨(dú)立的(例如獨(dú)立的內(nèi)核堆棧和上下文等等)。使用context保存寄存器的目的就在于在內(nèi)核態(tài)中能夠進(jìn)行上下文之間的切換。實(shí)際利用context進(jìn)行上下文切換的函數(shù)是在kern/process/switch.S中的switch_to。

  • proc_struct中的tf:中斷幀的指針??偸侵赶騼?nèi)核棧的某個(gè)位置:當(dāng)進(jìn)程從用戶空間跳到內(nèi)核空間時(shí),中斷幀記錄了進(jìn)程在被中斷前的狀態(tài)。當(dāng)內(nèi)核需要跳回用戶空間時(shí),需要調(diào)整中斷幀以恢復(fù)讓進(jìn)程繼續(xù)執(zhí)行的各寄存器值。除此之外,uCore內(nèi)核允許嵌套中斷。因此為了保證嵌套中斷發(fā)生時(shí)tf總是能夠指向當(dāng)前的trapframe,uCore在內(nèi)核棧上維護(hù)了tf的鏈,在trap.c::trap中。

練習(xí)2:為新創(chuàng)建的內(nèi)核線程分配資源

/* do_fork -     parent process for a new child process
 * @clone_flags: used to guide how to clone the child process
 * @stack:       the parent's user stack pointer. if stack==0, It means to fork a kernel thread.
 * @tf:          the trapframe info, which will be copied to child process's proc->tf
 */
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;

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

    bool intr_flag;
    local_intr_save(intr_flag);
    proc -> pid = get_pid();
//    5. insert proc_struct into hash_list && proc_list
    hash_proc(proc);
    list_add(&proc_list, &(proc -> list_link));
    nr_process++;
    local_intr_restore(intr_flag);
//    6. call wakeup_proc to make the new child process RUNNABLE
    wakeup_proc(proc);
//    7. set ret vaule using child proc's pid
    ret = proc -> pid;

fork_out:
    return ret;

bad_fork_cleanup_kstack:
    put_kstack(proc);
bad_fork_cleanup_proc:
    kfree(proc);
    goto fork_out;
}
請(qǐng)說明ucore是否做到給每個(gè)新fork的線程一個(gè)唯一的id?請(qǐng)說明你的分析和理由。

可以保證每個(gè)線程的id唯一。具體可見proc.c::get_pid函數(shù):

// get_pid - alloc a unique pid for process
static int
get_pid(void) {
    static_assert(MAX_PID > MAX_PROCESS);
    struct proc_struct *proc;
    list_entry_t *list = &proc_list, *le;
    static int next_safe = MAX_PID, last_pid = MAX_PID;
    if (++ last_pid >= MAX_PID) {
        last_pid = 1;
        goto inside;
    }
    if (last_pid >= next_safe) {
    inside:
        next_safe = MAX_PID;
    repeat:
        le = list;
        while ((le = list_next(le)) != list) {
            proc = le2proc(le, list_link);
            if (proc->pid == last_pid) {
                if (++ last_pid >= next_safe) {
                    if (last_pid >= MAX_PID) {
                        last_pid = 1;
                    }
                    next_safe = MAX_PID;
                    goto repeat;
                }
            }
            else if (proc->pid > last_pid && next_safe > proc->pid) {
                next_safe = proc->pid;
            }
        }
    }
    return last_pid;
}

首先,第一句assert可以保證進(jìn)程數(shù)一定不會(huì)多于可以分配的進(jìn)程標(biāo)識(shí)號(hào)的數(shù)目。
接下來,函數(shù)將掃描所有的進(jìn)程,找到一個(gè)當(dāng)前沒被使用的進(jìn)程號(hào),存儲(chǔ)在last_pid中,作為新進(jìn)程的進(jìn)程號(hào)。具體來說,循環(huán)掃描每一個(gè)當(dāng)前進(jìn)程:當(dāng)一個(gè)現(xiàn)有的進(jìn)程號(hào)和last_pid相等時(shí),則將last_pid+1;當(dāng)現(xiàn)有的進(jìn)程號(hào)大于last_pid時(shí),這意味著在已經(jīng)掃描的進(jìn)程中[last_pid, min(next_safe, proc->pid)]這段進(jìn)程號(hào)尚未被占用,繼續(xù)掃描。
這樣可以保證返回的新進(jìn)程號(hào)一定沒有被占用,即具有唯一的id。

練習(xí)3:閱讀代碼,理解proc_run函數(shù)和它調(diào)用的函數(shù)如何完成進(jìn)程切換的。

// proc_run - make process "proc" running on cpu
// NOTE: before call switch_to, should load  base addr of "proc"'s new PDT
void
proc_run(struct proc_struct *proc) {
// 如果要調(diào)度的進(jìn)程不是當(dāng)前進(jìn)程的話進(jìn)行如下操作
    if (proc != current) {
        bool intr_flag;
        struct proc_struct *prev = current, *next = proc;
// 關(guān)中斷,防止進(jìn)程調(diào)度過程中再發(fā)生其他中斷導(dǎo)致嵌套的進(jìn)程調(diào)度
        local_intr_save(intr_flag);
        {
// 當(dāng)前進(jìn)程設(shè)為待調(diào)度的進(jìn)程
            current = proc;
// 加載待調(diào)度進(jìn)程的內(nèi)核?;刂泛晚摫砘刂?            load_esp0(next->kstack + KSTACKSIZE);
            lcr3(next->cr3);
// 保存原線程的寄存器并恢復(fù)待調(diào)度線程的寄存器
            switch_to(&(prev->context), &(next->context));
        }
// 恢復(fù)中斷
        local_intr_restore(intr_flag);
    }
}

保存寄存器和恢復(fù)待調(diào)度進(jìn)程的寄存器部分代碼在switch_to中,如下:

switch_to:                      # switch_to(from, to)

    # save from's registers
    movl 4(%esp), %eax          # eax points to from
    popl 0(%eax)                # save eip !popl
    movl %esp, 4(%eax)          # save esp::context of from
    movl %ebx, 8(%eax)          # save ebx::context of from
    movl %ecx, 12(%eax)         # save ecx::context of from
    movl %edx, 16(%eax)         # save edx::context of from
    movl %esi, 20(%eax)         # save esi::context of from
    movl %edi, 24(%eax)         # save edi::context of from
    movl %ebp, 28(%eax)         # save ebp::context of from

    # restore to's registers
    movl 4(%esp), %eax          # not 8(%esp): popped return address already
                                # eax now points to to
    movl 28(%eax), %ebp         # restore ebp::context of to
    movl 24(%eax), %edi         # restore edi::context of to
    movl 20(%eax), %esi         # restore esi::context of to
    movl 16(%eax), %edx         # restore edx::context of to
    movl 12(%eax), %ecx         # restore ecx::context of to
    movl 8(%eax), %ebx          # restore ebx::context of to
    movl 4(%eax), %esp          # restore esp::context of to

    pushl 0(%eax)               # push eip

    ret
本實(shí)驗(yàn)在執(zhí)行過程中,創(chuàng)建且運(yùn)行了幾個(gè)內(nèi)核線程

共兩個(gè)線程:

  • idleproc:ucore的第一個(gè)內(nèi)核線程,完成內(nèi)核中各個(gè)子系統(tǒng)的初始化,之后立即調(diào)度,執(zhí)行其他進(jìn)程。
  • initproc:“hello world”線程。
語句 local_intr_save(intr_flag);....local_intr_restore(intr_flag);在這里有何作用?請(qǐng)說明理由。

在進(jìn)程調(diào)度開始前關(guān)中斷,在結(jié)束進(jìn)程調(diào)度后開中斷。這是為了防止在進(jìn)程調(diào)度過程中產(chǎn)生中斷導(dǎo)致進(jìn)程調(diào)度的嵌套。

覆蓋的知識(shí)點(diǎn)

  • 內(nèi)核線程的創(chuàng)建和切換。

與參考答案的區(qū)別

  • 練習(xí)1:只寫了一部分初始化,參考答案后發(fā)現(xiàn)沒有寫完整。
  • 練習(xí)2:沒有使用local_intr_save(intr_flag);....local_intr_restore(intr_flag);,后參考答案后不上。

總結(jié)

感謝期中考試讓我有機(jī)會(huì)從頭開始搞清楚了各種原理和ucore的前幾個(gè)lab的很多實(shí)現(xiàn)細(xì)節(jié)。
盡管這次在實(shí)驗(yàn)開始前觀看了mooc并認(rèn)真閱讀了實(shí)驗(yàn)指導(dǎo)書,但是在實(shí)現(xiàn)過程中還是有很多問題,有待進(jìn)一步研究。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 又來到了一個(gè)老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問題開始,來談?wù)劜?..
    tangsl閱讀 4,323評(píng)論 0 23
  • 操作系統(tǒng)概論 操作系統(tǒng)的概念 操作系統(tǒng)是指控制和管理計(jì)算機(jī)的軟硬件資源,并合理的組織調(diào)度計(jì)算機(jī)的工作和資源的分配,...
    野狗子嗷嗷嗷閱讀 12,480評(píng)論 3 34
  • 我們慢慢的學(xué)著懂事,慢慢的到了不惑之年。 開始有了壓力,有了追求,不管是物質(zhì)還是精神。 當(dāng)我們需要肩負(fù)責(zé)任時(shí),往往...
    斃考題閱讀 173評(píng)論 0 0
  • 猶記得那年, 在一個(gè)雨天, ...
    彳聿閱讀 766評(píng)論 1 3
  • 2018,你的微笑,讓人們贊口不絕,還是別人轉(zhuǎn)身就走。 牙齒不僅管著我們的吃喝 更是...
    讓文字溫暖心靈閱讀 154評(píng)論 0 0

友情鏈接更多精彩內(nèi)容