Linux内核信号传递机制完全解析:从force_sig_info到kick_process的完整路径
前言
通过本文,您将深入掌握Linux内核信号传递的完整技术体系。从强制信号发送到进程唤醒的每一个环节,我们将逐行分析关键函数的实现原理,揭示Linux内核如何高效、可靠地处理信号传递这一核心功能。
您将学到:
- 信号强制传递机制:了解
force_sig_info如何绕过信号阻塞和忽略设置 - 信号状态管理:掌握
recalc_sigpending_tsk如何智能计算信号挂起状态 - 信号排队策略:理解传统信号与实时信号在排队机制上的根本差异
- 内存优化技巧:学习内核如何通过slab缓存和资源限制防止信号队列溢出
- 多处理器协同:
kick_process如何通过IPI中断确保跨CPU信号及时处理 - 性能优化设计:了解
copy_siginfo如何根据信号来源优化内存复制操作
强制发送一个信号给目标进程force_sig_info
/** Force a signal that the process can't ignore: if necessary* we unblock the signal and change any SIG_IGN to SIG_DFL.*/int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{unsigned long int flags;int ret;spin_lock_irqsave(&t->sighand->siglock, flags);if (sigismember(&t->blocked, sig) || t->sighand->action[sig-1].sa.sa_handler == SIG_IGN) {t->sighand->action[sig-1].sa.sa_handler = SIG_DFL;sigdelset(&t->blocked, sig);recalc_sigpending_tsk(t);}ret = specific_send_sig_info(sig, info, t);spin_unlock_irqrestore(&t->sighand->siglock, flags);return ret;
}
函数概述
这个函数用于强制发送一个信号给目标进程,即使该信号被阻塞或被忽略也会被处理
变量声明
unsigned long int flags;
int ret;
flags:用于保存中断状态,在加锁时保存当前的中断使能状态ret:存储函数调用的返回值
加锁
spin_lock_irqsave(&t->sighand->siglock, flags);
- 获取目标进程的信号处理自旋锁,并禁用本地CPU中断
irqsave保存当前中断状态到flags,以便后续恢复- 防止在信号处理过程中被中断或其他CPU核心干扰
信号处理检查与重置
if (sigismember(&t->blocked, sig) || t->sighand->action[sig-1].sa.sa_handler == SIG_IGN) {t->sighand->action[sig-1].sa.sa_handler = SIG_DFL;sigdelset(&t->blocked, sig);recalc_sigpending_tsk(t);
}
sigismember(&t->blocked, sig) // 检查信号是否在进程的阻塞掩码中
|| // 或者
t->sighand->action[sig-1].sa.sa_handler == SIG_IGN // 检查信号处理程序是否为SIG_IGN(忽略)
条件成立时的处理:
t->sighand->action[sig-1].sa.sa_handler = SIG_DFL;
- 将信号的处理方式从 SIG_IGN(忽略)改为 SIG_DFL(默认处理)
- 数组索引
sig-1是因为信号编号从1开始,而数组索引从0开始
sigdelset(&t->blocked, sig);
- 从进程的阻塞信号集中移除该信号
- 确保即使信号被阻塞也能被传递
recalc_sigpending_tsk(t);
- 重新计算任务的挂起信号状态
- 更新
t->pending和线程组的信号状态 - 确保信号状态变化被正确反映
发送信号
ret = specific_send_sig_info(sig, info, t);
- 实际发送信号给目标进程
sig:信号编号info:信号附加信息(可为NULL)t:目标进程描述符- 返回值表示信号是否成功发送
释放锁
spin_unlock_irqrestore(&t->sighand->siglock, flags);
- 释放信号处理锁
irqrestore恢复之前保存的中断状态- 确保中断状态与进入函数时一致
返回结果
return ret;
- 返回
specific_send_sig_info的执行结果
函数功能
主要功能: 强制发送信号给目标进程,确保信号不会被忽略或阻塞
关键特性:
- 原子性操作:通过自旋锁确保信号处理的原子性
- 强制传递:即使信号被阻塞或设置为忽略,也会被重置并发送
- 中断安全:正确处理中断状态,不影响系统中断处理
- 状态同步:及时更新进程的信号状态
设置或清除 TIF_SIGPENDING 标志recalc_sigpending_tsk
#define PENDING(p,b) has_pending_signals(&(p)->signal, (b))fastcall void recalc_sigpending_tsk(struct task_struct *t)
{if (t->signal->group_stop_count > 0 ||PENDING(&t->pending, &t->blocked) ||PENDING(&t->signal->shared_pending, &t->blocked))set_tsk_thread_flag(t, TIF_SIGPENDING);elseclear_tsk_thread_flag(t, TIF_SIGPENDING);
}
/** Re-calculate pending state from the set of locally pending* signals, globally pending signals, and blocked signals.*/
static inline int has_pending_signals(sigset_t *signal, sigset_t *blocked)
{unsigned long ready;long i;switch (_NSIG_WORDS) {default:for (i = _NSIG_WORDS, ready = 0; --i >= 0 ;)ready |= signal->sig[i] &~ blocked->sig[i];break;case 4: ready = signal->sig[3] &~ blocked->sig[3];ready |= signal->sig[2] &~ blocked->sig[2];ready |= signal->sig[1] &~ blocked->sig[1];ready |= signal->sig[0] &~ blocked->sig[0];break;case 2: ready = signal->sig[1] &~ blocked->sig[1];ready |= signal->sig[0] &~ blocked->sig[0];break;case 1: ready = signal->sig[0] &~ blocked->sig[0];}return ready != 0;
}
宏定义和函数声明
PENDING 宏
#define PENDING(p,b) has_pending_signals(&(p)->signal, (b))
- 定义一个宏,用于检查是否有待处理的信号
p:指向信号集的指针b:指向阻塞信号集的指针- 展开为调用
has_pending_signals函数
recalc_sigpending_tsk 函数
条件判断
if (t->signal->group_stop_count > 0 ||PENDING(&t->pending, &t->blocked) ||PENDING(&t->signal->shared_pending, &t->blocked))
-
组停止计数检查:
t->signal->group_stop_count > 0- 检查进程组是否有停止的进程
group_stop_count> 0 表示有进程需要停止
-
私有挂起信号检查:
PENDING(&t->pending, &t->blocked)- 检查进程私有的挂起信号(只发给该进程的信号)
- 排除被阻塞的信号
-
共享挂起信号检查:
PENDING(&t->signal->shared_pending, &t->blocked)- 检查进程组共享的挂起信号(发给整个进程组的信号)
- 同样排除被阻塞的信号
设置或清除标志
set_tsk_thread_flag(t, TIF_SIGPENDING);
else
clear_tsk_thread_flag(t, TIF_SIGPENDING);
- 条件成立:调用
set_tsk_thread_flag设置TIF_SIGPENDING标志 - 条件不成立:调用
clear_tsk_thread_flag清除TIF_SIGPENDING标志 TIF_SIGPENDING:表示任务有挂起的信号需要处理
has_pending_signals 函数
变量声明
unsigned long ready;
long i;
ready:位掩码,表示哪些信号是就绪的(挂起且未被阻塞)i:循环计数器
默认情况(_NSIG_WORDS > 4):
for (i = _NSIG_WORDS, ready = 0; --i >= 0 ;)ready |= signal->sig[i] &~ blocked->sig[i];
- 循环处理每个信号字(signal word)
signal->sig[i] &~ blocked->sig[i]:计算挂起且未被阻塞的信号- 通过按位或操作累积到
ready变量中
优化情况(特定字数的展开):
case 4: ready = signal->sig[3] &~ blocked->sig[3];ready |= signal->sig[2] &~ blocked->sig[2];ready |= signal->sig[1] &~ blocked->sig[1];ready |= signal->sig[0] &~ blocked->sig[0];break;case 2: ready = signal->sig[1] &~ blocked->sig[1];ready |= signal->sig[0] &~ blocked->sig[0];break;case 1: ready = signal->sig[0] &~ blocked->sig[0];
- 对常见的信号字数(1、2、4)进行循环展开优化
- 避免循环开销,提高性能
- 逻辑与默认情况相同
返回结果
return ready != 0;
- 如果
ready不为0,返回真(有挂起且未被阻塞的信号) - 如果
ready为0,返回假(没有可处理的信号)
函数功能总结
recalc_sigpending_tsk:重新计算任务的信号挂起状态,根据以下条件设置或清除 TIF_SIGPENDING 标志:
- 进程组停止计数
- 私有挂起信号(排除阻塞的)
- 共享挂起信号(排除阻塞的)
has_pending_signals:高效检查是否有挂起且未被阻塞的信号,通过位操作快速判断
向特定进程发送信号specific_send_sig_info
static int
specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{int ret = 0;if (!irqs_disabled())BUG();
#ifdef CONFIG_SMPif (!spin_is_locked(&t->sighand->siglock))BUG();
#endifif (((unsigned long)info > 2) && (info->si_code == SI_TIMER))/** Set up a return to indicate that we dropped the signal.*/ret = info->si_sys_private;/* Short-circuit ignored signals. */if (sig_ignored(t, sig))goto out;/* Support queueing exactly one non-rt signal, so that wecan get more detailed information about the cause ofthe signal. */if (LEGACY_QUEUE(&t->pending, sig))goto out;ret = send_signal(sig, info, t, &t->pending);if (!ret && !sigismember(&t->blocked, sig))signal_wake_up(t, sig == SIGKILL);
out:return ret;
}
函数声明和变量定义
函数声明和初始化
static int
specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{int ret = 0;
static int:静态函数,只在当前文件内可见sig:要发送的信号编号info:信号附加信息结构体指针(可以为NULL)t:目标进程的任务结构指针ret = 0:初始化返回值为0,表示成功
中断检查
if (!irqs_disabled())BUG();
irqs_disabled():检查当前CPU的中断是否被禁用- 如果中断没有被禁用,触发内核BUG(系统崩溃)
- 原因:信号操作必须原子性执行,不能被中断打断
SMP锁检查
#ifdef CONFIG_SMP
if (!spin_is_locked(&t->sighand->siglock))BUG();
#endif
#ifdef CONFIG_SMP:只在SMP(多处理器)配置下编译spin_is_locked():检查目标进程的信号锁是否已被获取- 如果锁没有被持有,触发内核BUG
- 原因:在多处理器系统中,必须持有锁来保护信号数据结构
定时器信号检查
if (((unsigned long)info > 2) && (info->si_code == SI_TIMER))/** Set up a return to indicate that we dropped the signal.*/ret = info->si_sys_private;
(unsigned long)info > 2:检查info指针是否有效(不是特殊值1或2)info->si_code == SI_TIMER:检查是否是定时器产生的信号ret = info->si_sys_private:如果信号被丢弃,返回私有数据- 作用:如果这是一个有效的定时器信号,并且由于信号队列已满而被丢弃,那么返回定时器的超限计数,这样调用者就知道有多少次定时器到期被合并了
信号忽略检查
/* Short-circuit ignored signals. */
if (sig_ignored(t, sig))goto out;
sig_ignored(t, sig):检查信号是否被目标进程忽略- 如果信号被忽略,直接跳转到函数末尾(
goto out) - 短路优化:避免不必要的信号排队操作
信号队列检查
/* Support queueing exactly one non-rt signal, so that wecan get more detailed information about the cause ofthe signal. */
if (LEGACY_QUEUE(&t->pending, sig))goto out;
LEGACY_QUEUE(&t->pending, sig):检查传统信号是否已在队列中- 传统信号:非实时信号(1-31),不支持排队
- 规则:同种传统信号只能有一个在队列中
- 如果信号已在队列中,跳转到函数末尾
发送信号
ret = send_signal(sig, info, t, &t->pending);
- 调用
send_signal函数实际将信号加入目标进程的挂起队列 - 返回值表示是否成功(0=成功,负数=失败)
进程唤醒
if (!ret && !sigismember(&t->blocked, sig))signal_wake_up(t, sig == SIGKILL);
!ret:信号发送成功!sigismember(&t->blocked, sig):信号没有被阻塞signal_wake_up(t, sig == SIGKILL):唤醒目标进程sig == SIGKILL:如果是SIGKILL信号,设置特殊标志- SIGKILL特殊处理:即使进程处于不可中断睡眠也要立即唤醒
返回结果
out:
return ret;
out:标签,用于前面的goto跳转return ret:返回操作结果- 0:信号成功发送或已在队列中
- 正数:定时器信号被丢弃的计数
- 负数:错误码
函数功能总结
主要功能:向特定进程发送信号,处理信号排队和传递逻辑
关键特性:
- 原子性保证:要求中断禁用和锁持有
- 信号过滤:检查忽略状态和排队限制
- 定时器支持:处理定时器信号的特殊情况
- 进程唤醒:在适当时机唤醒目标进程处理信号
- 传统信号语义:遵守非实时信号的不可排队规则
sig_ignored/sigismember/LEGACY_QUEUE
static int sig_ignored(struct task_struct *t, int sig)
{void __user * handler;/** Tracers always want to know about signals..*/if (t->ptrace & PT_PTRACED)return 0;/** Blocked signals are never ignored, since the* signal handler may change by the time it is* unblocked.*/if (sigismember(&t->blocked, sig))return 0;/* Is it explicitly or implicitly ignored? */handler = t->sighand->action[sig-1].sa.sa_handler;return handler == SIG_IGN ||(handler == SIG_DFL && sig_kernel_ignore(sig));
}
#define sigismember(set,sig) \(__builtin_constant_p(sig) ? \__const_sigismember((set),(sig)) : \__gen_sigismember((set),(sig)))
static __inline__ int __const_sigismember(sigset_t *set, int _sig)
{unsigned long sig = _sig - 1;return 1 & (set->sig[sig / _NSIG_BPW] >> (sig % _NSIG_BPW));
}
static __inline__ int __gen_sigismember(sigset_t *set, int _sig)
{int ret;__asm__("btl %2,%1\n\tsbbl %0,%0": "=r"(ret) : "m"(*set), "Ir"(_sig-1) : "cc");return ret;
}
#define LEGACY_QUEUE(sigptr, sig) \(((sig) < SIGRTMIN) && sigismember(&(sigptr)->signal, (sig)))
sig_ignored 函数
函数声明和变量定义
static int sig_ignored(struct task_struct *t, int sig)
{void __user * handler;
static int:静态函数,只在当前文件内可见t:目标进程的任务结构指针sig:要检查的信号编号handler:存储信号处理函数的指针
调试器检查
/** Tracers always want to know about signals..*/
if (t->ptrace & PT_PTRACED)return 0;
- 调试器总是希望知道所有信号
t->ptrace & PT_PTRACED:检查进程是否正在被调试(ptrace跟踪)- 如果被调试,返回0(不忽略),让调试器能够看到所有信号
阻塞信号检查
/** Blocked signals are never ignored, since the* signal handler may change by the time it is* unblocked.*/
if (sigismember(&t->blocked, sig))return 0;
sigismember(&t->blocked, sig):检查信号是否在阻塞信号集中- 如果信号被阻塞,返回0(不忽略),等待解除阻塞后再判断
信号忽略判断
/* Is it explicitly or implicitly ignored? */
handler = t->sighand->action[sig-1].sa.sa_handler;
return handler == SIG_IGN ||(handler == SIG_DFL && sig_kernel_ignore(sig));
handler = t->sighand->action[sig-1].sa.sa_handler:获取信号的处理函数- 数组索引
sig-1因为信号编号从1开始,数组索引从0开始
- 数组索引
- 返回条件:
handler == SIG_IGN:显式忽略(用户设置了SIG_IGN)handler == SIG_DFL && sig_kernel_ignore(sig):隐式忽略(默认处理且内核认为可忽略)
sigismember 宏及其实现
#define sigismember(set,sig) \(__builtin_constant_p(sig) ? \__const_sigismember((set),(sig)) : \__gen_sigismember((set),(sig)))
- 条件编译优化:
__builtin_constant_p(sig):检查sig是否是编译时常量- 如果是常量:调用
__const_sigismember(编译时优化版本) - 如果是变量:调用
__gen_sigismember(通用汇编版本)
编译时常量版本
static __inline__ int __const_sigismember(sigset_t *set, int _sig)
{unsigned long sig = _sig - 1;return 1 & (set->sig[sig / _NSIG_BPW] >> (sig % _NSIG_BPW));
}
sig = _sig - 1:信号编号转为0-based索引_NSIG_BPW:每个字的位数(Bits Per Word),通常是32或64- 计算过程:
sig / _NSIG_BPW:找到信号所在的字索引sig % _NSIG_BPW:找到信号在字中的位位置set->sig[index] >> position:将对应位右移到最低位1 & value:取最低位,返回0或1
通用汇编版本
static __inline__ int __gen_sigismember(sigset_t *set, int _sig)
{int ret;__asm__("btl %2,%1\n\tsbbl %0,%0": "=r"(ret) : "m"(*set), "Ir"(_sig-1) : "cc");return ret;
}
- 内联汇编:
btl %2,%1:Bit Test指令,测试在%1中第%2个操作数指定的位- 即进行相与,如果为1,说明该位设置了,CF置1,反之亦然
sbbl %0,%0:带借位减法,用于根据CF标志设置返回值
- 操作数约束:
"=r"(ret):输出,将结果存入ret变量"m"(*set):输入,内存中的信号集"Ir"(_sig-1):输入,立即数或寄存器中的信号索引"cc":告知编译器条件码寄存器被修改
LEGACY_QUEUE 宏
#define LEGACY_QUEUE(sigptr, sig) \(((sig) < SIGRTMIN) && sigismember(&(sigptr)->signal, (sig)))
- 条件判断:
(sig) < SIGRTMIN:信号是传统信号(非实时信号,1-31)sigismember(&(sigptr)->signal, (sig)):信号已在挂起信号集中
- 用途:检查传统信号是否已经在队列中(传统信号不支持排队)
函数功能总结
主要功能:判断信号是否被目标进程忽略
忽略信号的三种情况:
- 显式忽略:信号处理函数设置为
SIG_IGN - 隐式忽略:信号处理函数为
SIG_DFL且内核认为该信号可忽略 - 特殊情况不忽略:
- 进程正在被调试(
ptrace) - 信号当前被阻塞
- 进程正在被调试(
设计原理
- 调试器优先:被调试的进程不忽略任何信号,便于调试器控制
- 阻塞信号特殊处理:阻塞的信号保留不处理,因为处理程序可能改变
将信号添加到目标进程的挂起队列中send_signal
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,struct sigpending *signals)
{struct sigqueue * q = NULL;int ret = 0;/** fast-pathed signals for kernel-internal things like SIGSTOP* or SIGKILL.*/if ((unsigned long)info == 2)goto out_set;q = __sigqueue_alloc(t, GFP_ATOMIC);if (q) {list_add_tail(&q->list, &signals->list);switch ((unsigned long) info) {case 0:q->info.si_signo = sig;q->info.si_errno = 0;q->info.si_code = SI_USER;q->info.si_pid = current->pid;q->info.si_uid = current->uid;break;case 1: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;}} else {if (sig >= SIGRTMIN && info && (unsigned long)info != 1&& info->si_code != SI_USER)/** Queue overflow, abort. We may abort if the signal was rt* and sent by user using something other than kill().*/return -EAGAIN;if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))/** Set up a return to indicate that we dropped * the signal.*/ret = info->si_sys_private;}out_set:sigaddset(&signals->signal, sig);return ret;
}
函数代码分析
函数声明和变量定义
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,struct sigpending *signals)
{struct sigqueue * q = NULL;int ret = 0;
sig:要发送的信号编号info:信号附加信息t:目标进程的任务结构signals:目标信号挂起队列
局部变量:
q:信号队列节点指针,初始为NULLret:返回值,初始为0(成功)
快速路径处理
/** fast-pathed signals for kernel-internal things like SIGSTOP* or SIGKILL.*/
if ((unsigned long)info == 2)goto out_set;
(unsigned long)info == 2:表示来自用户空间的kill()系统调用- 对于简单的用户空间kill信号,跳过复杂的队列分配
实时信号队列分配
q = __sigqueue_alloc(t, GFP_ATOMIC);
__sigqueue_alloc(t, GFP_ATOMIC):为信号队列节点分配内存GFP_ATOMIC:原子分配标志,在中断上下文中使用,不会睡眠
内存分配成功的情况
if (q) {list_add_tail(&q->list, &signals->list);switch ((unsigned long) info) {
list_add_tail(&q->list, &signals->list):将新节点添加到队列尾部
Case 0:来自用户kill()
case 0:q->info.si_signo = sig;q->info.si_errno = 0;q->info.si_code = SI_USER;q->info.si_pid = current->pid;q->info.si_uid = current->uid;break;
info == 0:传统的kill()系统调用SI_USER:信号来自用户进程- 填充当前进程的PID和UID
Case 1:来自内核
case 1: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;
info == 1:信号来自内核内部SI_KERNEL:信号来自内核- PID和UID设为0
默认情况:完整的siginfo
default:copy_siginfo(&q->info, info);break;
info > 1:指向真正的siginfo结构体copy_siginfo():复制完整的信号信息
内存分配失败处理
} else {if (sig >= SIGRTMIN && info && (unsigned long)info != 1&& info->si_code != SI_USER)/** Queue overflow, abort. We may abort if the signal was rt* and sent by user using something other than kill().*/return -EAGAIN;
-
sig >= SIGRTMIN:是实时信号 -
info && (unsigned long)info != 1:有有效的siginfo且不是内核信号 -
info->si_code != SI_USER:不是普通kill()发送的 -
只有实时信号且通过
sigqueue()发送时才允许失败
定时器信号特殊处理
if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))/** Set up a return to indicate that we dropped * the signal.*/ret = info->si_sys_private;
- 如果是定时器信号且内存分配失败
- 返回定时器的超限计数(
si_sys_private) - 让调用者知道信号被丢弃了
信号集设置和返回
out_set:sigaddset(&signals->signal, sig);return ret;
-
sigaddset(&signals->signal, sig):在信号位图中设置对应位 -
确保即使队列分配失败,信号也能被标记为挂起
-
ret:0表示成功,正数表示定时器丢弃计数,-EAGAIN表示实时信号失败
函数功能总结
主要功能
将信号添加到目标进程的挂起队列中,处理信号排队和内存分配失败的各种情况
三种信号来源处理:
- 用户kill() (
info == 0):填充基本用户信息 - 内核内部 (
info == 1):标记为内核信号 - 完整
siginfo(info > 1):复制完整信号信息
内存分配策略:
- 成功:信号加入队列,携带完整信息
- 失败:
- 实时信号(非kill):返回-EAGAIN
- 定时器信号:返回丢弃计数
- 其他情况:至少设置信号位图
高效复制siginfo结构体copy_siginfo
static inline void copy_siginfo(struct siginfo *to, struct siginfo *from)
{if (from->si_code < 0)memcpy(to, from, sizeof(*to));else/* _sigchld is currently the largest know union member */memcpy(to, from, __ARCH_SI_PREAMBLE_SIZE + sizeof(from->_sifields._sigchld));
}
函数代码分析
函数声明
static inline void copy_siginfo(struct siginfo *to, struct siginfo *from)
static inline void:静态内联函数,无返回值to:目标siginfo结构体指针from:源siginfo结构体指针
信号来源检查
if (from->si_code < 0)
si_code:表示信号的来源和具体原因si_code< 0:信号来自用户空间si_code>= 0:信号来自内核空间
完整内存复制
memcpy(to, from, sizeof(*to));
当 si_code < 0(用户空间信号)时:
sizeof(*to):完整的siginfo结构体大小memcpy(to, from, sizeof(*to)):复制整个siginfo结构体
部分内存复制
/* _sigchld is currently the largest know union member */
memcpy(to, from, __ARCH_SI_PREAMBLE_SIZE + sizeof(from->_sifields._sigchld));
_sigchld是目前已知的最大的联合体成员- SIGCHLD信号包含最多的信息字段
复制大小计算
__ARCH_SI_PREAMBLE_SIZE + sizeof(from->_sifields._sigchld)
-
__ARCH_SI_PREAMBLE_SIZE:架构相关的
siginfo头部大小- 包含:
si_signo,si_errno,si_code等通用字段
- 包含:
-
sizeof(from->_sifields._sigchld):最大的联合体成员大小_sifields是siginfo中的联合体,包含各种信号特定数据_sigchld是SIGCHLD信号的特定字段,通常是最大的
函数功能总结
主要功能
高效复制siginfo结构体,根据信号来源优化复制大小
用户空间信号(完整复制):
- 原因:用户可能通过
sigqueue传递任意数据 - 大小:完整的
siginfo结构体(通常128字节) - 保证:不丢失任何用户数据
内核空间信号(部分复制):
- 原因:内核知道信号的确切类型和所需字段
- 大小:头部 + 最大联合体成员大小
- 优化:避免复制未使用的尾部填充字节
__sigqueue_alloc/__sigqueue_free
static struct sigqueue *__sigqueue_alloc(struct task_struct *t, int flags)
{struct sigqueue *q = NULL;if (atomic_read(&t->user->sigpending) <t->signal->rlim[RLIMIT_SIGPENDING].rlim_cur)q = kmem_cache_alloc(sigqueue_cachep, flags);if (q) {INIT_LIST_HEAD(&q->list);q->flags = 0;q->lock = NULL;q->user = get_uid(t->user);atomic_inc(&q->user->sigpending);}return(q);
}static inline void __sigqueue_free(struct sigqueue *q)
{if (q->flags & SIGQUEUE_PREALLOC)return;atomic_dec(&q->user->sigpending);free_uid(q->user);kmem_cache_free(sigqueue_cachep, q);
}
__sigqueue_alloc 函数 - 信号队列节点分配
函数声明和初始化
static struct sigqueue *__sigqueue_alloc(struct task_struct *t, int flags)
{struct sigqueue *q = NULL;
static struct sigqueue *:静态函数,返回信号队列节点指针t:目标进程的任务结构指针flags:内存分配标志(如 GFP_ATOMIC)q = NULL:初始化返回指针为NULL
信号挂起数限制检查
if (atomic_read(&t->user->sigpending) <t->signal->rlim[RLIMIT_SIGPENDING].rlim_cur)q = kmem_cache_alloc(sigqueue_cachep, flags);
-
atomic_read(&t->user->sigpending):读取当前用户的信号挂起计数 -
t->signal->rlim[RLIMIT_SIGPENDING].rlim_cur:获取RLIMIT_SIGPENDING资源限制的当前值 -
RLIMIT_SIGPENDING:每个用户允许的挂起信号数量限制
-
只有在未超过限制时才分配内存
-
kmem_cache_alloc(sigqueue_cachep, flags):从专用的slab缓存分配sigqueue结构体
初始化信号队列节点
if (q) {INIT_LIST_HEAD(&q->list);q->flags = 0;q->lock = NULL;q->user = get_uid(t->user);atomic_inc(&q->user->sigpending);
}
INIT_LIST_HEAD(&q->list):初始化链表头,用于加入挂起信号链表q->flags = 0:清除所有标志位q->lock = NULL:锁指针初始化为NULLq->user = get_uid(t->user):设置用户引用并增加引用计数atomic_inc(&q->user->sigpending):增加用户的信号挂起计数
返回结果
return(q);
- 返回分配的
sigqueue指针,如果分配失败返回NULL
__sigqueue_free 函数 - 信号队列节点释放
函数声明
static inline void __sigqueue_free(struct sigqueue *q)
static inline void:静态内联函数,无返回值q:要释放的信号队列节点指针
预分配标志检查
if (q->flags & SIGQUEUE_PREALLOC)return;
SIGQUEUE_PREALLOC:表示这是预分配的信号队列节点- 如果是预分配的节点,直接返回不释放
- 用途:某些实时信号或关键信号可能使用预分配节点避免内存分配失败
资源清理
atomic_dec(&q->user->sigpending);
free_uid(q->user);
kmem_cache_free(sigqueue_cachep, q);
atomic_dec(&q->user->sigpending):减少用户的信号挂起计数free_uid(q->user):减少用户引用计数,如果为0则释放用户结构kmem_cache_free(sigqueue_cachep, q):将节点返回给slab缓存
signal_wake_up
void signal_wake_up(struct task_struct *t, int resume)
{unsigned int mask;set_tsk_thread_flag(t, TIF_SIGPENDING);mask = TASK_INTERRUPTIBLE;if (resume)mask |= TASK_STOPPED;if (!wake_up_state(t, mask))kick_process(t);
}
函数代码分析
函数声明
void signal_wake_up(struct task_struct *t, int resume)
t:目标进程的任务结构指针resume:是否恢复停止的进程(1=是,0=否)- 特殊用例:当
resume=1且sig=SIGKILL时,强制唤醒停止的进程
设置信号挂起标志
set_tsk_thread_flag(t, TIF_SIGPENDING);
TIF_SIGPENDING:线程有挂起的信号需要处理- 当进程返回到用户空间时,内核会检查这个标志并处理信号
唤醒掩码设置逻辑
mask = TASK_INTERRUPTIBLE;
if (resume)mask |= TASK_STOPPED;
mask = TASK_INTERRUPTIBLE:基础掩码,可中断睡眠状态mask |= TASK_STOPPED:如果resume为真,增加停止状态掩码
唤醒和踢出逻辑
if (!wake_up_state(t, mask))kick_process(t);
wake_up_state函数
// 尝试将进程从指定状态唤醒
// 返回值:1=成功唤醒,0=进程不在指定状态
- 检查进程是否处于
mask指定的状态之一 - 如果是,将进程状态改为TASK_RUNNING并加入运行队列
- 返回是否成功唤醒了进程
kick_process函数
// 强制踢出进程,使其重新调度
// 通过发送处理器间中断实现
- 进程在运行中但需要立即处理信号
- 进程在其他CPU上执行,需要中断它
- 确保信号及时被处理
函数功能总结
通知目标进程有新的信号需要处理,并确保进程在适当的状态下被唤醒
信号标志管理:
- 设置
TIF_SIGPENDING标志,标记有挂起信号 - 确保进程在返回用户空间时处理信号
多处理器支持:
- 使用
kick_process处理在其他CPU上运行的进程 - 确保信号及时传递到所有处理器
kick_process
/**** kick_process - kick a running thread to enter/exit the kernel* @p: the to-be-kicked thread** Cause a process which is running on another CPU to enter* kernel-mode, without any delay. (to get signals handled.)*/
void kick_process(task_t *p)
{int cpu;preempt_disable();cpu = task_cpu(p);if ((cpu != smp_processor_id()) && task_curr(p))smp_send_reschedule(cpu);preempt_enable();
}
函数代码分析
函数声明
void kick_process(task_t *p)
-
task_t *p:目标进程的任务结构指针 -
task_t是struct task_struct的类型定义 -
主要目标:强制目标进程重新调度,以便及时处理挂起的信号
禁用抢占
preempt_disable();
- 防止当前任务在检查过程中被抢占
- 保证后续操作的原子性
- 避免在检查CPU编号和任务状态时情况发生变化
获取CPU编号和条件检查
cpu = task_cpu(p);
if ((cpu != smp_processor_id()) && task_curr(p))
-
获取进程当前运行的CPU编号
-
返回值为0到NR_CPUS-1之间的整数
-
条件1:
cpu != smp_processor_id()- 目标进程不在当前CPU上运行
smp_processor_id()返回当前CPU的编号
-
条件2:
task_curr(p)- 目标进程正在某个CPU上运行(是当前运行的任务)
task_curr(p)检查进程是否处于对应CPU的curr中
-
如果进程在当前CPU上,不需要IPI(已经在正确上下文)
-
如果进程不在运行状态,不需要立即中断它
发送重调度中断
smp_send_reschedule(cpu);
- 向指定CPU发送处理器间中断(IPI)
- 触发目标CPU上的重调度操作
重新启用抢占
preempt_enable();
- 与
preempt_disable()配对使用 - 恢复正常的抢占行为
函数功能总结
通过处理器间中断强制运行在其他CPU上的进程重新调度,使其及时进入内核模式处理信号或其他事件
精确目标:
- 只对运行在其他CPU上的进程发送IPI
- 避免不必要的处理器间通信开销
状态感知:
- 检查进程是否真正在运行(
task_curr) - 避免对睡眠或停止的进程发送IPI
原子性保证:
- 使用抢占禁用保护关键检查阶段
- 防止竞态条件导致错误决策
kick_process 是Linux内核SMP架构下信号及时传递的最后保障机制,确保即使进程在其他处理器上繁忙执行,也能及时响应信号处理请求
