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

5
我在一本教科书中学习到,在进程变成僵尸进程后,Linux会保留进程描述符,直到未来的父进程验证退出状态。我了解到进程描述符包含两个结构体: 在slab中的task_struct和在内核堆栈中(忘了x86)的thread_info
我正在阅读源代码的do_exit()部分,位于<kernel/exit.c>,但不太清楚内核堆栈在哪里被释放。我发现exit_notify()将进程的状态更改为僵尸进程,其余的代码看起来主要是在清理锁等内容,直到schedule()
我似乎找不到释放内核堆栈的部分?还是我没有理解内核堆栈的工作原理? 或者说thread_info根本就不被考虑保留,并且在变成僵尸进程之前已经与内核堆栈一起丢弃了?
究竟是什么正在发生?
3个回答

1

经过一番搜索,我想我终于找到了它...

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()会释放内核栈。

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


0

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

  • 使用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()操作来递减参考计数器。


0

让我们深入了解任务结束:

有两个系统调用:exit_group()exit(),它们都将进入do_exit(),该函数将执行以下操作。

  • 设置PF_EXTING,表示任务正在删除
  • 通过del_timer_sync()从计时器中删除任务描述符
  • 调用exit_mm(),exit_sem(),__exit_fs()和其他函数来释放该任务的结构
  • 减少引用计数
  • exit_code设置为_exit()/exit_group()或错误代码
  • 调用exit_notify()
    • 更新与父进程和子进程的关系
    • 检查exit_signal,发送SIGCHLD
    • 如果任务未被跟踪或返回值为-1,则将退出状态设置为EXIT_DEAD,调用release_task()回收其他内存并减少引用计数。
    • 如果任务被跟踪,则将退出状态设置为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()以减少计数器,并释放内存和任务描述符

然后,我们全部释放。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接