2.6.2 终止进程
系统调用kill(源文件“kernel/signal.c”)负责向线程组或者进程组发送信号,执行流程如图2.19所示。
图2.19 系统调用kill的执行流程
(1)如果参数pid大于0,那么调用函数kill_pid_info来向线程pid所属的线程组发送信号。
(2)如果参数pid等于0,那么向当前进程组发送信号。
(3)如果参数pid小于−1,那么向组长标识符为-pid的进程组发送信号。
(4)如果参数pid等于−1,那么向除了1号进程和当前线程组以外的所有线程组发送信号。
函数kill_pid_info负责向线程组发送信号,执行流程如图2.20所示,函数check_kill_permission检查当前进程是否有权限发送信号,函数__send_signal负责发送信号。
图2.20 向线程组发送信号的执行流程
函数__send_signal的主要代码如下:
kernel/signal.c
1 static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
2 int group, int from_ancestor_ns)
3 {
4 struct sigpending *pending;
5 struct sigqueue *q;
6 int override_rlimit;
7 int ret = 0, result;
8
9 …
10 result = TRACE_SIGNAL_IGNORED;
11 if (! prepare_signal(sig, t,
12 from_ancestor_ns || (info == SEND_SIG_FORCED)))
13 goto ret;
14
15 pending = group ? &t->signal->shared_pending : &t->pending;
16
17 result = TRACE_SIGNAL_ALREADY_PENDING;
18 if (legacy_queue(pending, sig))
19 goto ret;
20
21 …
22 if (sig < SIGRTMIN)
23 override_rlimit = (is_si_special(info) || info->si_code >= 0);
24 else
25 override_rlimit = 0;
26
27 q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
28 override_rlimit);
29 if (q) {
30 list_add_tail(&q->list, &pending->list);
31 …
32 } else if (! is_si_special(info)) {
33 …
34 }
35
36 out_set:
37 signalfd_notify(t, sig);
38 sigaddset(&pending->signal, sig);
39 complete_signal(sig, t, group);
40 ret:
41 …
42 return ret;
43 }
第11~13行代码,如果目标线程忽略信号,那么没必要发送信号。
第15行代码,确定把信号添加到哪个信号队列和集合。线程组有一个共享的信号队列和集合,每个线程有一个私有的信号队列和集合。如果向线程组发送信号,那么应该把信号添加到线程组共享的信号队列和集合中;如果向线程发送信号,那么应该把信号添加到线程私有的信号队列和集合中。
第18行代码,如果是传统信号,并且信号集合已经包含同一个信号,那么没必要重复发送信号。
第22~25行代码,判断分配信号队列节点时是否可以忽略信号队列长度的限制:对于传统信号,如果是特殊的信号信息,或者信号的编码大于0,那么允许忽略;如果是实时信号,那么不允许忽略。
第27行和第28行代码,分配一个信号队列节点。
第29行和第30行代码,如果分配信号队列节点成功,那么把它添加到信号队列中。
第37行代码,如果某个进程正在通过信号文件描述符(signalfd)监听信号,那么通知进程。signalfd是进程创建用来接收信号的文件描述符,进程可以使用select或poll监听信号文件描述符。
第38行代码,把信号添加到信号集合中。
第39行代码,调用函数complete_signal:如果向线程组发送信号,那么需要在线程组中查找一个没有屏蔽信号的线程,唤醒它,让它处理信号。
上一节已经介绍过,当线程准备从内核模式返回用户模式时,检查是否收到信号,如果收到信号,那么处理信号。