六: wait4 ()系統調用
在父進程中,用wait4()可以獲得子進程的退出狀態,並且防止在父進程退出前,子進程退出造成僵死 狀態。這是我們這節分析的最後一個小節了。
關於wait4()在使用者空間的調用方式可以自行參考相關資料,在這裡只是討論核心對這個系統調用的實 現過程。
Wait4()的系統調用入口為sys_wait4().代碼如下所示:
asmlinkage long sys_wait4(pid_t pid, int __user *stat_addr, int options, struct rusage __user *ru){ long ret; //options的標誌為須為WNOHANG…__WALL的組合,否則會出錯 //相關標誌的作用在do_wait()中再進行分析 if (options & ~(WNOHANG|WUNTRACED|WCONTINUED| __WNOTHREAD|__WCLONE|__WALL)) return -EINVAL; ret = do_wait(pid, options | WEXITED, NULL, stat_addr, ru); /* avoid REGPARM breakage on x86: */ prevent_tail_call(ret); return ret;}
do_wait()是其中的核心處理函數。代碼如下:
static long do_wait(pid_t pid, int options, struct siginfo __user *infop, int __user *stat_addr, struct rusage __user *ru){ //初始化一個等待隊列 DECLARE_WAITQUEUE(wait, current); struct task_struct *tsk; int flag, retval; int allowed, denied; //將當前進程加入等待隊列,子進程退出給父進程發送訊號會wake up些等待隊列 add_wait_queue(¤t->signal->wait_chldexit,&wait);repeat: flag = 0; allowed = denied = 0; //設定進程狀態為TASK_INTERRUPTIBLE.下次調度必須要等到子進程喚醒才可以了 current->state = TASK_INTERRUPTIBLE; read_lock(&tasklist_lock); tsk = current; do { struct task_struct *p; struct list_head *_p; int ret; //遍曆進程下的子進程 list_for_each(_p,&tsk->children) { p = list_entry(_p, struct task_struct, sibling); //判斷是否是我們要wait 的子進程 ret = eligible_child(pid, options, p); if (!ret) continue; if (unlikely(ret < 0)) { denied = ret; continue; } allowed = 1; switch (p->state) { //子進程為TASK_TRACED.即處於跟蹤狀態。則取子進程的相關資訊 case TASK_TRACED: flag = 1; //判斷是否是被父進程跟蹤的子進程 //如果是則返回1..不是返回0 if (!my_ptrace_child(p)) continue; /*FALLTHROUGH*/ case TASK_STOPPED: flag = 1; //WUNTRACED:子進程是停止的,也馬上返回 //沒有定義WUNTRACED 參數.繼續遍曆子進程 /*從此看出.生父進程是不會處理STOP狀態的子進程的.只有 發起跟蹤的進程才會 */ if (!(options & WUNTRACED) && !my_ptrace_child(p)) continue; //WNOWAIT:不會將zombie子進程的退出狀態撤銷 //下次調用wait系列函數的時候還可以繼續獲得這個退出狀態 retval = wait_task_stopped(p, ret == 2, (options & WNOWAIT), infop, stat_addr, ru); if (retval == -EAGAIN) goto repeat; if (retval != 0) /* He released the lock. */ goto end; break; default: // case EXIT_DEAD: //不需要處理DEAD狀態 if (p->exit_state == EXIT_DEAD) continue; // case EXIT_ZOMBIE: //子進程為殭屍狀態 if (p->exit_state == EXIT_ZOMBIE) { if (ret == 2) goto check_continued; if (!likely(options & WEXITED))continue; retval = wait_task_zombie( p, (options & WNOWAIT), infop, stat_addr, ru); /* He released the lock. */ if (retval != 0) goto end; break; }check_continued: /* * It's running now, so it might later * exit, stop, or stop and then continue. */ flag = 1; //WCONTINUED:報告任何繼續啟動並執行指定進程號的子進程的狀態 if (!unlikely(options & WCONTINUED)) continue; //取進程的相關狀態 retval = wait_task_continued( p, (options & WNOWAIT), infop, stat_addr, ru); if (retval != 0) /* He released the lock. */ goto end; break; } } //遍曆被跟蹤出去的子進程 //從這裡可以看出.如果一個子進程被跟蹤出去了.那麼子進程的退出 //操作並不是由生父進程進行了 if (!flag) { list_for_each(_p, &tsk->ptrace_children) { p = list_entry(_p, struct task_struct, ptrace_list); if (!eligible_child(pid, options, p)) continue; flag = 1; break; } } if (options & __WNOTHREAD) break; //也有可能是進程中的線程在wait其fork出來的子進程 tsk = next_thread(tsk); BUG_ON(tsk->signal != current->signal); } while (tsk != current); // read_unlock(&tasklist_lock); if (flag) { retval = 0; //如果定義了WHNOHANG:馬上退出 if (options & WNOHANG) goto end; retval = -ERESTARTSYS; if (signal_pending(current)) goto end; schedule(); goto repeat; } retval = -ECHILD; if (unlikely(denied) && !allowed) retval = denied;end: //將進程設為運行狀態,從等待隊列中移除 current->state = TASK_RUNNING; remove_wait_queue(¤t->signal->wait_chldexit,&wait); if (infop) { if (retval > 0) retval = 0; else { /* * For a WNOHANG return, clear out all the fields * we would set so the user can easily tell the * difference. */ if (!retval) retval = put_user(0, &infop->si_signo); if (!retval) retval = put_user(0, &infop->si_errno); if (!retval) retval = put_user(0, &infop->si_code); if (!retval) retval = put_user(0, &infop->si_pid); if (!retval) retval = put_user(0, &infop->si_uid); if (!retval) retval = put_user(0, &infop->si_status); } } return retval;}