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描述符和内核态堆栈。
linux进程解析--进程的退出及销毁的更多相关文章
- linux进程解析--进程的创建
		通常我们在代码中调用fork()来创建一个进程或者调用pthread_create()来创建一个线程,创建一个进程需要为其分配内存资源,文件资源,时间片资源等,在这里来描述一下linux进程的创建过程 ... 
- linux进程解析--进程切换
		为了控制进程的执行,linux内核必须有能力挂起正在cpu上运行的进程,换入想要切换的进程,也就是恢复以前某个挂起的进程,这就是linux的进程切换. 1进程切换的时机 一般来说,进程切换都是发生在 ... 
- [进程管理]Linux进程状态解析之T、Z、X
		Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态. 向进程发送一个SIGSTOP信号,它就会因响应该信号而进入 ... 
- linux系统编程之进程(六):父进程查询子进程的退出,wait,waitpid
		本节目标: 僵进程 SIGCHLD wait waitpid 一,僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. ... 
- Linux 进程--父进程查询子进程的退出状态
		僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它 ... 
- Linux  线程与进程,以及通信
		http://blog.chinaunix.net/uid-25324849-id-3110075.html 部分转自:http://blog.chinaunix.net/uid-20620288-i ... 
- linux 进程(二) --- 进程的创建及相关api
		一.进程的创建fork()函数 由fork创建的新进程被称为子进程(child process).该函数被调用一次,但返回两次.两次返回的区别是子进程的返回值是0,而父进程的返回值则是 新子进程的进 ... 
- linux 中的进程wait()和waitpid函数,僵尸进程详解,以及利用这两个函数解决进程同步问题
		转载自:http://blog.sina.com.cn/s/blog_7776b9d3010144f9.html 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / wait ... 
- (转)linux下进程的进程最大数、最大线程数、进程打开的文件数和ulimit命令修改硬件资源限制
		ulimit命令查看和更改系统限制 ulimit命令详解 ulimit用于shell启动进程所占用的资源,可以用来设置系统的限制 语法格式 ulimit [-acdfHlmnpsStvw] [size ... 
随机推荐
- 浅谈MySQL 数据库性能优化
			MySQL数据库是 IO 密集型的程序,和其他数据库一样,主要功能就是数据的持久化以及数据的管理工作.本文侧重通过优化MySQL 数据库缓存参数如查询缓存,表缓存,日志缓存,索引缓存,innodb缓存 ... 
- Git Hub,eclipse  pull 出现问题
			一般在eclise里面使用geithub,之后会出现无法pull,或者pull 报错的问题.这里需要修改本地库的配置文件 The current branch is not configured fo ... 
- (WinForm)文件夹状态监控,最小化到托盘,开机自启动
			原文 (WinForm)文件夹状态监控,最小化到托盘,开机自启动 . 文件夾監控(監測文件夾中的文件動態): //MSDN上的例子 public class Watcher { public stat ... 
- Android 高手进阶,自己定义圆形进度条
			背景介绍 在Android 开发中,我们常常遇到各种各样绚丽的控件,所以,依靠我们Android本身所带的控件是远远不够的,许多时候须要我们自定义控件,在开发的过程中.我们公司遇到了一种须要自己写的一 ... 
- 微信公众平台应用开发框架sophia设计不足(1)
			设计一个小框架考虑的东西真不少,每一样都不easy: 1.既要解决当前技术的不足: 2.又要方便他人使用(基本的目的). 3.同一时候又要设计得优雅.easy扩展. sophia一開始设计用来支持智能 ... 
- Linux网络基础配置
			这是看itercast视频的笔记 Linux网络基础配置 以太网连接 在Linux中,以太网接口被命令为:eth0, eth1等, 0,1代表网卡编号 通过lspci命令可以查看网上硬件信息(如果是u ... 
- cocos2dX 事件之触摸事件和触摸事件集合
			今天, 我们来学习cocos2dX里面的触摸事件与触摸事件合集, 如今的手机游戏交互基本上都是通过触摸交互的, 所以大家明确这节的重要性了吧, 本节篇幅比較大, 所以我就不扯闲话了 先来看看经常使用函 ... 
- 跟我一起学extjs5(11--自己定义模块的设计)
			跟我一起学extjs5(11--自己定义模块的设计) 从这一节開始我们来设计并完毕一个自己定义模块.我们先来确定一个独立的模块的所能定义的一些模块信息. 下面信息仅仅是我自己在开发过程中 ... 
- [容斥原理] zoj 3556 How Many Sets I
			主题链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do? problemId=4535 How Many Sets I Time Limit: 2 ... 
- WM_ERASEBKGND官方解释(翻译),以及Delphi里所有的使用情况(就是绘制窗口控件背景色,并阻止进一步传递消息)
			#define WM_ERASEBKGND 0x0014 Parameters wParam A handle to the device context. // ... 
