草庐IT

Linux 进程管理之进程的终结

Linux码农 2023-03-28 原文
当一个进程终结时,内核必须释放掉它所占有的资源并把这一终结事件告知父进程。

进程的终结大部分都要靠 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 进程管理之进程的终结的更多相关文章

  1. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  3. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  4. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  5. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  6. ruby - 无法在 Ruby 中将 ffmpeg 作为子进程运行 - 2

    我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope

  7. ruby - (Ruby || Python) 窗口管理器 - 2

    我想用这两种语言中的任何一种(最好是ruby​​)制作一个窗口管理器。老实说,除了我需要加载某种X模块外,我不知道从哪里开始。因此,如果有人有线索,如果您能指出正确的方向,那就太好了。谢谢 最佳答案 XCB,X的下一代API使用XML格式定义X协议(protocol),并使用脚本生成特定语言绑定(bind)。它在概念上与SWIG类似,只是它描述的不是CAPI,而是X协议(protocol)。目前,C和Python存在绑定(bind)。理论上,Ruby端口只是编写一个从XML协议(protocol)定义语言到Ruby的翻译器的问题。生

  8. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

    我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

  9. ruby-on-rails - 事件管理员和自定义方法 - 2

    这是我在ActiveAdmin中的自定义页面ActiveAdmin.register_page"Settings"doaction_itemdolink_to('Importprojects','settings/importprojects')endcontentdopara"Text"endcontrollerdodefimportprojectssystem"rakedataspider:import_projects_ninja"para"OK"endendend我想做的是,当我单击“导入项目”按钮时,我想在Controller中执行rake任务。但是我无法访问该方法。可能是什

  10. ruby - 在 ruby​​ 中生成一个进程,捕获 stdout,stderr,获取退出状态 - 2

    我想从ruby​​rake脚本运行一个可执行文件,比如foo.exe我希望将foo.exe的STDOUT和STDERR输出直接写入我正在运行rake任务的控制台.当进程完成时,我想将退出代码捕获到一个变量中。我如何实现这一目标?我一直在玩backticks、process.spawn、system但我无法获得我想要的所有行为,只有部分更新:我在Windows上,在标准命令提示符下,而不是cygwin 最佳答案 system获取您想要的STDOUT行为。它还返回true作为零退出代码,这可能很有用。$?填充了有关最后一次system调

随机推荐