Linux内核分析之进程管理-07

韩乔落

第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
/*
* 这是从 release() 调用的,没有返回值的进程退出函数。
* 此函数不会返回。
*/
void __noreturn do_exit(long code)
{
struct task_struct *tsk = current;
int group_dead;

WARN_ON(irqs_disabled());

// 1. 同步线程组退出
synchronize_group_exit(tsk, code);

WARN_ON(tsk->plug);

// 2. kcov/kmsan 退出
kcov_task_exit(tsk);
kmsan_task_exit(tsk);

// 3. coredump 处理
coredump_task_exit(tsk);
ptrace_event(PTRACE_EVENT_EXIT, code);
user_events_exit(tsk);

// 4. io_uring 取消
io_uring_files_cancel();

// 5. 设置 PF_EXITING 并退出信号
exit_signals(tsk); /* sets PF_EXITING */

// 6. seccomp 释放
seccomp_filter_release(tsk);

// 7. 计费更新
acct_update_integrals(tsk);
group_dead = atomic_dec_and_test(&tsk->signal->live);
if (group_dead) {
// 检查是否是 init 进程
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);

// 8. 设置退出码
tsk->exit_code = code;
taskstats_exit(tsk, group_dead);

/*
* 停止 perf 事件 (必须在 mm 释放前)
* 同时将继承的计数器刷新到父进程
*/
perf_event_exit_task(tsk);

// 9. 释放内存描述符
exit_mm();

if (group_dead)
acct_process();
trace_sched_process_exit(tsk);

// 10. 释放 IPC 资源
exit_sem(tsk);
exit_shm(tsk);

// 11. 释放文件描述符
exit_files(tsk);

// 12. 释放文件系统信息
exit_fs(tsk);
if (group_dead)
disassociate_ctty(1);
exit_task_namespaces(tsk);

// 13. 释放 task_work
exit_task_work(tsk);

// 14. 释放线程信息
exit_thread(tsk);

sched_autogroup_exit_task(tsk);
cgroup_exit(tsk);

// 15. 刷新 ptrace 硬件断点
flush_ptrace_hw_breakpoint(tsk);

// 16. 开始 RCU 任务退出
exit_tasks_rcu_start();

// 17. 通知父进程
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

// 18. 确保没有持有锁
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);

// 19. 最终调度,永不返回
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
mm_release(tsk, mm);
if (!mm)
return;

// 同步线程组
sync_mm_rss(mm);

// 去除与 mm 的关联
mmgrab(mm);
tsk->mm = NULL;
tsk->active_mm = NULL;

// 递减 mm 的引用计数
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)
{
// 1. 向父进程发送 SIGCHLD 信号
autoreap = do_notify_parent(tsk, tsk->exit_signal);

// 2. 如果可以自动回收
if (autoreap) {
tsk->exit_state = EXIT_DEAD;
list_add(&tsk->ptrace_entry, &dead);
} else {
// 3. 设置僵尸状态
tsk->exit_state = EXIT_ZOMBIE;
}

// 4. 如果成为孤儿,被 init 收养
forget_original_parent(tsk);

// 5. 唤醒可能的父进程
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
// kernel/exit.c
static void forget_original_parent(struct task_struct *tsk)
{
struct task_struct *reaper;

// 查找新的父进程 (通常是 init)
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
// init 进程会定期调用 wait() 回收孤儿进程
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
// kernel/exit.c
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
// include/linux/wait.h
#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
// include/linux/wait.h
#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
// kernel/exit.c
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);
}

// sys_exit_group 系统调用
SYSCALL_DEFINE1(exit_group, int, error_code)
{
do_group_exit((error_code & 0xff) << 8);
/* NOTREACHED */
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
// kernel/signal.c
/*
* 确定子进程退出时发送给父进程的信号
*/
static int signal_pending_for_child(int sig,
struct task_struct *child,
int *coredumped)
{
int exit_code;

// 默认是 SIGCHLD
if (sig == -1)
return SIGCHLD;

// 检查信号是否有效
if (!valid_signal(sig))
return SIGCHLD;

// 某些信号会生成 coredump
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
/*
* 释放进程描述符
* 这是在父进程 wait() 后调用的
*/
void release_task(struct task_struct *p)
{
// 1. 释放 PID
if (p->pid)
free_pid(p->pid);

// 2. 释放 namespace
if (p->nsproxy)
put_nsproxy(p->nsproxy);

// 3. 释放信号结构
if (p->signal)
__put_task_struct(p->signal);

// 4. 释放 sighand
if (p->sighand)
__put_task_struct(p->sighand);

// 5. 释放 task_struct
__put_task_struct(p);
}

7.6.2 task_struct 引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// kernel/fork.c
void __put_task_struct(struct task_struct *tsk)
{
// 递减引用计数
if (refcount_dec_and_test(&tsk->usage)) {
// 引用计数为 0,释放 task_struct
task_struct_free(tsk);
}
}

// 增加 task_struct 引用计数
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 ...

# 字段含义:
# 第3个字段: 状态 (Z = Zombie)
# 第4个字段: 父进程 PID
# 第5个字段: 进程组 ID

7.8 本章小结

本章介绍了 Linux 进程退出与等待机制:

  1. **do_exit()**:进程退出的核心函数,按顺序释放所有资源
  2. 资源释放:信号、内存、文件、证书等依次释放
  3. 僵尸进程:已退出但未被 wait 的进程
  4. 孤儿进程:父进程先退出,被 init 收养
  5. wait 系列:等待子进程退出的系统调用
  6. 等待队列:高效的等待/唤醒机制
  7. **release_task()**:最终释放进程描述符

下一章将介绍内核线程。

  • Title: Linux内核分析之进程管理-07
  • Author: 韩乔落
  • Created at : 2026-01-14 19:20:17
  • Updated at : 2026-01-19 13:40:40
  • Link: https://jelasin.github.io/2026/01/14/Linux内核分析之进程管理-07/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments