当一个进程终结时,内核必须释放掉它所占有的资源并把这一终结事件告知父进程。

进程的终结大部分都要靠 exit() 来完成的,最终的系统调用为 do_exit()。

asmlinkage long sys_exit(int error_code)
{
do_exit((error_code&0xff)<<8);
}

/当cpu进入到do_exit后,当前进程就会在中途寿终正寝,不会从这个函数中返回,当然也就不会从sys_exit
中返回,从而也就不会从系统调用exit()中返回
/
fastcall NORET_TYPE void do_exit(long code)
{

WARN_ON(atomic_read(&tsk->fs_excl));
/*由于中断服务程序根本不应该调用do_exit,不管是直接还是间接,所以首先通过in_interrupt进行加以检查
若发现是在某个中断服务程序中调用的,那就一定是出了问题*/
if (unlikely(in_interrupt()))panic("Aiee, killing interrupt handler!");
.../*current->flags的PF_EXITING标志表示进程正在被删除  */
if (unlikely(tsk->flags & PF_EXITING)) {printk(KERN_ALERT"Fixing recursive fault but reboot is needed!\n");...tsk->flags |= PF_EXITPIDONE; /*  设置进程标识为PF_EXITPIDONE*/if (tsk->io_context)exit_io_context();/*  设置进程状态为不可中断的等待状态 */set_current_state(TASK_UNINTERRUPTIBLE);/*  调度其它进程  */schedule();
}tsk->flags |= PF_EXITING;/*  内存屏障,用于确保在它以后的操做开始执行以前,它以前的操做已经完成  */
smp_mb();
spin_unlock_wait(&tsk->pi_lock);...//清除定时器
group_dead = atomic_dec_and_test(&tsk->signal->live);//live用来表示线程组中活动进程的数量
if (group_dead) { //当没有活动的进程时exit_child_reaper(tsk);//取消高精度定时器hrtimer_cancel(&tsk->signal->real_timer); //删除POSIX.1b类型的定时器exit_itimers(tsk->signal);
}
//收集进程会计信息
acct_collect(code, group_dead);...//设置终止代码
tsk->exit_code = code;
taskstats_exit(tsk, group_dead);
//释放线性区描述符和页表
exit_mm(tsk);if (group_dead)acct_process();
//遍历current->sysvsem.undo_list链表,并清除进程所涉及的每个IPC信号量的操作痕迹
exit_sem(tsk);
//释放文件对象相关资源
__exit_files(tsk);
//释放struct fs_struct结构体
__exit_fs(tsk);
//检查有多少未使用的进程内核栈
check_stack_usage();exit_thread();
cgroup_exit(tsk, 1);
exit_keys(tsk);if (group_dead && tsk->signal->leader)disassociate_ctty(1);module_put(task_thread_info(tsk)->exec_domain->module);
if (tsk->binfmt)module_put(tsk->binfmt->module);proc_exit_connector(tsk);
//给父进程发送信号,让其知道子进程生命已经结束,来料理子进程的后事. 同时把进程状态exit_state 设置成 EXIT_ZOMBIE
exit_notify(tsk);...tsk->flags |= PF_EXITPIDONE;...preempt_disable();
/* causes final put_task_struct in finish_task_switch(). */
tsk->state = TASK_DEAD;/*do_exit 不返回的真正原因在这里,由于进程状态设置成了EXIT_ZOMBIE,使得该进程永远不会再被选中进行调度,所以也就不会使用schedule()调度别的进程后从schedule中返回。因此只能等父进程收到子进程发送的信号来处理子进程,并将子进程的task_struct结构释放掉,子进程最终从系统中消失。而父进程在wait4(对应系统函数sys_wait4)中等待着。
*/
schedule();
BUG();
/* Avoid "noreturn function does return".  */
for (;;)cpu_relax();    /* For when BUG is null */

}

do_exit() 完成工作如下:
对该调用进行检查,比如该方法是不能在中断服务程序中调用的。
将 task_struct 中的标志成员设置为 PF_EXITING。
删除内核定时器,根据返回的结果,它确保没有定时器在排队,也没有定时器处理程序在运行。
把进程的退出代码 exit_code 设置为由 exit() 提供的退出代码,或者去完成任何其他由内核机制规定的退出动作。退出代码存放在这里供父进程随时检索。
调用 exit_mm( )释放进程占用的 mm_struct,若没有别的进程使用它们(也即是这个地址空间没有被共享),就彻底释放它们。
调用 exit_sem(),清除进程所涉及的每个IPC信号量的操作痕迹,使得若进程排队等候IPC信号,则离开队列。
调用 __exit_files、__exit_fs,分别递减文件描述符、文件系统数据的引用计数。若其中某个引用计数的数值降为零,那么就代表没有进程在使用相应的资源,此时就可以释放。
调用 exit_notify() 向父进程发送信号,给子进程重新找养父,养父为线程组中的其他线程或者init进程,并把进程状态(task_strcut 结构中的exit_state)设置成 EXIT_ZOMBIE。
调用 schedule() 切换到新的进程。由于处于 EXIT_ZOMBIE 状态的进程不会再会被调度,所以这是进程所执行的最后一段代码。do_exit 永不返回。

