實(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)程的生命周期可用下圖表示:
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-431302.html
一個(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)!