内核代码阅读(23) - 进程之exit和wait4

坚持就是胜利!

进程的退出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(&current->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(&current->wait_chldexit,&wait);
        return retval;
    }
1) DECLARE_WAITQUEUE(wait, current);
   add_wait_queue(&current->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 = &current->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
上一篇:aegis 启动流程


下一篇:erlang - 单元测试总结(一)