到此,与进程相关的所有资源该释放的都释放掉了(假设该进程是这些资源的唯一使用者)。进程不可运行(实际上它也没有地址空间可供它运行)并处于EXIT_ZOMBIE 退出状态。

该进程目前所占用的内存资源就是内核栈、thread_info 结构和 task_struct 结构。此时进程存在的唯一目的就是向它的父进程提供信息。父进程检索到信息后,或者通知内核它不关心那些无关的信息后,子进程的这些剩余资源才被释放归还给系统。

进程描述符的删除

从上面可以知道,进程在调用 do_exit() 后,进程处于僵死状态且不能运行。但是系统还保留它的进程描述符相关信息。之所以保留这些信息是为了让系统有办法在子进程终结后仍能获得它的信息。

当父进程获取已终结的子进程的信息后,或者通知内核它不关心那些无关的信息后,子进程的这些剩余资源才被释放归还给系统。

wait() 这一族函数都是通过唯一的一条系统调用 wait4() 来实现的。它的作用就是挂起调用它的进程,直到其中的一个子进程退出,此时函数会返回该子进程的 PID。另外,调用该函数时提供的指针会包含子函数退出时的退出代码。

wait4() 最终会调用 sys_wait4()。

asmlinkage long sys_wait4(pid_t pid, int __user *stat_addr,
int options, struct rusage __user *ru)
{
long ret;

if (options & ~(WNOHANG|WUNTRACED|WCONTINUED|__WNOTHREAD|__WCLONE|__WALL))return -EINVAL;ret = do_wait(pid, options | WEXITED, NULL, stat_addr, ru);/* avoid REGPARM breakage on x86: */
prevent_tail_call(ret);
return ret;

}

当父进程因子进程在 exit() 中向其发送信号而被唤醒,父进程在将子进程在用户空间运行的时间和系统空间运行的时间两项统计数据合并入其自身的统计数据中,然后,在典型的条件下,就会调用 release_task() 将子进程残存的资源,就是其 task_struct 结构和系统空间堆栈,全部释放掉。

调用过程如下:
sys_wait4
–> do_wait
–> wait_task_zombie
–> release_task

release_task() 实现如下:

void release_task(struct task_struct * p)
{
struct task_struct *leader;
int zap_leader;
repeat:

/* 1)该函数调用_unhash_process(),后者调用detach_pid()从pidhash* 上删除该进程,同时也要从任务列表中删除该进程* 2)释放目前僵死进程所使用的所有剩余资源,并进行最终统计和记录     */
__exit_signal(p);/** If we are the last non-leader member of the thread* group, and the leader is zombie, then notify the* group leader's parent process. (if it wants notification.)*/
zap_leader = 0;
leader = p->group_leader;
/*若该进程是线程组最后一个进程,并且领头进程已经死掉,,则通知僵死的领头进程的父进程 */
if (leader != p && thread_group_empty(leader) && leader->exit_state == EXIT_ZOMBIE) {BUG_ON(leader->exit_signal == -1);do_notify_parent(leader, leader->exit_signal);/** If we were the last child thread and the leader has* exited already, and the leader's parent ignores SIGCHLD,* then we are the one who should release the leader.** do_notify_parent() will have marked it self-reaping in* that case.*/zap_leader = (leader->exit_signal == -1);
}write_unlock_irq(&tasklist_lock);
release_thread(p);
//调用 put_task_struct 释放进程内核栈和thread_info结构所占的页,并释放task_struct 所占的slab告诉缓存。
call_rcu(&p->rcu, delayed_put_task_struct);p = leader;
if (unlikely(zap_leader))goto repeat;

}

release_task 完成的工作如下:

调用__exit_signal(),该函数调用_unhash_process(),后者调用detach_pid() 从 pidhash 上删除该进程,同时也要从任务列表中删除该进程。
__exit_signal() 释放目前僵死进程所使用的所有剩余资源,并进行最终统计和记录。
若该进程是线程组最后一个进程,并且领头进程已经死掉,则通知僵死的领头进程的父进程 。
调用 put_task_struct() 释放进程内核栈和 thread_info 结构所占的页,并释放 task_struct 所占的 slab 告诉缓存。

到此,进程描述符和进程所有独享的资源全部就释放掉了。

原文链接
Linux 进程管理之进程的终结

