Linux 是否为僵尸进程保留内核堆栈?

问题描述

我在一本教科书中了解到,当进程变成僵尸时,Linux 会保留进程描述符,直到未来的父进程验证退出状态。我知道进程描述符有两个结构:slab 中的 task_struct 和内核堆栈中的 thread_info(忘记 x86)。

我正在阅读源代码do_exit() 部分,但我不太明白内核堆栈的释放位置。我可以发现 exit_notify() 将进程的状态更改为僵尸。其余的代码看起来主要是清理锁和东西,直到 schedule()

我似乎找不到释放内核堆栈的部分?还是我不明白内核堆栈是如何工作的? 或者,也许 thread_info 根本不被认为是被保留的,并且在变成僵尸之前已经与内核堆栈一起被丢弃了?

到底发生了什么?

解决方法

经过一番挖掘,我想我终于找到了......

void free_task(struct task_struct *tsk)
{
    prop_local_destroy_single(&tsk->dirties);
    account_kernel_stack(tsk->stack,-1);
    free_thread_info(tsk->stack);
    rt_mutex_debug_task_free(tsk);
    ftrace_graph_exit_task(tsk);
    free_task_struct(tsk);
}

在父验证僵尸时,

put_task_struct()->__put_task_struct()->free_task() 确实释放了内核堆栈。

所以,答案是肯定的。僵尸进程确实保留了内核堆栈。

,

进程内核堆栈分配有两种形式:

  • 使用 CONFIG_VMAP_STACK,堆栈在 VMA 中分配
  • 没有 CONFIG_VMAP_STACK,堆栈在 kmem 中分配

分配/释放进程内核栈的服务在kernel/fork.c:

  • alloc_thread_stack_node()
  • free_thread_stack()

当CONFIG_VMAP_STACK时,栈实际上并没有被释放而是放入缓存中以供后续进程重用(为了效率)。

进程的任务结构有一个引用计数器。一旦引用计数器下降到 0,任务结构和内核堆栈就会被释放。引用计数器通过 put_task_struct() 递减。

,

让我们深入了解任务的结尾:

有两个系统调用:exit_group()exit(),它们都将转到 do_exit(),这将执行以下操作。

  • set PF_EXTING 表示正在删除任务
  • 通过del_timer_sync()从计时器中删除任务描述符
  • 调用 exit_mm(),exit_sem(),__exit_fs() 和其他人来发布该任务的结构
  • 减少引用计数
  • exit_code 设置为 _exit()/exit_group() 或错误
  • 致电exit_notify()
    • 更新与父母和孩子的关系
    • 检查exit_signal,发送SIGCHLD
    • 如果任务没有被追踪或返回值为-1,则将exit_state设置为EXIT_DEAD,调用release_task()来回收其他内存并减少引用计数。
    • 如果跟踪到任务,则将 exit_state 设置为 EXIT_ZOMBIE
    • 将任务标志设置为 PF_DEAD
  • 调用schedule()

我们需要僵尸状态,因为父级可能需要使用这些文件描述符,所以我们不能在第一时间删除所有的东西。父任务将需要使用类似 wait() 的东西来检查子任务是否已死。 wait()之后,僵尸到了release_task()

完全释放的时间
  • 减少所有者的任务数量
  • 如果任务被跟踪,从ptrace_children列表中删除
  • 调用__exit_signal()删除所有挂起的信号并释放signal_struct描述符并exit_itimers()删除所有定时器
  • 调用 __exit_sighand() 删除信号处理程序
  • 致电__unhash_process()
    • nr_threads--
    • 调用detach_pid()PIDTYPE_PIDPIDTYPE_TGID中删除任务描述符
    • 调用 REMOVE_LINKS 从列表中删除任务
  • 致电sched_exit()安排家长的时间
  • 调用 put_task-struct() 减少计数器,并释放内存和任务描述符

然后,我们将其全部发布。