用户进程的退出和等待
退出
在进程执行完工作后,需要退出,释放资源,正我们在do_execve
函数末尾看到的一样,退出进程是调用do_exit
函数来实现的。
execve_exit:
do_exit(ret);
panic("already exit: %e.\n", ret);
// 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;
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);
}
如果是内核线程则不需要回收空间
如果是用户进程,就开始回收,首先执行
lcr3(boot_cr3);
切换到内核的页表上,这样用户进程就只能在内核的虚拟地址空间上执行,因为内核权限高。如果当前进程的被调用数减一后等于0,那么就没有其他进程在使用了,就可以进行回收,先回收内存资源,调用exit_mmap
函数释放mm
中的vma
描述的进程合法空间中实际分配的内存,然后把对应的页表项内容清空,最后把页表项和页目录表清空。然后调用put_pgdir
函数释放页目录表所占用的内存。最后调用mm_destroy
释放vma
与mm
的内存。把mm
置为NULL,表示与当前进程相关的用户虚拟内存空间和对应的内存管 理成员变量所占的内核虚拟内存空间已经回收完毕;设置进程的状态为
PROC_ZOMBIE
表示该进程要死了,等待父进程来回收资源,回收内核栈和进程控制块。当前进程的退出码为error_code
表示该进程已经不能被调度。如果当前进程的父进程处于等待子进程的状态,则唤醒父进程让父进程回收资源。
如果该进程还有子进程,那么就指向第一个孩子,把后面的孩子全部置为空,然后把孩子过继给内核线程
initproc
,把子进程插入到initproc
的孩子链表中,如果某个子进程的状态时要死的状态,并且initproc
的状态时等待孩子的状态,则唤醒initproc
来回收子进程的资源。然后开启中断,执行
schedule
函数,选择新的进程执行
等待
那么父进程如何完成对子进程的最后回收工作呢?这要求父进程要执行wait
用户函数或wait_pid
用户函数,这两个函数的区别是,wait
函数等待任意子进程的结束通知,而wait_pid
函数等待进程id号为pid的子进程结束通知。这两个函数最终访问sys_wait
系统调用接口让ucore来完成对子进程的最后回收工作,即回收子进程的内核栈和进程控制块所占内存空间,具体流程如下:
// 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) {
proc = find_proc(pid);
if (proc != NULL && proc->parent == current) {
haskid = 1;
if (proc->state == PROC_ZOMBIE) {
goto found;
}
}
}
else {
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) {
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;
}
首先进行检查
若
pid
等于0,就去找对应的孩子进程,否则就任意的一个快死的孩子进程。如果此子进程的执行状态不为PROC_ZOMBIE
,表明此子进程还没有退出,则当前进程只 好设置自己的执行状态为PROC_SLEEPING
,睡眠原因为WT_CHILD
(即等待子进程退 出),调用schedule()
函数选择新的进程执行,自己睡眠等待,如果被唤醒,则重复该步骤执行;如果此子进程的执行状态为
PROC_ZOMBIE
,表明此子进程处于退出状态,需要当前进程 (即子进程的父进程)完成对子进程的最终回收工作,即首先把子进程控制块从两个进程队列proc_list
和hash_list
中删除,并释放子进程的内核堆栈和进程控制块。自此,子进程才彻底 地结束了它的执行过程,消除了它所占用的所有资源。
最后更新于
这有帮助吗?