一进程的退出:

当一个进程运行完毕或者因为触发系统异常而退出时,最终会调用到内核中的函数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描述符和内核态堆栈。

linux进程解析--进程的退出及销毁的更多相关文章

  1. linux进程解析--进程的创建

    通常我们在代码中调用fork()来创建一个进程或者调用pthread_create()来创建一个线程,创建一个进程需要为其分配内存资源,文件资源,时间片资源等,在这里来描述一下linux进程的创建过程 ...

  2. linux进程解析--进程切换

    为了控制进程的执行,linux内核必须有能力挂起正在cpu上运行的进程,换入想要切换的进程,也就是恢复以前某个挂起的进程,这就是linux的进程切换.  1进程切换的时机 一般来说,进程切换都是发生在 ...

  3. [进程管理]Linux进程状态解析之T、Z、X

             Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态.          向进程发送一个SIGSTOP信号,它就会因响应该信号而进入 ...

  4. linux系统编程之进程(六):父进程查询子进程的退出,wait,waitpid

    本节目标: 僵进程 SIGCHLD wait waitpid 一,僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. ...

  5. Linux 进程--父进程查询子进程的退出状态

    僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它 ...

  6. Linux 线程与进程,以及通信

    http://blog.chinaunix.net/uid-25324849-id-3110075.html 部分转自:http://blog.chinaunix.net/uid-20620288-i ...

  7. linux 进程(二) --- 进程的创建及相关api

    一.进程的创建fork()函数  由fork创建的新进程被称为子进程(child process).该函数被调用一次,但返回两次.两次返回的区别是子进程的返回值是0,而父进程的返回值则是 新子进程的进 ...

  8. linux 中的进程wait()和waitpid函数,僵尸进程详解,以及利用这两个函数解决进程同步问题

    转载自:http://blog.sina.com.cn/s/blog_7776b9d3010144f9.html 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / wait ...

  9. (转)linux下进程的进程最大数、最大线程数、进程打开的文件数和ulimit命令修改硬件资源限制

    ulimit命令查看和更改系统限制 ulimit命令详解 ulimit用于shell启动进程所占用的资源,可以用来设置系统的限制 语法格式 ulimit [-acdfHlmnpsStvw] [size ...

随机推荐

  1. 使用JDBC进行数据库的事务操作(2)

    本篇将讲诉如何使用JDBC进行数据库有关事务的操作.在上一篇博客中已经介绍了事务的概念,和在MySQL命令行窗口进行开启事务,提交事务以及回滚事务的操作. 似乎事务和批处理都可以一次同时执行多条SQL ...

  2. HTTPS的学习

    HTTPS的学习总结   HTTPS学习总结 简述 HTTPS对比HTTP就多了一个安全层SSL/TLS,具体就是验证服务端的证书和对内容进行加密. 先来看看HTTP和HTTPS的区别 我用AFN访问 ...

  3. VC++ WIN32 sdk实现按钮自绘详解 之二(关键是BS_OWNERDRAW和WM_DRAWITEM)

    网上找了很多,可只是给出代码,没有详细解释,不便初学者理解.我就抄回冷饭.把这个再拿出来说说. 实例图片:    首先建立一个标准的Win32 Application 工程.选择a simple Wi ...

  4. 基于visual Studio2013解决面试题之0301累加

     题目

  5. 静态书架和js模拟翻书效果

    书籍图片随便找了个,有点难看,须要的自己替换个好看点的png格式图片 源代码下载:http://download.csdn.net/detail/sweetsuzyhyf/7604091

  6. Struts2通过自己定义拦截器实现登录之后跳转到原页面

    这个功能对用户体验来说是非常重要的.实现起来事实上非常easy. 拦截器的代码例如以下: package go.derek.advice; import go.derek.entity.User; i ...

  7. Swift - 数组排序方法(附样例)

    下面通过一个样例演示如何对数组元素进行排序.数组内为自定义用户对象,最终要实现按用户名排序,数据如下: 1 2 3 4 var userList = [UserInfo]() userList.app ...

  8. BDIA增强

    SE24     CL_EXITHANDLER的方法GET_INSTANCE中有基本上所有的增强都会走这边,打上断点查找增强名称,或者在程序中全局搜索GET_INSTANCE关键字 然后 SE19 下 ...

  9. 移动无边框窗体(设置标志位更流畅,或者发送WM_SYSCOMMAND和SC_MOVE + HTCAPTION消息)

    移动无边框窗体的代码网上很多,其原理都是一样的,但是是有问题的,我这里只是对其修正一下 网上的代码仅仅实现了两个事件 void EditDialog::mousePressEvent(QMouseEv ...

  10. C中程序的内存分配

    一.预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. ...