第7章 进程退出与等待
基于 Linux 6.12.38 源码分析
7.1 进程退出 7.1.1 do_exit() 函数 位置: kernel/exit.c:do_exit()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 void __noreturn do_exit (long code) { struct task_struct *tsk = current; int group_dead; WARN_ON(irqs_disabled()); synchronize_group_exit(tsk, code); WARN_ON(tsk->plug); kcov_task_exit(tsk); kmsan_task_exit(tsk); coredump_task_exit(tsk); ptrace_event(PTRACE_EVENT_EXIT, code); user_events_exit(tsk); io_uring_files_cancel(); exit_signals(tsk); seccomp_filter_release(tsk); acct_update_integrals(tsk); group_dead = atomic_dec_and_test(&tsk->signal->live); if (group_dead) { if (unlikely(is_global_init(tsk))) panic("Attempted to kill init! exitcode=0x%08x\n" , tsk->signal->group_exit_code ?: (int )code); #ifdef CONFIG_POSIX_TIMERS hrtimer_cancel(&tsk->signal->real_timer); exit_itimers(tsk); #endif if (tsk->mm) setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm); } acct_collect(code, group_dead); if (group_dead) tty_audit_exit(); audit_free(tsk); tsk->exit_code = code; taskstats_exit(tsk, group_dead); perf_event_exit_task(tsk); exit_mm(); if (group_dead) acct_process(); trace_sched_process_exit(tsk); exit_sem(tsk); exit_shm(tsk); exit_files(tsk); exit_fs(tsk); if (group_dead) disassociate_ctty(1 ); exit_task_namespaces(tsk); exit_task_work(tsk); exit_thread(tsk); sched_autogroup_exit_task(tsk); cgroup_exit(tsk); flush_ptrace_hw_breakpoint(tsk); exit_tasks_rcu_start(); exit_notify(tsk, group_dead); proc_exit_connector(tsk); mpol_put_task_policy(tsk); #ifdef CONFIG_FUTEX if (unlikely(current->pi_state_cache)) kfree(current->pi_state_cache); #endif debug_check_no_locks_held(); if (tsk->io_context) exit_io_context(tsk); if (tsk->splice_pipe) free_pipe_info(tsk->splice_pipe); if (tsk->task_frag.page) put_page(tsk->task_frag.page); exit_task_stack_account(tsk); check_stack_usage(); preempt_disable(); if (tsk->nr_dirtied) __this_cpu_add(dirty_throttle_leaks, tsk->nr_dirtied); exit_rcu(); exit_tasks_rcu_finish(); lockdep_free_task(tsk); do_task_dead(); }
7.1.2 释放资源顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 do_exit() │ ├─→ synchronize_group_exit() 同步线程组退出 ├─→ kcov_task_exit() kcov 退出 ├─→ kmsan_task_exit() kmsan 退出 ├─→ coredump_task_exit() coredump 处理 ├─→ ptrace_event() ptrace 事件 ├─→ io_uring_files_cancel() io_uring 取消 │ ├─→ exit_signals() 设置 PF_EXITING,清理信号 ├─→ seccomp_filter_release() seccomp 释放 ├─→ acct_update_integrals() 计费更新 │ ├─→ perf_event_exit_task() 停止 perf 事件 ├─→ exit_mm() 释放地址空间 │ ├─→ exit_sem() 释放信号量 ├─→ exit_shm() 释放共享内存 ├─→ exit_files() 释放文件描述符 ├─→ exit_fs() 释放文件系统信息 │ ├─→ exit_task_namespaces() 释放命名空间 ├─→ exit_task_work() 释放 task_work ├─→ exit_thread() 清理线程相关资源 │ ├─→ sched_autogroup_exit_task() 调度自动组退出 ├─→ cgroup_exit() cgroup 退出 ├─→ flush_ptrace_hw_breakpoint() 刷新硬件断点 │ ├─→ exit_tasks_rcu_start() 开始 RCU 任务退出 ├─→ exit_notify() 通知父进程 │ ├─→ exit_io_context() 释放 IO 上下文 ├─→ free_pipe_info() 释放管道 ├─→ exit_task_stack_account() 退出栈统计 │ ├─→ exit_rcu() RCU 退出 ├─→ exit_tasks_rcu_finish() 完成 RCU 任务退出 │ └─→ do_task_dead() 最终调度,永不返回
7.1.3 exit_mm() 位置: kernel/exit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void exit_mm (struct task_struct *tsk) { struct mm_struct *mm = tsk->mm; mm_release(tsk, mm); if (!mm) return ; sync_mm_rss(mm); mmgrab(mm); tsk->mm = NULL ; tsk->active_mm = NULL ; mmdrop(mm); }
7.1.4 exit_notify() 位置: kernel/exit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void exit_notify (struct task_struct *tsk, int group_dead) { autoreap = do_notify_parent(tsk, tsk->exit_signal); if (autoreap) { tsk->exit_state = EXIT_DEAD; list_add(&tsk->ptrace_entry, &dead); } else { tsk->exit_state = EXIT_ZOMBIE; } forget_original_parent(tsk); wake_up_process(tsk->parent); }
7.2 僵尸进程与孤儿进程 7.2.1 僵尸进程 定义: 进程已退出,但其父进程尚未调用 wait() 系列函数回收,导致进程描述符仍残留在系统中。
特征:
进程状态为 EXIT_ZOMBIE
保留 PID、退出状态、运行时间等信息
不占用 CPU 和内存(大部分资源已释放)
显示为 Z 状态
1 2 3 4 5 ps aux | grep Z user 1234 0.0 0.0 0 ? Zs 10:00 0:00 [defunct]
7.2.2 孤儿进程 定义: 父进程先于子进程退出,子进程成为孤儿进程。
处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void forget_original_parent (struct task_struct *tsk) { struct task_struct *reaper ; reaper = find_child_reaper(tsk); if (reaper != tsk->parent) { tsk->real_parent = reaper; tsk->parent = reaper; } }
7.2.3 init 进程的特殊作用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int kernel_init (void *unused) { while (1 ) { wait(); schedule(); } return 0 ; }
7.3 wait 系列系统调用 7.3.1 系统调用列表
系统调用
描述
wait()
等待任意子进程
waitpid()
等待指定子进程
waitid()
等待子进程状态变化
wait3()
等待并获取资源使用
wait4()
等待指定子进程并获取资源
7.3.2 wait4() 实现 位置: kernel/exit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SYSCALL_DEFINE4(wait4, pid_t , upid, int __user *, stat_addr, int , options, struct rusage __user *, ru) { struct wait_opts wo = { .wo_type = PIDTYPE_PID, .wo_pid = upid, .wo_flags = options, .wo_info = NULL , .wo_stat = stat_addr, .wo_rusage = ru, }; return do_wait(&wo); }
7.3.3 do_wait() 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static long do_wait (struct wait_opts *wo) { struct task_struct *tsk = current; int retval; do { retval = do_wait_thread(wo, tsk); if (retval) return retval; if (!wo->wo_info) { set_current_state(TASK_INTERRUPTIBLE); if (!wo->wo_pid || has_child(tsk, wo->wo_pid, wo->wo_type)) schedule(); __set_current_state(TASK_RUNNING); } retval = -ERESTARTSYS; if (signal_pending(current)) break ; } while (1 ); return retval; }
7.4 等待队列优化 7.4.1 __wait_event() 宏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define __wait_event(wq_head, condition) \ do { \ DEFINE_WAIT(__wait); \ \ for (;;) { \ prepare_to_wait(&wq_head, &__wait, \ TASK_UNINTERRUPTIBLE); \ if (condition) \ break; \ schedule(); \ } \ finish_wait(&wq_head, &__wait); \ } while (0)
7.4.2 可中断等待 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define __wait_event_interruptible(wq_head, condition) \ ({ \ long __ret = 0; \ DEFINE_WAIT(__wait); \ \ for (;;) { \ prepare_to_wait(&wq_head, &__wait, \ TASK_INTERRUPTIBLE); \ if (condition) \ break; \ \ if (signal_pending(current)) { \ __ret = -ERESTARTSYS; \ break; \ } \ \ schedule(); \ } \ \ finish_wait(&wq_head, &__wait); \ __ret; \ })
7.5 退出码与信号 7.5.1 退出码处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void do_group_exit (int signal_code) { struct signal_struct *sig = current->signal; BUG_ON(signal_code != SIGINT && signal_code != SIGQUIT && signal_code != SIGKILL && signal_code != SIGTERM); current->exit_code = signal_code; do_exit(signal_code); } SYSCALL_DEFINE1(exit_group, int , error_code) { do_group_exit((error_code & 0xff ) << 8 ); return 0 ; }
7.5.2 退出信号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int signal_pending_for_child (int sig, struct task_struct *child, int *coredumped) { int exit_code; if (sig == -1 ) return SIGCHLD; if (!valid_signal(sig)) return SIGCHLD; if (sig == SIGQUIT || sig == SIGILL || sig == SIGTRAP || sig == SIGABRT || sig == SIGFPE || sig == SIGSEGV || sig == SIGBUS || sig == SIGSYS || sig == SIGXCPU || sig == SIGXFSZ) { *coredumped = 1 ; } return sig; }
7.6 release_task() 7.6.1 函数实现 位置: kernel/exit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void release_task (struct task_struct *p) { if (p->pid) free_pid(p->pid); if (p->nsproxy) put_nsproxy(p->nsproxy); if (p->signal) __put_task_struct(p->signal); if (p->sighand) __put_task_struct(p->sighand); __put_task_struct(p); }
7.6.2 task_struct 引用计数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __put_task_struct(struct task_struct *tsk){ if (refcount_dec_and_test(&tsk->usage)) { task_struct_free(tsk); } } void __get_task_struct(struct task_struct *tsk){ refcount_inc(&tsk->usage); }
7.7 /proc 文件系统中的退出信息 7.7.1 /proc/[pid]/status 1 2 3 4 $ cat /proc/1234/status Name: sleep State: Z (zombie) ...
7.7.2 /proc/[pid]/stat 1 2 3 4 5 6 7 $ cat /proc/1234/stat 1234 (sleep ) Z 5678 5678 0 0 ...
7.8 本章小结 本章介绍了 Linux 进程退出与等待机制:
**do_exit()**:进程退出的核心函数,按顺序释放所有资源
资源释放 :信号、内存、文件、证书等依次释放
僵尸进程 :已退出但未被 wait 的进程
孤儿进程 :父进程先退出,被 init 收养
wait 系列 :等待子进程退出的系统调用
等待队列 :高效的等待/唤醒机制
**release_task()**:最终释放进程描述符
下一章将介绍内核线程。