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

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 指令执行两个操作:

  1. 测试:将指定位的值保存到 Carry Flag(CF)
  2. 设置:将指定位设置为 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_signosi_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:在原子上下文中分配内存,不会睡眠
  • 如果分配失败,qNULL

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;

无论是否成功分配队列,都会在信号位图中设置对应的位。

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

相关文章:

  • 禅城网站制作产品营销方案案例范文
  • 网站建设需要什么系统wordpress 速度变慢
  • 选择适合的服装管理信息系统提升业务效率
  • 企业建设网站找网站公司吗企业官方网站制作推广软件
  • 建设网站实验活动小结青海建设网站价格低
  • 力扣hot100做题整理(21-30)
  • 建设网站招聘织梦网站档目管理空白
  • 进口网站建设简述jsp网站开发的环境配置过程
  • 2015年做那些网站致富广州网站制作开发公司
  • 有域名了怎么建立网站杭州市网站seo
  • 题解:P14041 [PAIO 2025] Towers
  • 城乡建设学校官方网站wordpress定时失败
  • 许昌长葛网站建设制作设计做外汇上什么网站看新闻
  • 案例学网页设计和网站建设网站建设百度不通过
  • 有哪些做外贸网站ps怎样做网站首页图
  • 网站建设论文设计网站开发软件成本
  • 丽水网站建设哪家好专业团队建设方案
  • 连云港做网站制作型云网站建设
  • 【Docker-Day 38】Kubernetes 核心调度:深入解析资源请求 (Requests) 与限制 (Limits) 的奥秘
  • 2328. 网格图中递增路径的数目 2001 关于灵神解法的消化
  • 网站数据库有哪些中企动力官网 网站
  • 正则表达式入门与进阶(优化版)
  • 服装销售网站建设策划书wordpress time()
  • 1.7 巴特沃斯滤波器
  • 在线做试卷网站网页设计作业代做
  • 个人网站免费的吗网站开发的体会
  • 做安防在哪个网站做广告呢保网微商城官网登录
  • 中国建设银行网站如何注册品牌网站建设 蝌蚪小7
  • 重庆专业做淘宝网站百度h5怎么发布
  • 厦门网站设计排行兰州网络推广徽hyhyk1