坚持就是胜利!
进程的退出exit
asmlinkage long sys_exit(int error_code) { do_exit((error_code&0xff)<<8); }
显然,主体是do_exit。
do_exit
当CPU进入do_exit以后,不会从这个函数返回。直接在内核态中退出了。 另外,只有进程和线程才有exit的概念。中断服务程序谈不上exit。 do_exit的大部分工作是清理和释放资源。 当进程把用户态的内存释放后进入了TASK_ZOMBIE状态,表示不能被调度了。 此时进程保留着最少的资源task_struct结构体和内核栈共计两个页面。 子进程并不会消除自己的task_struct,会通知父进程由父进程来读取退出码和清除。 为什么设计成父进程来清除呢? 一方面,父进程要读取退出码和一些统计信息。 另一方面,如果子进程自己清除了自己的task_struct,在调度到下一个进程之前,发生了中断,就没有内核栈来给中断运行了。
fastcall NORET_TYPE void do_exit(long code) { struct task_struct *tsk = current; int group_dead; profile_task_exit(tsk); if (unlikely(in_interrupt())) panic("Aiee, killing interrupt handler!"); if (unlikely(!tsk->pid)) panic("Attempted to kill the idle task!"); if (unlikely(tsk->pid == 1)) panic("Attempted to kill init!"); if (tsk->io_context) exit_io_context(); if (unlikely(current->ptrace & PT_TRACE_EXIT)) { current->ptrace_message = code; ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP); } tsk->flags |= PF_EXITING; del_timer_sync(&tsk->real_timer); if (unlikely(in_atomic())) printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n", current->comm, current->pid, preempt_count()); acct_update_integrals(); update_mem_hiwater(); group_dead = atomic_dec_and_test(&tsk->signal->live); if (group_dead) acct_process(code); exit_mm(tsk); exit_sem(tsk); __exit_files(tsk); __exit_fs(tsk); exit_namespace(tsk); exit_thread(); exit_keys(tsk); if (group_dead && tsk->signal->leader) disassociate_ctty(1); module_put(tsk->thread_info->exec_domain->module); if (tsk->binfmt) module_put(tsk->binfmt->module); tsk->exit_code = code; exit_notify(tsk); BUG_ON(!(current->flags & PF_DEAD)); schedule(); BUG(); /* Avoid "noreturn function does return". */ for (;;) ; }
1) if (unlikely(in_interrupt())) 检查是否处于中断服务程序。 如何检查是否正处于中断服务程序的调用中呢? #define in_interrupt() ({ int __cpu = smp_processor_id(); \ (local_irq_count(__cpu) + local_bh_count(__cpu) != 0); }) 每次进入中断服务调用irq_enter,会递增CPU数据local_irq_count[__cpu]。退出的时候irq_exit会递减。 bh的判断也是一样。 2) if (unlikely(!tsk->pid)) 0号idle进程不允许退出。 3) if (unlikely(tsk->pid == 1)) 1号init进程不允许退出。 4) del_timer_sync(&tsk->real_timer); 进程可能设置了实时定时器,也就是将tsk->real_timer挂入了内核的定时器队列。 这里需要脱队列。 5) 在fork中申请的资源都要进行释放。 exit_sem清理信号量。 6) tsk->exit_code = code; 设置退出码,等待父进程来读取。 7) exit_notify(tsk); 通知父进程。 8) schedule(); 主动触发一次调度,调度之后就会再返回到这个进程了,因为此时进程的状态被设置成了ZOMBIE
exit_notify
static void exit_notify(void) { struct task_struct * p, *t; forget_original_parent(current); t = current->p_pptr; if ((t->pgrp != current->pgrp) && (t->session == current->session) && will_become_orphaned_pgrp(current->pgrp, current) && has_stopped_jobs(current->pgrp)) { kill_pg(current->pgrp,SIGHUP,1); kill_pg(current->pgrp,SIGCONT,1); }
这块代码主要给父进程进程发送信号,并唤醒父进程。同时还涉及进程组,session,tty的操作。
父进程进程的wait4
asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru) { int flag, retval; DECLARE_WAITQUEUE(wait, current); struct task_struct *tsk; if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL)) return -EINVAL; add_wait_queue(¤t->wait_chldexit,&wait); repeat: flag = 0; current->state = TASK_INTERRUPTIBLE; read_lock(&tasklist_lock); tsk = current; do { struct task_struct *p; for (p = tsk->p_cptr ; p ; p = p->p_osptr) { if (pid>0) { if (p->pid != pid) continue; } else if (!pid) { if (p->pgrp != current->pgrp) continue; } else if (pid != -1) { if (p->pgrp != -pid) continue; } if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0)) && !(options & __WALL)) continue; flag = 1; switch (p->state) { case TASK_STOPPED: if (!p->exit_code) continue; if (!(options & WUNTRACED) && !(p->ptrace & PT_PTRACED)) continue; read_unlock(&tasklist_lock); retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0; if (!retval && stat_addr) retval = put_user((p->exit_code << 8) | 0x7f, stat_addr); if (!retval) { p->exit_code = 0; retval = p->pid; } goto end_wait4; case TASK_ZOMBIE: current->times.tms_cutime += p->times.tms_utime + p->times.tms_cutime; current->times.tms_cstime += p->times.tms_stime + p->times.tms_cstime; read_unlock(&tasklist_lock); retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0; if (!retval && stat_addr) retval = put_user(p->exit_code, stat_addr); if (retval) goto end_wait4; retval = p->pid; if (p->p_opptr != p->p_pptr) { write_lock_irq(&tasklist_lock); REMOVE_LINKS(p); p->p_pptr = p->p_opptr; SET_LINKS(p); do_notify_parent(p, SIGCHLD); write_unlock_irq(&tasklist_lock); } else release_task(p); goto end_wait4; default: continue; } } if (options & __WNOTHREAD) break; tsk = next_thread(tsk); } while (tsk != current); read_unlock(&tasklist_lock); if (flag) { retval = 0; if (options & WNOHANG) goto end_wait4; retval = -ERESTARTSYS; if (signal_pending(current)) goto end_wait4; schedule(); goto repeat; } retval = -ECHILD; end_wait4: current->state = TASK_RUNNING; remove_wait_queue(¤t->wait_chldexit,&wait); return retval; }
1) DECLARE_WAITQUEUE(wait, current); add_wait_queue(¤t->wait_chldexit,&wait); 在栈上申请一个waitqueue。 子进程在do_notify_parent会遍历wait_chldexit。 2) 依次当前进程的所有子进程,如果pid是想要找的pid, 并且该子进程的状态是 TASK_STOPPED, TASK_ZOMBIE,说明这个子进程就是wait4要找的子进程。 3) tsk = next_thread(tsk); 为什么要有next_thread呢?当前进程可能是一个线程,要遍历所有的线程组里的task_struct。 4) retval = put_user(p->exit_code, stat_addr); release_task(p); 找到了一个已经退出的子进程TASK_ZOMBIE,除了要累计子进程的运行时间,获取子进程的退出码。好要释放子进程残留的资源task_struct和内核栈。 5) schedule(); 如果子进程pid的状态不是TASK_STOPPED, TASK_ZOMBIE,则发生一次调度。并把进程状态改成TASK_INTERRUPTIBLE。
release_task释放子进程
static void release_task(struct task_struct * p) { if (p != current) { atomic_dec(&p->user->processes); free_uid(p->user); unhash_process(p); release_thread(p); current->cmin_flt += p->min_flt + p->cmin_flt; current->cmaj_flt += p->maj_flt + p->cmaj_flt; current->cnswap += p->nswap + p->cnswap; current->counter += p->counter; if (current->counter >= MAX_COUNTER) current->counter = MAX_COUNTER; free_task_struct(p); } else { printk("task releasing itself\n"); } }
1) free_task_struct(p); 终于把子进程的task_struct给释放了
如果父进程没有调用wait4,而子进程退出了怎么处理呢?
子进程退出一定会向父进程发送CHLD信号,父进程在进入内核态返回,或者父进程在运行过程中来了中断(时钟中断座位最后的保障), 从内核态返回时候,因为父进程有待处理的信号SIG_CHLD,会进入signal_return,然后调用
do_signal ka = ¤t->sig->action[signr-1]; if (ka->sa.sa_handler == SIG_IGN) { if (signr != SIGCHLD) continue; while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0) continue; }
如果当前进程收到了SIGCHLD信号,内核会在内核态主动调用sys_wait4