目录
(代码:linux 6.3.1,架构:arm64)
One look is worth a thousand words. —— Tess Flanders
一、信号发送简介
Linux中,信号(signal)是进程间通信的一种重要机制,用于通知进程、线程、进程组发生了某种事件。用户态程序可以通过多种方式向其他进程发送信号,以下是常见的几种方法:
1)kill 系统调用
kill 可以向指定的进程、进程组发送特定信号,函数原型如下:
#include <signal.h>
/*
* - pid:目标进程 ID,特殊值含义:
* pid > 0:发送信号给 ID 为 pid 的进程
* pid = 0:发送信号给与当前进程同组的所有进程
* pid = -1:发送信号给所有有权限发送的进程(除了 init 进程)
* pid < -1:发送信号给进程组 ID 为 |pid| 的所有进程
* - sig:要发送的信号编号(如 SIGTERM、SIGKILL 等)
*/
int kill(pid_t pid, int sig);
2)raise() 函数
raise()函数用于向当前进程发送信号,是kill(getpid(), sig)的简单封装,函数原型如下:
#include <signal.h>
int raise(int sig);
3)pthread_kill() 函数
多线程环境下,pthread_kill() 用于向进程内指定的线程发送信号,函数原型如下:
#include <signal.h>
#include <pthread.h>
int pthread_kill(pthread_t thread, int sig);
4)alarm() 函数
alarm() 用于在指定秒数后,向当前进程发送SIGALRM信号,函数原型如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
由此可见,信号的发送对象既可以是一个线程、也可以是一个进程、进程组,对于发送给指定线程的信号,信号的处理就是指定线程负责完成;
而针对发送给进程的信号,最终处理该信号的可能是进程中任意一个有能力处理的线程,具体由谁处理,是由内核发送信号的时候,就已经决定好了的!
二、实现原理
上一节介绍的各种信号发送的用户态接口,这些接口在内核中,最终对应的 “信号发送” 函数实际上都是同一个:send_signal_locked
以下就是信号发送最核心代码的完整逻辑:
信号发送的处理流程主要分两块内容:
1)检查信号的类型、信号是否被目标task屏蔽、信号是否为legacy信号(主要涉及 步骤1 ~ 步骤6);
2)将信号设置到对应的私有、共享信号pending队列中,并给选取到的task置位TIF_SIGPENDING代表有信号需要处理,然后唤醒该task(若该任务处于挂起状态)去处理信号(主要涉及 步骤7 ~ 步骤10)。
这里需要注意的是:信号的唯一处理时机是内核态返回用户态前夕,task的挂起只可能是在内核态,因此这里唤醒任务后,任务也是从内核态挂起的地方继续运行,直到其内核态处理完毕,并走到返回用户态前夕时,就会去处理pending的信号了!
一些特殊的信号处理:
1)SIGCONT、SIGSTOP类型信号
对于这类信号的处理,是在信号发送时,就直接在内核态中进行处理了(具体见步骤2、步骤3)
2)Fatal信号
对于fatal类型的信号,是需要整个进程下的所有线程都去立刻处理该信号的,因此会将进程中的所有线程都置位TIF_SIGPENDING、并设置相应信号pending位,然后唤醒所有线程去立刻处理Fatal信号。
三、代码实现
1)所有类型的信号发送在内核中的实现
/* 发送信号给进程、进程组 */
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
kill_something_info(sig, &info, pid)
if (pid > 0)
/* 发送给进程 */
kill_proc_info
kill_pid_info
group_send_sig_info
do_send_sig_info
send_signal_locked
if (pid != -1) {
/* 发送给进程组 */
__kill_pgrp_info
do_each_pid_task(pgrp, PIDTYPE_PGID, p) {
group_send_sig_info
do_send_sig_info
send_signal_locked
}
} else {
/* 发送给所有进程 */
for_each_process(p) {
group_send_sig_info(sig, info, p,PIDTYPE_MAX)
do_send_sig_info
send_signal_locked
}
}
/* 发送信号给指定线程 */
SYSCALL_DEFINE2(tkill, pid_t, pid, int, sig)
do_tkill
do_send_specific
do_send_sig_info
send_signal_locked
2)send_signal_locked 函数实现
send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type) {
bool force = false
/* Check whether signal must be sent ? */
...
return __send_signal_locked(sig, info, t, type, force) {
struct sigpending *pending
struct sigqueue *q
/* 1) 预处理信号, 若是STOP/CONTINUE类型的信号则先做一些处理, 若目标进程被置位SIGNAL_GROUP_EXIT则无需发送信号,
* 最后检查信号是否被目标进程ignore
*
* 处理影响进程级别的 stop/continue信号,与其他信号的action不同,如果是这两个信号的话,对应的sigaction会立即执行,
* 并且不论blocking, ignoring, handling.
* This does the actual continuing for SIGCONT, but not the actual stopping for stop signals.
* The process stop is done as a signal action for SIG_DFL.
*/
res = prepare_signal(sig, struct task_struct *p = t, force) {
struct signal_struct *signal = p->signal
/* 1.1) 若目标进程的signal->flags被置位SIGNAL_GROUP_EXIT, 说明其需要走退出流程, 无需再给它发送信号了, 信号发送提前结束 */
if (signal->flags & SIGNAL_GROUP_EXIT) {
if (signal->core_state)
return sig == SIGKILL
return false
} else if (sig_kernel_stop(sig)) {
/* 1.2) 若是STOP类型的信号, 则清空所有pending位图(进程共享/线程私有)中的SIGCONT信号位 */
siginitset(&flush, sigmask(SIGCONT))
flush_sigqueue_mask(&flush, &signal->shared_pending)
for_each_thread(p, t)
flush_sigqueue_mask(&flush, &t->pending)
} else if (sig == SIGCONT) {
/* 1.3) 若是CONTINUE类型的信号, 则清空所有pending位图中的STOP类型信号位, 并唤醒所有线程 */
siginitset(&flush, SIG_KERNEL_STOP_MASK)
flush_sigqueue_mask(&flush, &signal->shared_pending)
for_each_thread(p, t) {
flush_sigqueue_mask(&flush, &t->pending)
task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING)
if (likely(!(t->ptrace & PT_SEIZED))) {
t->jobctl &= ~JOBCTL_STOPPED
wake_up_state(t, __TASK_STOPPED)
} else
ptrace_trap_notify(t)
}
}
/* 1.4) 检查发送的信号是否被目标进程忽略(如: 其handler为SIG_IGN等) */
is_ignored = sig_ignored(p, sig, force) {
if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))
return false
if (t->ptrace && sig != SIGKILL)
return false
return sig_task_ignored(t, sig, force) {
void __user *handler = sig_handler(t, sig)
return t->sighand->action[sig - 1].sa.sa_handler
return sig_handler_ignored(handler, sig) {
/* Is it explicitly or implicitly ignored? */
return handler == SIG_IGN || (handler == SIG_DFL && sig_kernel_ignore(sig))
}//sig_handler_ignored
}//sig_task_ignored
}//sig_ignored
return !is_ignored
}//prepare_signal
if(!res)
goto ret
/* 2) 根据信号发送类型, 判断信号是发送给进程还是线程的, 获取对应的pending信号队列 */
pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending
/* 3) 若是legacy类型信号, 且信号已经被设置到pending队列中, 则提前结束信号发送 */
is_iegacy = legacy_queue(pending, sig) {
return (sig < SIGRTMIN) && sigismember(&signals->signal, sig) // SIGRTMIN: 32
}//legacy_queue
if (is_iegacy)
goto ret
/* 4) 若是给内核线程发送一个SIGKILL信号, 则跳过siginfo allocation过程 */
if ((sig == SIGKILL) || (t->flags & PF_KTHREAD))
goto out_set
/* 5) 若当前没有超过实时信号的MAX, 则分配一个sigqueue对象 */
q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit, 0) {
struct sigqueue *q = NULL
rcu_read_lock()
struct ucounts *ucounts = task_ucounts(t)
sigpending = inc_rlimit_get_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING)
rcu_read_unlock()
if (!sigpending)
return NULL
if (override_rlimit || likely(sigpending <= task_rlimit(t, RLIMIT_SIGPENDING))) {
q = kmem_cache_alloc(sigqueue_cachep, gfp_flags)
}
return q
}//__sigqueue_alloc
if (q) {
/* 6) 将sigqueue对象添加到对应的pending队列中, 并设置sigqueue->info字段内容 */
list_add_tail(&q->list, &pending->list)
switch ((unsigned long) info) {
case (unsigned long) SEND_SIG_NOINFO:
clear_siginfo(&q->info)
q->info.si_signo = sig
q->info.si_errno = 0
q->info.si_code = SI_USER
q->info.si_pid = task_tgid_nr_ns(current, task_active_pid_ns(t))
q->info.si_uid = from_kuid_munged(task_cred_xxx(t, user_ns), current_uid())
break
case (unsigned long) SEND_SIG_PRIV:
clear_siginfo(&q->info)
q->info.si_signo = sig
q->info.si_errno = 0
q->info.si_code = SI_KERNEL
q->info.si_pid = 0
q->info.si_uid = 0
break
default:
copy_siginfo(&q->info, info)
break
}
}//if (q)
out_set:
signalfd_notify(t, sig) {
if (unlikely(waitqueue_active(&tsk->sighand->signalfd_wqh)))
wake_up(&tsk->sighand->signalfd_wqh)
}
/* 7) 将信号添加到对应的pending位图中 */
sigaddset(&pending->signal, sig)
/* 8) 在目标进程中搜寻并唤醒一个可以处理该信号的线程, 同时置位TIF_SIGPENDING, 若是致命信号则将所有线程都置位SIGKILL, 并全部唤醒 */
complete_signal(sig, struct task_struct *p = t, type) {
struct signal_struct *signal = p->signal
struct task_struct *t
/* 8.1) 判断目标任务是否能够处理该信号 */
ret = wants_signal(sig, p) {
if (sigismember(&p->blocked, sig)) /* 目标任务屏蔽了该信号 */
return false
if (p->flags & PF_EXITING) /* 目标任务正在执行退出流程 */
return false
if (sig == SIGKILL) /* 信号为SIGKILL */
return true
if (task_is_stopped_or_traced(p))
return false
return task_curr(p) || !task_sigpending(p)
}//wants_signal
/* 8.2) 若目标任务不能处理该信号, 则搜寻进程中其他线程是否有能够处理该信号的 */
if (ret) {
t = p
} else if ((type == PIDTYPE_PID) || thread_group_empty(p)) {
return
} else {
t = signal->curr_target
while (!wants_signal(sig, t)) {
t = next_thread(t)
if (t == signal->curr_target)
return
}
signal->curr_target = t
}
/* 8.3) 若信号是致命信号, 则将进程中所有线程都置位SIGKILL信号, 并将所有线程唤醒 */
if (sig_fatal(p, sig) && (signal->core_state || !(signal->flags & SIGNAL_GROUP_EXIT)) &&
!sigismember(&t->real_blocked, sig) && (sig == SIGKILL || !p->ptrace))
{
if (!sig_kernel_coredump(sig)) {
/* 给进程signal置位SIGNAL_GROUP_EXIT, 代表进程需要走退出流程 */
signal->flags = SIGNAL_GROUP_EXIT
signal->group_exit_code = sig
signal->group_stop_count = 0
t = p
/* 将所有线程都置位SIGKILL pending信号位并唤醒它们 */
do {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK)
sigaddset(&t->pending.signal, SIGKILL)
signal_wake_up(t, 1)
} while_each_thread(p, t)
return
}
}
/* 8.4) 唤醒处理该信号的任务, 并为其置位TIF_SIGPENDING */
signal_wake_up(t, fatal = sig == SIGKILL) {
if (fatal && !(t->jobctl & JOBCTL_PTRACE_FROZEN)) {
t->jobctl &= ~(JOBCTL_STOPPED | JOBCTL_TRACED)
state = TASK_WAKEKILL | __TASK_TRACED
}
signal_wake_up_state(t, state) {
set_tsk_thread_flag(t, TIF_SIGPENDING)
### 若wake_up_state返回0, 代表目标task已经在其他CPU上运行, 此时调用kick_process发送核间中断, 使其在中断返回前夕相应信号
if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
kick_process(struct task_struct *p = t) {
preempt_disable()
cpu = task_cpu(p)
if ((cpu != smp_processor_id()) && task_curr(p))
smp_send_reschedule(cpu)
preempt_enable()
}
}//signal_wake_up_state
}//signal_wake_up
return
}//complete_signal
ret:
return ret
}//__send_signal_locked
}