Linux 是否保留僵尸进程的内核堆栈?
Does Linux keep the kernel stack for zombie processes?
我在教科书中了解到,Linux 在进程变成僵尸时保留进程描述符,直到未来的父进程验证退出状态。我知道进程描述符有两个结构:slab 中的 task_struct
和内核堆栈中的 thread_info
(忘记 x86)。
我正在通读源代码的 do_exit()
部分,但我不太了解内核堆栈的释放位置。我可以发现 exit_notify()
将进程的状态更改为僵尸。其余代码看起来主要是清理锁和其他东西,直到 schedule()
.
我好像找不到解除分配内核堆栈的部分?还是我不了解内核堆栈的工作原理?
或者也许 thread_info
根本不被认为是保留的,并且在变成僵尸之前已经与内核堆栈一起被丢弃了?
到底是怎么回事?
进程内核堆栈分配有两种形式:
- 用CONFIG_VMAP_STACK,栈在VMA中分配
- 没有CONFIG_VMAP_STACK,栈分配在kmem
allocate/deallocate 进程内核堆栈的服务在 kernel/fork.c:
- alloc_thread_stack_node()
- free_thread_stack()
当CONFIG_VMAP_STACK时,实际上并没有释放一个栈,而是将其放入缓存中,以供后续过程重用(为了效率)。
进程的任务结构有一个引用计数器。一旦引用计数器下降到 0,任务结构和内核堆栈就会被释放。引用计数器递减 put_task_struct()。
让我们进入任务的结尾:
有两个系统调用:exit_group()
和exit()
,它们都会转到do_exit()
,它会做以下事情。
- 设置
PF_EXTING
表示任务正在删除
- 通过
del_timer_sync()
从计时器中删除任务描述符
- 调用
exit_mm(), exit_sem(), __exit_fs()
和其他人来释放该任务的结构
- 减少引用计数
- 将
exit_code
设置为_exit()/exit_group()
或错误
- 致电
exit_notify()
- 更新与 parent 和 child
的关系
- 检查
exit_signal
,发送SIGCHLD
- 如果任务未被跟踪或return值为-1,将exit_state设置为
EXIT_DEAD
,调用release_task()
回收其他内存并减少引用计数。
- 如果任务被跟踪,将exit_state设置为
EXIT_ZOMBIE
- 将任务标志设置为
PF_DEAD
- 致电
schedule()
我们需要僵尸状态因为parent可能需要使用那些文件描述符所以我们不能在第一时间删除所有的东西。 parent 任务将需要使用类似 wait()
的东西来检查 child 是否已死。在wait()
之后,release_task()
是僵尸完全释放的时间
- 减少业主的任务数量
- 如果任务被跟踪,从
ptrace_children
列表中删除
- 调用
__exit_signal()
删除所有未决信号并释放signal_struct描述符和exit_itimers()
删除所有定时器
- 调用
__exit_sighand()
删除信号处理程序
- 致电
__unhash_process()
nr_threads
--
- 调用
detach_pid()
从 PIDTYPE_PID
和 PIDTYPE_TGID
中删除任务描述符
- 调用
REMOVE_LINKS
从列表中删除任务
- 致电
sched_exit()
安排parent的时间段
- 调用
put_task-struct()
减少计数器,释放内存和任务描述符
然后,我们全部释放。
经过一番挖掘,我想我终于找到了...
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()
确实释放了内核堆栈。
所以,答案是肯定的。僵尸进程确实保留了内核堆栈。
我在教科书中了解到,Linux 在进程变成僵尸时保留进程描述符,直到未来的父进程验证退出状态。我知道进程描述符有两个结构:slab 中的 task_struct
和内核堆栈中的 thread_info
(忘记 x86)。
我正在通读源代码的 do_exit()
部分,但我不太了解内核堆栈的释放位置。我可以发现 exit_notify()
将进程的状态更改为僵尸。其余代码看起来主要是清理锁和其他东西,直到 schedule()
.
我好像找不到解除分配内核堆栈的部分?还是我不了解内核堆栈的工作原理?
或者也许 thread_info
根本不被认为是保留的,并且在变成僵尸之前已经与内核堆栈一起被丢弃了?
到底是怎么回事?
进程内核堆栈分配有两种形式:
- 用CONFIG_VMAP_STACK,栈在VMA中分配
- 没有CONFIG_VMAP_STACK,栈分配在kmem
allocate/deallocate 进程内核堆栈的服务在 kernel/fork.c:
- alloc_thread_stack_node()
- free_thread_stack()
当CONFIG_VMAP_STACK时,实际上并没有释放一个栈,而是将其放入缓存中,以供后续过程重用(为了效率)。
进程的任务结构有一个引用计数器。一旦引用计数器下降到 0,任务结构和内核堆栈就会被释放。引用计数器递减 put_task_struct()。
让我们进入任务的结尾:
有两个系统调用:exit_group()
和exit()
,它们都会转到do_exit()
,它会做以下事情。
- 设置
PF_EXTING
表示任务正在删除 - 通过
del_timer_sync()
从计时器中删除任务描述符
- 调用
exit_mm(), exit_sem(), __exit_fs()
和其他人来释放该任务的结构 - 减少引用计数
- 将
exit_code
设置为_exit()/exit_group()
或错误 - 致电
exit_notify()
- 更新与 parent 和 child 的关系
- 检查
exit_signal
,发送SIGCHLD
- 如果任务未被跟踪或return值为-1,将exit_state设置为
EXIT_DEAD
,调用release_task()
回收其他内存并减少引用计数。 - 如果任务被跟踪,将exit_state设置为
EXIT_ZOMBIE
- 将任务标志设置为
PF_DEAD
- 致电
schedule()
我们需要僵尸状态因为parent可能需要使用那些文件描述符所以我们不能在第一时间删除所有的东西。 parent 任务将需要使用类似 wait()
的东西来检查 child 是否已死。在wait()
之后,release_task()
- 减少业主的任务数量
- 如果任务被跟踪,从
ptrace_children
列表中删除 - 调用
__exit_signal()
删除所有未决信号并释放signal_struct描述符和exit_itimers()
删除所有定时器 - 调用
__exit_sighand()
删除信号处理程序 - 致电
__unhash_process()
nr_threads
--- 调用
detach_pid()
从PIDTYPE_PID
和PIDTYPE_TGID
中删除任务描述符
- 调用
REMOVE_LINKS
从列表中删除任务
- 致电
sched_exit()
安排parent的时间段 - 调用
put_task-struct()
减少计数器,释放内存和任务描述符
然后,我们全部释放。
经过一番挖掘,我想我终于找到了...
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()
确实释放了内核堆栈。
所以,答案是肯定的。僵尸进程确实保留了内核堆栈。