当前位置: 首页 > news >正文

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 的执行结果

函数功能

主要功能: 强制发送信号给目标进程,确保信号不会被忽略或阻塞

关键特性:

  1. 原子性操作:通过自旋锁确保信号处理的原子性
  2. 强制传递:即使信号被阻塞或设置为忽略,也会被重置并发送
  3. 中断安全:正确处理中断状态,不影响系统中断处理
  4. 状态同步:及时更新进程的信号状态

设置或清除 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))
  1. 组停止计数检查

    t->signal->group_stop_count > 0
    
    • 检查进程组是否有停止的进程
    • group_stop_count > 0 表示有进程需要停止
  2. 私有挂起信号检查

    PENDING(&t->pending, &t->blocked)
    
    • 检查进程私有的挂起信号(只发给该进程的信号)
    • 排除被阻塞的信号
  3. 共享挂起信号检查

    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 标志:

  1. 进程组停止计数
  2. 私有挂起信号(排除阻塞的)
  3. 共享挂起信号(排除阻塞的)

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:信号成功发送或已在队列中
    • 正数:定时器信号被丢弃的计数
    • 负数:错误码

函数功能总结

主要功能:向特定进程发送信号,处理信号排队和传递逻辑

关键特性

  1. 原子性保证:要求中断禁用和锁持有
  2. 信号过滤:检查忽略状态和排队限制
  3. 定时器支持:处理定时器信号的特殊情况
  4. 进程唤醒:在适当时机唤醒目标进程处理信号
  5. 传统信号语义:遵守非实时信号的不可排队规则

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开始
  • 返回条件
    1. handler == SIG_IGN:显式忽略(用户设置了SIG_IGN)
    2. 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
  • 计算过程
    1. sig / _NSIG_BPW:找到信号所在的字索引
    2. sig % _NSIG_BPW:找到信号在字中的位位置
    3. set->sig[index] >> position:将对应位右移到最低位
    4. 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)))
  • 条件判断
    1. (sig) < SIGRTMIN:信号是传统信号(非实时信号,1-31)
    2. sigismember(&(sigptr)->signal, (sig)):信号已在挂起信号集中
  • 用途:检查传统信号是否已经在队列中(传统信号不支持排队)

函数功能总结

主要功能:判断信号是否被目标进程忽略

忽略信号的三种情况

  1. 显式忽略:信号处理函数设置为 SIG_IGN
  2. 隐式忽略:信号处理函数为 SIG_DFL 且内核认为该信号可忽略
  3. 特殊情况不忽略
    • 进程正在被调试(ptrace
    • 信号当前被阻塞

设计原理

  1. 调试器优先:被调试的进程不忽略任何信号,便于调试器控制
  2. 阻塞信号特殊处理:阻塞的信号保留不处理,因为处理程序可能改变

将信号添加到目标进程的挂起队列中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:信号队列节点指针,初始为NULL
  • ret:返回值,初始为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表示实时信号失败

函数功能总结

主要功能

将信号添加到目标进程的挂起队列中,处理信号排队和内存分配失败的各种情况

三种信号来源处理:

  1. 用户kill() (info == 0):填充基本用户信息
  2. 内核内部 (info == 1):标记为内核信号
  3. 完整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)
  1. __ARCH_SI_PREAMBLE_SIZE:架构相关的siginfo头部大小

    • 包含:si_signo, si_errno, si_code等通用字段
  2. sizeof(from->_sifields._sigchld):最大的联合体成员大小

    • _sifieldssiginfo中的联合体,包含各种信号特定数据
    • _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:锁指针初始化为NULL
  • q->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);
  1. atomic_dec(&q->user->sigpending):减少用户的信号挂起计数
  2. free_uid(q->user):减少用户引用计数,如果为0则释放用户结构
  3. 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=1sig=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函数

// 强制踢出进程,使其重新调度
// 通过发送处理器间中断实现
  1. 进程在运行中但需要立即处理信号
  2. 进程在其他CPU上执行,需要中断它
  3. 确保信号及时被处理

函数功能总结

通知目标进程有新的信号需要处理,并确保进程在适当的状态下被唤醒

信号标志管理:

  • 设置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_tstruct 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架构下信号及时传递的最后保障机制,确保即使进程在其他处理器上繁忙执行,也能及时响应信号处理请求

http://www.dtcms.com/a/601949.html

相关文章:

  • 佛山新网站建设哪家好建筑方案设计流程步骤
  • 计算机工作原理
  • 北京做网站建设比较好的公司上海网站建设企业名录
  • AEC-Q100 stress实验详解#3——HTSL(高温储存寿命测试)
  • 洋洋点建站wordpress判断是否登录
  • 做的好的农产品网站怎样开通微商城平台
  • Python | 变量如何定义,数据类型介绍
  • 12. 2 雅可比法
  • 【OpenCV + VS】图像通道的均值和方差计算
  • (5)框架搭建:Qt实战项目之主窗体菜单栏
  • 网页C语言在线编译 | 快速、便捷的编程体验
  • 网站免费注册建站培训班
  • WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程
  • 万网的网站建设好吗北京模板网站建站
  • Leaflet入门,Leaflet如何修复瓦片之间有白线问题
  • Unity一分钟思路---UI任务条:宝箱位置如何准确卡在百分比位置上
  • 在线做爰a视频网站个人网站搭建详细步骤
  • 网站开发 工作量云台山旅游景区网站建设内容
  • Android 使用MediaMuxer+MediaCodec编码MP4视频异步方案
  • 第14章 智能指针
  • GSV6128E/ACP---嵌入式Display port 1.4到 LVDS转换器,带音频提取和嵌入式MCU
  • 网站建设ftp上传是空目录仿做网站的网站
  • c 网站开发代码辅助色网站
  • 无法下载依赖:pentaho-aggdesigner-algorithm/5.1.5-jhyde
  • sward实战教程系列(1) - 安装与配置
  • C语言编译环境 | 配置和优化你的开发环境,让编程更加高效
  • 《Vue项目开发实战》第五章:组件封装--Form
  • 数据管理战略|流程与IT变革、量化闭环
  • 外卖网站制作wordpress主题 制作教程
  • 企业网站总承包建设模式关键步骤wordpress安装主题后打不开