Linux发生信号send_signal函数以及配套工具函数的实现
文章目录
- 一、获取线程组中的下一个线程`next_thread`
- 1.函数定义
- 2.宏定义
- 3.函数逻辑
- 3.1. SMP 系统的安全检查
- 3.2. 获取下一个线程
- 4.功能说明
- 二、检查信号集是否有该信号`sigismember`
- 1.整体结构
- 2. `__const_sigismember` - 编译时常量版本
- 3. `__gen_sigismember` - 运行时变量版本
- 3.1.第一条指令:`btl %2,%1`
- 3.2.第二条指令:`sbbl %0,%0`
- 三、检查信号是否被忽略`sig_ignored`
- 1.函数定义
- 2.逻辑分析
- 2.1. 跟踪器检查
- 2.2. 阻塞信号检查
- 2.3. 信号忽略检查
- 2.3.1.情况一:显式忽略
- 2.3.2.情况二:隐式忽略(默认处理为忽略)
- 3.辅助宏 `sig_kernel_ignore`
- 4.完整逻辑流程图
- 四、分配 `sigqueue` 结构体`__sigqueue_alloc`
- 1.函数定义
- 2.函数逻辑分析
- 2.1. 信号数量限制检查
- 2. 初始化 `sigqueue` 结构
- 3.关键数据结构
- 五、信号集添加函数`sigaddset`
- 1.函数定义
- 2.内联汇编分析
- 3.详细工作原理
- 六、复制 `siginfo` 结构体`copy_siginfo`
- 1.关键概念解析
- 1.1. `siginfo` 结构体
- 1.2. `si_code` 的含义
- 2.函数逻辑分析
- 七、发送信号`send_signal`
- 1.函数定义
- 2.函数逻辑分析
- 2.1. 快速路径处理
- 2.2. 实时信号队列分配
- 2.3. 成功分配队列的情况
- 2.3.1.情况 1:`info == 0` - 普通 kill() 信号
- 2.3.2.情况 2:`info == 1` - 内核生成信号
- 2.3.3.情况 3:其他值 - 完整的 `siginfo`
- 2.4. 队列分配失败的处理
- 2.5. 设置信号位图
一、获取线程组中的下一个线程next_thread
#define pid_task(elem, type) \list_entry(elem, struct task_struct, pids[type].pid_list)
task_t fastcall *next_thread(const task_t *p)
{
#ifdef CONFIG_SMPif (!p->sighand)BUG();if (!spin_is_locked(&p->sighand->siglock) &&!rwlock_is_locked(&tasklist_lock))BUG();
#endifreturn pid_task(p->pids[PIDTYPE_TGID].pid_list.next, PIDTYPE_TGID);
}
1.函数定义
task_t fastcall *next_thread(const task_t *p)
task_t
:任务结构体类型fastcall
:表示使用快速调用约定(寄存器传递参数)- 参数
p
:当前线程的指针
2.宏定义
#define pid_task(elem, type) \list_entry(elem, struct task_struct, pids[type].pid_list)
这个宏用于从链表节点获取包含该节点的 task_struct
结构体指针:
elem
:链表节点指针type
:PID 类型(如 PIDTYPE_TGID)- 通过结构体成员偏移计算得到完整的结构体地址
3.函数逻辑
3.1. SMP 系统的安全检查
#ifdef CONFIG_SMPif (!p->sighand)BUG();if (!spin_is_locked(&p->sighand->siglock) &&!rwlock_is_locked(&tasklist_lock))BUG();
#endif
- 检查信号处理结构体是否存在
- 验证必须持有锁:要么是信号锁(
siglock
),要么是任务列表锁(tasklist_lock
) - 这是为了在多处理器环境下保证线程安全
3.2. 获取下一个线程
return pid_task(p->pids[PIDTYPE_TGID].pid_list.next, PIDTYPE_TGID);
PIDTYPE_TGID
:表示线程组ID类型p->pids[PIDTYPE_TGID].pid_list
:当前线程所在线程组的链表.next
:获取链表中的下一个节点pid_task
:将链表节点转换为task_struct
指针
4.功能说明
这个函数用于遍历线程组中的所有线程。在 Linux 中:
- 每个线程组(进程)有一个主线程
- 所有属于同一进程的线程通过
pids[PIDTYPE_TGID].pid_list
链表连接 - 该函数返回给定线程所在线程组中的下一个线程
二、检查信号集是否有该信号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));
}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 sigismember(set,sig) \(__builtin_constant_p(sig) ? \__const_sigismember((set),(sig)) : \__gen_sigismember((set),(sig)))
1.整体结构
#define sigismember(set,sig) \(__builtin_constant_p(sig) ? \__const_sigismember((set),(sig)) : \__gen_sigismember((set),(sig)))
这是一个条件编译宏:
- 如果
sig
是编译时常量,使用__const_sigismember
- 如果
sig
是运行时变量,使用__gen_sigismember
2. __const_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
:信号编号从1开始,但数组索引从0开始sig / _NSIG_BPW
:计算在哪个字(word)中_NSIG_BPW
= Number of Signals Per Word(每个字的信号数)- 通常是 32 或 64,取决于架构
sig % _NSIG_BPW
:计算在字中的哪一位set->sig[...] >> ...
:右移到目标位1 & ...
:取最低位,得到0或1
示例: 检查信号3(SIGQUIT)
sig = 3 - 1 = 2
字索引 = 2 / 32 = 0
位偏移 = 2 % 32 = 2
返回值 = 1 & (set->sig[0] >> 2)
3. __gen_sigismember
- 运行时变量版本
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;
}
内联汇编分析:
3.1.第一条指令:btl %2,%1
btl
= Bit Test Long(测试长整型的特定位)%2
=_sig-1
(要测试的信号位)%1
=*set
(信号集内存)- 作用:测试信号集中指定位置的位,结果存储在 Carry Flag(CF)中
3.2.第二条指令:sbbl %0,%0
sbbl
= Subtract with Borrow Long(带借位减法)%0
=ret
(返回值)- 相当于:
ret = ret - ret - CF
- 简化后:
ret = -CF
返回值:
- 如果位被设置(CF=1):
ret = -1
- 如果位未设置(CF=0):
ret = 0
三、检查信号是否被忽略sig_ignored
#define sig_kernel_ignore(sig) \(((sig) < SIGRTMIN) && T(sig, SIG_KERNEL_IGNORE_MASK))
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));
}
1.函数定义
static int sig_ignored(struct task_struct *t, int sig)
- 参数:
t
:目标进程的task_struct
指针sig
:要检查的信号编号
- 返回值:
1
:信号被忽略0
:信号不被忽略
2.逻辑分析
2.1. 跟踪器检查
if (t->ptrace & PT_PTRACED)return 0;
作用:如果进程正在被调试器跟踪(ptrace
),信号永远不会被忽略。
原因:调试器需要接收所有信号来进行调试,即使信号原本应该被忽略。
2.2. 阻塞信号检查
if (sigismember(&t->blocked, sig))return 0;
作用:检查信号是否在当前进程的信号掩码中被阻塞。
原因:
- 阻塞的信号不会被传递,所以谈不上"忽略"
- 阻塞是临时的,而忽略是持久的信号处理方式
2.3. 信号忽略检查
handler = t->sighand->action[sig-1].sa.sa_handler;
return handler == SIG_IGN ||(handler == SIG_DFL && sig_kernel_ignore(sig));
这里检查两种忽略情况:
2.3.1.情况一:显式忽略
handler == SIG_IGN
- 进程明确设置了
SIG_IGN
作为信号处理函数
2.3.2.情况二:隐式忽略(默认处理为忽略)
handler == SIG_DFL && sig_kernel_ignore(sig)
- 信号处理函数是默认的 (
SIG_DFL
) - 并且 内核默认忽略该信号
3.辅助宏 sig_kernel_ignore
#define sig_kernel_ignore(sig) \(((sig) < SIGRTMIN) && T(sig, SIG_KERNEL_IGNORE_MASK))
逻辑:
- 只对标准信号有效(
sig < SIGRTMIN
),实时信号没有默认忽略的 T(sig, SIG_KERNEL_IGNORE_MASK)
:检查信号是否在忽略掩码中
4.完整逻辑流程图
sig_ignored(t, sig)↓
进程被跟踪? → 是 → 返回0(不忽略)↓ 否
信号被阻塞? → 是 → 返回0(不忽略) ↓ 否
获取信号处理函数↓
显式忽略(SIG_IGN)? → 是 → 返回1(忽略)↓ 否
默认处理(SIG_DFL)且内核默认忽略? → 是 → 返回1(忽略)↓ 否
返回0(不忽略)
四、分配 sigqueue
结构体__sigqueue_alloc
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);
}
1.函数定义
static struct sigqueue *__sigqueue_alloc(struct task_struct *t, int flags)
- 参数:
t
:目标进程的task_struct
指针flags
:内存分配标志(如GFP_KERNEL
,GFP_ATOMIC
等)
- 返回值:成功返回
sigqueue
指针,失败返回NULL
2.函数逻辑分析
2.1. 信号数量限制检查
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:每个用户允许的待处理信号总数限制
内存分配:
- 只有在未超过限制时才分配
sigqueue
kmem_cache_alloc(sigqueue_cachep, flags)
:从专用的 slab 缓存分配
2. 初始化 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
:锁指针初始化为空q->user = get_uid(t->user)
:设置所属用户,并增加引用计数atomic_inc(&q->user->sigpending)
:增加用户的待处理信号计数
3.关键数据结构
struct sigqueue
struct sigqueue {struct list_head list; // 链表节点int flags; // 标志位spinlock_t *lock; // 保护锁struct user_struct *user; // 所属用户sigval_t info; // 信号信息
};
资源限制 RLIMIT_SIGPENDING
- 目的:防止用户产生大量信号
- 默认值:通常为 11
五、信号集添加函数sigaddset
static __inline__ void sigaddset(sigset_t *set, int _sig)
{__asm__("btsl %1,%0" : "=m"(*set) : "Ir"(_sig - 1) : "cc");
}
1.函数定义
static __inline__ void sigaddset(sigset_t *set, int _sig)
{__asm__("btsl %1,%0" : "=m"(*set) : "Ir"(_sig - 1) : "cc");
}
作用:将指定的信号添加到信号集中(设置对应的位)
2.内联汇编分析
汇编指令:btsl %1,%0
btsl
:Bit Test and Set Long(测试并设置长整型的特定位)- %1:第二个操作数(
_sig - 1
) - %0:第一个操作数(
*set
- 信号集内存)
操作数约束
: "=m"(*set) // 输出操作数:内存地址,可读写
: "Ir"(_sig - 1) // 输入操作数:立即数或寄存器
: "cc" // 破坏部分:条件代码寄存器(标志寄存器)
3.详细工作原理
信号编号转换
_sig - 1
- 信号编号从 1 开始(如 SIGINT=2, SIGQUIT=3)
- 但位操作从 0 开始,所以需要减 1
btsl
指令功能
btsl
指令执行两个操作:
- 测试:将指定位的值保存到 Carry Flag(CF)
- 设置:将指定位设置为 1
指令语义:
CF = bit[offset] // 将指定位的值保存到进位标志
bit[offset] = 1 // 将指定位设置为1
六、复制 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));
}
1.关键概念解析
1.1. siginfo
结构体
struct siginfo
包含信号的所有相关信息:
typedef struct siginfo {int si_signo; // 信号编号int si_errno; // 错误代码int si_code; // 信号来源代码union {// 各种信号特定的数据} _sifields;
} siginfo_t;
1.2. si_code
的含义
si_code
表示信号的来源:
si_code < 0
:用户空间发送的信号(通过kill()
,sigqueue()
等)si_code >= 0
:内核生成的信号
2.函数逻辑分析
情况一:用户空间信号 (si_code < 0
)
if (from->si_code < 0)memcpy(to, from, sizeof(*to));
完整复制:复制整个 siginfo
结构体
- 原因:用户空间可能设置了完整的
siginfo
- 安全性:需要完整复制所有数据,包括可能的扩展字段
情况二:内核生成信号 (si_code >= 0
)
memcpy(to, from, __ARCH_SI_PREAMBLE_SIZE + sizeof(from->_sifields._sigchld));
部分复制:只复制必要的部分
__ARCH_SI_PREAMBLE_SIZE
:架构相关的头部大小(通常是si_signo
到si_code
的固定字段)sizeof(from->_sifields._sigchld)
:最大的联合体成员大小
七、发送信号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;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)return -EAGAIN;if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))ret = info->si_sys_private;}out_set:sigaddset(&signals->signal, sig);return ret;
}
1.函数定义
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,struct sigpending *signals)
参数:
sig
:要发送的信号编号info
:信号信息结构体指针t
:目标进程signals
:目标信号队列
2.函数逻辑分析
2.1. 快速路径处理
if ((unsigned long)info == 2)goto out_set;
这是一个特殊优化:当 info == (void *)2
时,直接跳到设置信号位图,不进行队列分配。
- 用于内核内部的快速信号(如 SIGSTOP、SIGKILL)
- 这些信号不需要额外的
siginfo
信息
2.2. 实时信号队列分配
q = __sigqueue_alloc(t, GFP_ATOMIC);
尝试为信号分配队列条目:
GFP_ATOMIC
:在原子上下文中分配内存,不会睡眠- 如果分配失败,
q
为NULL
2.3. 成功分配队列的情况
if (q) {list_add_tail(&q->list, &signals->list);switch ((unsigned long) info) {// 处理三种不同的 info 情况}
}
2.3.1.情况 1:info == 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; // 发送者PIDq->info.si_uid = current->uid; // 发送者UIDbreak;
2.3.2.情况 2:info == 1
- 内核生成信号
case 1:q->info.si_signo = sig;q->info.si_errno = 0;q->info.si_code = SI_KERNEL; // 来自内核q->info.si_pid = 0; // 无发送者PIDq->info.si_uid = 0; // 无发送者UIDbreak;
2.3.3.情况 3:其他值 - 完整的 siginfo
default:copy_siginfo(&q->info, info); // 完整复制 siginfobreak;
2.4. 队列分配失败的处理
} else {if (sig >= SIGRTMIN && info && (unsigned long)info != 1&& info->si_code != SI_USER)return -EAGAIN;if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))ret = info->si_sys_private;
}
实时信号失败处理:
- 如果是实时信号(
sig >= SIGRTMIN
) - 并且有完整的
info
(info != 0 && info != 1
) - 并且不是来自普通用户 (
si_code != SI_USER
) - 则返回
-EAGAIN
,表示队列溢出
定时器信号特殊处理:
- 如果是定时器信号 (
SI_TIMER
) - 保存返回值到
ret
,用于通知定时器子系统信号被丢弃
2.5. 设置信号位图
out_set:sigaddset(&signals->signal, sig);return ret;
无论是否成功分配队列,都会在信号位图中设置对应的位。