公众号 Linux码农 推荐阅读

Linux ‘网络配置’ 和 ‘故障排除’ 命令总结
Linux 进程管理之基础知识

你需要了解的55个网络概念
Centos7 开启 iptables 日志

memcache 多线程模型

linux ulimit 调优

Linux GDB的实现原理

欢迎关注公众号 Linux码农,获取更多干货

Linux 进程管理之进程的终结相关推荐

  1. linux ps 进程组,linux进程管理(2)---进程的组织结构

    一.目的 linux为了不同的进程管理目的,使用了不同的方法组织进程之间的关系,为了体现父子关系,使用了"树形"图:为了对同一信号量统一处理,使用了进程组:为了快速查找某个进程,使 ...

  2. Linux内核进程管理:进程的“内核栈”、current宏、进程描述符

    目录 linux 进程内核栈 概念 thread_info 有什么用? thread_info .内核栈.task_struct 关联 current 宏 1.arm 2.ARM64 3.x86 SY ...

  3. Linux进程管理:进程和线程基础知识

    <Linux进程管理:进程和线程基础知识> <Linux-进程管理> <C语言进程的内存地址空间分配> <进程和线程模型> <(1)Linux进程 ...

  4. [转]QNX进程管理器-进程调度策略

    如果你认为本系列文章对你有所帮助,请大家有钱的捧个钱场,点击此处赞助,赞助额0.1元起步,多少随意 声明:本文只用于个人学习交流,若不慎造成侵权,请及时联系我,立即予以改正 锋影 email:1741 ...

  5. 【k8s】理解Docker容器的进程管理(PID1进程(容器内kill命令无法杀死)、进程信号处理、僵尸进程)

    文章目录 概述 1. 容器的PID namespace(名空间) 2. 如何指明容器PID1进程 3. 进程信号处理 4. 孤儿进程与僵尸进程管理 5. 进程监控 6. 总结 参考 概述 简介: Do ...

  6. 广州大学2020操作系统实验一:进程管理与进程通信

    相关资料 广州大学2020操作系统实验一:进程管理与进程通信 广州大学2020操作系统实验二:银行家算法 广州大学2020操作系统实验三:内存管理 广州大学2020操作系统实验四:文件系统 广州大学2 ...

  7. Linux 内核进程管理之进程ID

    Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一.该数据结构 ...

  8. linux进程管理之进程创建

    所谓进程就是程序执行时的一个实例. 它是现代操作系统中一个很重要的抽象,我们从进程的生命周期:创建,执行,消亡来分析一下Linux上的进程管理实现. 一:前言 进程管理结构; 在内核中,每一个进程对应 ...

  9. 【Linux 内核】进程管理 ( 进程状态 | 进程创建 | 进程终止 | 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 )

    文章目录 一.进程状态 二.进程创建 三.进程终止 ( 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 ) 一.进程状态 Linux 进 ...

最新文章

  1. Oracle的介绍及其在安装和使用Oracle过程中可能遇到的困难及其相应的解决措施
  2. RxJava使用(一)基本使用
  3. POJ2527(两多项式取余)
  4. visual studio编译错误集(转)
  5. php调用无参数函数可以传入参数
  6. torch.mul() 和 torch.mm() 区别【矩阵a和b对应位相乘/矩阵相乘】
  7. ResultSetMetaData和ResultSet
  8. php pdo fetchassoc,pdo执行fetch查询语句,出现500错误,请问应该怎么写
  9. zabbix-2.0.8日常巡检-检测项目状态
  10. 驱动等待队列,poll和select编程
  11. Spring事务总结(一) 内部调用事务失效、异常回滚
  12. stm32PWM输入捕获模式详解
  13. 12.docker inspect
  14. 【转】对前端质量保障的思考 - Barret Lee
  15. cacheable注解原理_Cacheable注解使用详解
  16. typora生成目录
  17. 简约生活的72条观念
  18. DCloud与APICloud的对比选择
  19. Qt设置按钮背景图片,点击不显示背景
  20. js截取某个字段后面的字符串

热门文章

  1. 淘宝自动回复机器人配置手册——售前模板配置(上)
  2. 电子商务专业(技术方向)学习经验(忠告)
  3. java 打印日志log_java打印log日志
  4. c语言表示三个数除却最大最小,湖南师范大l历年年语言学及应用语言学现代汉语考研试题.doc...
  5. 用js限制网页只在微信浏览器中打开,复制粘贴即可使用
  6. java宝典_java宝典
  7. 腾讯云cos html,Docsify+腾讯云对象存储 COS,一键搭建云上静态博客
  8. 毕业论文之转化为三线表格(wps)
  9. cocos2d 高仿doodle jump 无源码
  10. SD高达G世纪DS的破解研究笔记