Does Linux keep the kernel stack for zombie processes?

Viewed 293

I learned in a textbook that Linux keeps the process descriptors when the process becomes a zombie until future parent verifies the exit status. I understand that process descriptor comes in two structs: task_struct in slab, and thread_info in kernel stack (forget x86).

I am reading through <kernel/exit.c> do_exit() part of the source code, but I don't quite get where kernel stack is deallocated. I can find that exit_notify() changes the process' status to zombie. and the rest of the code looks like its about cleaning up mostly locks and stuff until schedule().

I can't seem to find the part for deallocating kernel stack? or am I not understanding how kernel stacks work? Or perhaps thread_info is not considered to be kept at all and was already discarded along with kernel stack before changing into zombie?

What is exactly going on?

3 Answers

After a bit of digging, I think I have finally found it...

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);
}

on parent verifying the zombie,

put_task_struct()->__put_task_struct()->free_task() does free the kernel stack.

So, the answer is Yes. Zombie processes do keep the kernel stack.

Process kernel stack allocation comes in two flavors:

  • With CONFIG_VMAP_STACK, the stack is allocated in VMA
  • Without CONFIG_VMAP_STACK, the stack is allocated in kmem

The services to allocate/deallocate the process kernel stack are in kernel/fork.c:

  • alloc_thread_stack_node()
  • free_thread_stack()

When CONFIG_VMAP_STACK, a stack is actually not deallocated but put into a cache to be reused for a subsequent process (for the sake of efficiency).

The task struct of a process has a reference counter. The task struct along with the kernel stack are deallocated as soon as the reference counter drops to 0. The reference counter is decremented with put_task_struct().

Let's dive into the end of task :

there are two system calls : exit_group() and exit(), and all of them will go to do_exit(), which will do the following things.

  • set PF_EXTING which means the task is deleting
  • remove the task descriptor from timer by del_timer_sync()
  • call exit_mm(), exit_sem(), __exit_fs() and others to release structure of that task
  • decrease the ref count
  • set exit_code to _exit()/exit_group() or error
  • call exit_notify()
    • update relationship with parent and child
    • check exit_signal, send SIGCHLD
    • if task is not traced or return value is -1, set the exit_state to EXIT_DEAD, call release_task() to recycle other memory and decrease ref count.
    • if task is traced, set exit_state to EXIT_ZOMBIE
    • set task flag to PF_DEAD
  • call schedule()

We need zombie state cause the parent may need to use those file descriptors so we can not delete all the things in the first time. The parent task will need to use something like wait() to check if child is dead. After wait(), it is time for the zombie to release totally by release_task()

  • decrease the owners' task number
  • if the task is traced, delete from the ptrace_children list
  • call __exit_signal() delete all pending signals and release signal_struct descriptor and exit_itimers() delete all the timer
  • call __exit_sighand() delete signal handler
  • call __unhash_process()
    • nr_threads--
    • call detach_pid() to delete task descriptor from PIDTYPE_PID and PIDTYPE_TGID
    • call REMOVE_LINKS to delete the task from list
  • call sched_exit() to schedule parent's time pieces
  • call put_task-struct() to decrease the counter, and release memory & task descriptor

Then, we release it all.

Related