zoukankan      html  css  js  c++  java
  • linux进程解析--进程的退出及销毁

    一进程的退出:
    当一个进程运行完毕或者因为触发系统异常而退出时,最终会调用到内核中的函数do_exit(),在do_exit()函数中会清理一些进程使用的文件描述符,会释放掉进程用户态使用的相关的物理内存,清理页表,同时进程会调整其子进程的父子关系,会根据实际的情况向父进程发送SIG_CHLD信号。
    下面是经过简化的内核代码,去掉了一些不用太关注的东西。
    fastcall NORET_TYPE void do_exit(long code)
    {
    struct task_struct *tsk = current;
    int group_dead;


    //设置进程的状态为pf_exiting
    tsk->flags |= PF_EXITING;
    //从定时器队列中删除该进程
    del_timer_sync(&tsk->real_timer);
    //当前进程需要被trace相应的exit,将该进程的exit事件通过信号
    //发送给父进程,也就是trace 进程
    if (unlikely(current->ptrace & PT_TRACE_EXIT)) {
    current->ptrace_message = code;
    ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);
    }


    //下面的几个exit是将进程同各个描述符分离,主要有内存描述符,信号量,文件描述符,文件系统,命名空间,若相关描述符不再有任何进程使用,会释放掉,后面会分析一下__exit_mm()和__exit_files()函数
    __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);
    //exit_code中存放进程的退出码
    tsk->exit_code = code;
    //调整进程子进程的父子关系,向相关进程发出SIG_CHLD信号
    exit_notify(tsk);
    //进程处于zombie或者dead状态,在此调用schedule,该进程就永远回不来了^O^
    schedule();
    for (;;) ;
    }


    static inline void __exit_mm(struct task_struct * tsk)
    {
    struct mm_struct *mm = tsk->mm;
    //对于vfork来说,父进程会等待,直到子进程退出,在这里唤醒父进程
    mm_release(tsk, mm);
    if (!mm)
    return;
    /* more a memory barrier than a real lock */
    task_lock(tsk);
    //将进程的内存描述符设为空
    tsk->mm = NULL;
    up_read(&mm->mmap_sem);
    //使当前的cpu进入懒惰tlb模式
    enter_lazy_tlb(mm, current);
    task_unlock(tsk);
    //在这里真正的去释放内存描述符及相关所属资源
    mmput(mm);
    }
    void mmput(struct mm_struct *mm)
    {
    //在多线程的情况下,可能多个线程会共享同一个进程描述符,mm_users就是指明了有多少个线程正在使用该描述符
    if (atomic_dec_and_test(&mm->mm_users)) {
    exit_aio(mm);
    //没有进程使用该内存描述符了,应该可以释放掉该内存描述符所描述的一些进程用户态空间内存,并释放掉所有的vm_area_struct
    exit_mmap(mm);
    if (!list_empty(&mm->mmlist)) {
    spin_lock(&mmlist_lock);
    list_del(&mm->mmlist);
    spin_unlock(&mmlist_lock);
    }
    //释放掉交换标记
    put_swap_token(mm);
    //释放掉内存描述符和pgd表,释放掉内存描述符
    mmdrop(mm);
    }
    }


    static void exit_notify(struct task_struct *tsk)
    {
    int state;
    struct task_struct *t;
    struct list_head ptrace_dead, *_p, *_n;
    write_lock_irq(&tasklist_lock);


    INIT_LIST_HEAD(&ptrace_dead);
        //改变进程子进程的父子关系
    forget_original_parent(tsk, &ptrace_dead);


     
    t = tsk->real_parent;

    if ((process_group(t) != process_group(tsk)) &&
       (t->signal->session == tsk->signal->session) &&
       will_become_orphaned_pgrp(process_group(tsk), tsk) &&
       has_stopped_jobs(process_group(tsk))) {
    __kill_pg_info(SIGHUP, (void *)1, process_group(tsk));
    __kill_pg_info(SIGCONT, (void *)1, process_group(tsk));
    }
    if (tsk->exit_signal != SIGCHLD && tsk->exit_signal != -1 &&
       ( tsk->parent_exec_id != t->self_exec_id  ||
         tsk->self_exec_id != tsk->parent_exec_id)
       && !capable(CAP_KILL))
    tsk->exit_signal = SIGCHLD;


    //线程为组长线程且线程组已经空了,这个时候可以通知父进程sigchild消息了
    //exit_signal为-1只有在其为非组长线程的线程的情况下才发生
    if (tsk->exit_signal != -1 && thread_group_empty(tsk)) {
    int signal = tsk->parent == tsk->real_parent ? tsk->exit_signal : SIGCHLD;
    do_notify_parent(tsk, signal);
    } else if (tsk->ptrace) {
    do_notify_parent(tsk, SIGCHLD);
    }


    state = EXIT_ZOMBIE;
    //对于线程而言,线程若不为trace进程trace的话,可以直接
    //将exit_state置位exit_dead,对于单线程进程,exit_state为EXIT_DEAD
    if (tsk->exit_signal == -1 && tsk->ptrace == 0)
    state = EXIT_DEAD;
    tsk->exit_state = state;


    /*
    * Clear these here so that update_process_times() won't try to deliver
    * itimer, profile or rlimit signals to this task while it is in late exit.
    */
    tsk->it_virt_value = 0;
    tsk->it_prof_value = 0;


    write_unlock_irq(&tasklist_lock);


    list_for_each_safe(_p, _n, &ptrace_dead) {
    list_del_init(_p);
    t = list_entry(_p,struct task_struct,ptrace_list);
    release_task(t);
    }


    /* If the process is dead, release it - nobody will wait for it */
    //对于非组长线程的线程,对其进行清理,对于组长
    //线程的清理则是在发送了sig_chld信号后,由其父进程
    //进行清理
    if (state == EXIT_DEAD)
    release_task(tsk);


    /* PF_DEAD causes final put_task_struct after we schedule. */
    preempt_disable();
    tsk->flags |= PF_DEAD;
    }


    从exit_notify代码中,我们可以看出发送SIG_CHLD信号的条件:
    1对于单线程进程,当进程退出就发送sig_chld信号。
    2对多线程进程,当线程组无其他线程时,才会发送sig_chld信号,发送sig_chld信号主要是为了让父进程处理回收组长进程,普通的非组长线程自己会把自己清理掉的。
      2.1组长线程退出且线程组无其他线程
      2.2非组长线程退出且线程组无其他线程
    3进程被trace。
        线程组组长进程是最后一个被撤销处理掉的


    二进程的撤销:
    当进程终止运行后,一般会处于僵死状态,需要由父进程来执行wait操作来回收进程的进程描述符及内核栈所占内存,同时把僵死进程从进程相关的各个表上摘除下来。对应的处理函数是release_task(),该函数的处理过程是:
    1递减进程拥有者进程的个数, atomic_dec(&p->user->processes);
    2如果进程被跟踪,把它从调试程序的ptrace_children链表中删除。__ptrace_unlink(p);
    3调用__exit_signal()删除所有的挂起信号并释放signal_struct描述符,若没有其它轻量级进程使用该signal_struct的话,会删除该数据结构: __exit_signal(p);
    4调用__exit_sighand()删除信号处理函数: __exit_sighand(p);
    5调用__unhash_process(),该函数依次执行下列操作:
     a变量nr_threads减1.
     b两次调用detach_pid(),分别从PIDTYPE_PID和PIDTYPE_TGID类型的PID散列表中删除进程描述符。
     c如果进程是线程组的领头进程,那么调用两次detach_pid()从PIDTYPE_PGID, PIDTYPE_SID类型的散列表中删除该进程描述符。
     d用RMOVE_LINKS从进程链表中删除该进程。
    6如果进程不是线程组的领头进程,领头进程处于僵死状态,且该进程时线程组中的最后一个成员,该进程向领头进程的父进程发出一个信号,通知该进程已经死亡。
    7调用sched_exit()函数来调整父进程的时间片。
    8调用put_task_struct()递减进程描述符的引用计数,当计数器变为0时,则函数终止所有残留的对进程的引用.
      a递减进程所有者的user_struct 数据结构的使用计数,如果该引用计数变为0,释放该结构。
      b释放进程描述符以及thread_info描述符和内核态堆栈。

  • 相关阅读:
    第四章:文件stat获取函数
    第四章:文件的访问权限
    第三章:ioctl 函数详解
    第三章:fcntl 函数详解
    第四章:用户ID和组ID
    第四章:文件属性更改
    第三章:文件 I/O
    第四章:文件类型
    第二章:Unix的标准化及实现
    xml DOM解析
  • 原文地址:https://www.cnblogs.com/riskyer/p/3297107.html
Copyright © 2011-2022 走看看