Linux信号处理的相关数据结构和操作函数

文章目录

一、是否有未阻塞的待处理信号has_pending_signals

c 复制代码
typedef struct {
        unsigned long sig[_NSIG_WORDS];
} sigset_t;
#define _NSIG		64
#define _NSIG_BPW	32
#define _NSIG_WORDS	(_NSIG / _NSIG_BPW)
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;
}

1.函数定义

c 复制代码
static inline int has_pending_signals(sigset_t *signal, sigset_t *blocked)
  • signal: 待处理信号集(pending signals)
  • blocked: 被阻塞的信号集(blocked signals)
  • 返回值: 1表示有未阻塞的待处理信号,0表示没有

2.核心算法原理

基本逻辑

  • 对于每个信号位:
  • 如果信号待处理 且 没有被阻塞 → 说明有需要处理的信号
  • ready = (signal & ~blocked)

位运算解释

  • signal: 0110 (信号2和3待处理)
  • blocked: 0010 (信号2被阻塞)
  • ~blocked: 1101 (阻塞信号的取反)
  • ready: 0100 (只有信号3需要处理)

3.信号集数据结构

sigset_t结构

c 复制代码
#define _NSIG		64
#define _NSIG_BPW	32
#define _NSIG_WORDS	(_NSIG / _NSIG_BPW)

typedef struct {
        unsigned long sig[_NSIG_WORDS];
} sigset_t;

4.逐case详细分析

4.1.Case 1: 单字信号集 (_NSIG_WORDS = 1)

c 复制代码
case 1: ready = signal->sig[0] &~ blocked->sig[0];
  • 获取未阻塞的待处理信号63-0

4.2.Case 2: 双字信号集 (_NSIG_WORDS = 2)

c 复制代码
case 2: ready  = signal->sig[1] &~ blocked->sig[1];
        ready |= signal->sig[0] &~ blocked->sig[0];
  • 两个字的按位或结果

4.3.Case 4: 四字信号集(_NSIG_WORDS = 4)

c 复制代码
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];

适用场景:支持更多信号的系统

4.4.Default Case: 通用处理

c 复制代码
default:
    for (i = _NSIG_WORDS, ready = 0; --i >= 0 ;)
        ready |= signal->sig[i] &~ blocked->sig[i];

适用场景:支持任意数量信号字的系统

二、信号状态重计算recalc_sigpending

c 复制代码
#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);
        else
                clear_tsk_thread_flag(t, TIF_SIGPENDING);
}
void recalc_sigpending(void)
{
        recalc_sigpending_tsk(current);
}

1.宏定义解析

c 复制代码
#define PENDING(p,b) has_pending_signals(&(p)->signal, (b))
  • 简化调用 :封装 has_pending_signals 函数
  • 参数
    • p: 待处理信号集结构指针
    • b: 阻塞信号集指针
  • 作用:检查指定信号集中是否有未阻塞的待处理信号

2.核心判断逻辑

函数检查三种情况,只要满足任一条件就设置 TIF_SIGPENDING 标志:

2.1.条件1: 进程组停止计数

c 复制代码
t->signal->group_stop_count > 0

含义:进程组有停止的进程

  • 当进程组收到 SIGSTOPSIGTSTP 等停止信号时递增
  • 表示有进程需要被停止或恢复

2.2.条件2: 私有待处理信号

c 复制代码
PENDING(&t->pending, &t->blocked)

含义:进程有未阻塞的私有待处理信号

  • t->pending: 进程特有的待处理信号
  • t->blocked: 进程阻塞的信号掩码

2.3.条件3: 共享待处理信号

c 复制代码
PENDING(&t->signal->shared_pending, &t->blocked)

含义:进程组有未阻塞的共享待处理信号

  • t->signal->shared_pending: 进程组共享的待处理信号
  • 线程组中所有线程共享的信号

3.标志设置函数

设置信号挂起标志

c 复制代码
set_tsk_thread_flag(t, TIF_SIGPENDING)
  • TIF_SIGPENDING: 线程信息标志,表示有信号需要处理
  • 设置后,在从内核返回用户空间时会检查并处理信号

清除信号挂起标志

c 复制代码
clear_tsk_thread_flag(t, TIF_SIGPENDING)
  • 当没有需要处理的信号时清除标志

三、实现进程的挂起refrigerator

c 复制代码
void refrigerator(unsigned long flag)
{
        /* Hmm, should we be allowed to suspend when there are realtime
           processes around? */
        long save;
        save = current->state;
        current->state = TASK_UNINTERRUPTIBLE;
        pr_debug("%s entered refrigerator\n", current->comm);
        printk("=");
        current->flags &= ~PF_FREEZE;

        spin_lock_irq(&current->sighand->siglock);
        recalc_sigpending(); /* We sent fake signal, clean it up */
        spin_unlock_irq(&current->sighand->siglock);

        current->flags |= PF_FROZEN;
        while (current->flags & PF_FROZEN)
                schedule();
        pr_debug("%s left refrigerator\n", current->comm);
        current->state = save;
}

1.保存当前状态

c 复制代码
long save;
save = current->state;
  • 保存进程原状态,用于恢复时还原进程状态

2.设置为不可中断状态

c 复制代码
current->state = TASK_UNINTERRUPTIBLE;
  • TASK_UNINTERRUPTIBLE:表示进程处于不可中断状态
  • 调度时不会被信号唤醒

3.调试信息输出

c 复制代码
pr_debug("%s entered refrigerator\n", current->comm);
printk("=");
  • 调试日志:记录进程开始挂起

4.清除冻结标志

c 复制代码
current->flags &= ~PF_FREEZE;
  • PF_FREEZE:表示进程应该被冻结
  • 清除此标志,冻结过程已经开始

5.信号清理

c 复制代码
spin_lock_irq(&current->sighand->siglock);
recalc_sigpending(); /* We sent fake signal, clean it up */
spin_unlock_irq(&current->sighand->siglock);
  • 获取信号锁
  • 重新计算信号状态:清理可能用于唤醒进程的伪信号
  • 释放信号锁

6.设置冻结完成标志

c 复制代码
current->flags |= PF_FROZEN;
  • PF_FROZEN:表示进程已完全冻结
  • 通知冻结机制此进程已准备就绪

7.等待唤醒循环

c 复制代码
while (current->flags & PF_FROZEN)
    schedule();
  • 循环检查 :持续检查 PF_FROZEN 标志
  • 主动调度 :调用 schedule() 让出CPU
  • 退出条件 :当其他代码清除 PF_FROZEN 标志时退出循环

8.恢复状态

c 复制代码
pr_debug("%s left refrigerator\n", current->comm);
current->state = save;
  • 调试日志:记录进程解冻
  • 恢复原状态:将进程状态恢复为进入前的值

四、从信号队列中移除指定信号rm_from_queue

c 复制代码
static inline int sigtestsetmask(sigset_t *set, unsigned long mask)
{
	return (set->sig[0] & mask) != 0;
}
static inline void sigdelsetmask(sigset_t *set, unsigned long mask)
{
	set->sig[0] &= ~mask;
}
#define sigmask(sig)	(1UL << ((sig) - 1))
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);
}
static int rm_from_queue(unsigned long mask, struct sigpending *s)
{
        struct sigqueue *q, *n;

        if (!sigtestsetmask(&s->signal, mask))
                return 0;

        sigdelsetmask(&s->signal, mask);
        list_for_each_entry_safe(q, n, &s->list, list) {
                if (q->info.si_signo < SIGRTMIN &&
                    (mask & sigmask(q->info.si_signo))) {
                        list_del_init(&q->list);
                        __sigqueue_free(q);
                }
        }
        return 1;
}

1.辅助函数解析

1.1.信号掩码测试

c 复制代码
static inline int sigtestsetmask(sigset_t *set, unsigned long mask)
{
    return (set->sig[0] & mask) != 0;
}
  • 功能:检查信号集中是否有指定的信号
  • 返回:有信号返回1,没有返回0

1.2.信号掩码删除

c 复制代码
static inline void sigdelsetmask(sigset_t *set, unsigned long mask)
{
    set->sig[0] &= ~mask;
}
  • 功能:从信号集中清除指定的信号位
  • 操作:按位与掩码的反码

1.3.信号掩码生成

c 复制代码
#define sigmask(sig) (1UL << ((sig) - 1))
  • 功能:将信号编号转换为位掩码
  • 计算:信号1 → 位0,信号2 → 位1,依此类推

1.4.信号队列释放

c 复制代码
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);
}
  • 预分配检查:预分配的信号结构不释放
  • 引用计数:减少用户的待处理信号计数
  • 用户释放:释放用户引用
  • 内存释放:返回信号队列缓存

2.主函数详细分析

2.1.步骤1: 快速检查

c 复制代码
if (!sigtestsetmask(&s->signal, mask))
    return 0;
  • 早期返回:如果没有目标信号,立即返回0
  • 性能优化:避免不必要的队列遍历

2.2.步骤2: 清除信号掩码

c 复制代码
sigdelsetmask(&s->signal, mask);
  • 更新信号集:从位图中移除指定的信号

2.3.步骤3: 遍历信号队列

c 复制代码
list_for_each_entry_safe(q, n, &s->list, list) {
    if (q->info.si_signo < SIGRTMIN &&
        (mask & sigmask(q->info.si_signo))) {
        list_del_init(&q->list);
        __sigqueue_free(q);
    }
}
2.3.1.循环宏
c 复制代码
list_for_each_entry_safe(q, n, &s->list, list)
  • 安全遍历:允许在遍历时删除节点
  • q:当前信号队列条目
  • n:下一个条目(用于安全删除)
2.3.2.条件判断
c 复制代码
if (q->info.si_signo < SIGRTMIN &&
    (mask & sigmask(q->info.si_signo)))

两个条件

  1. 只处理标准信号q->info.si_signo < SIGRTMIN
  2. 信号在删除掩码中mask & sigmask(q->info.si_signo)
2.3.3.删除操作
c 复制代码
list_del_init(&q->list);   // 从链表中移除
__sigqueue_free(q);        // 释放信号结构
相关推荐
前行居士2 小时前
Sub-process /usr/bin/dpkg returned an error code (1)
linux·运维·windows
tt5555555555553 小时前
Linux 驱动开发入门:LCD 驱动与内核机制详解
linux·运维·驱动开发
行者..................4 小时前
petalinux 安装Armadillo
linux·运维·服务器
xiatianit4 小时前
【centos生产环境搭建(三)jdk环境配置】
linux
zhaotiannuo_19984 小时前
linux centos 7 解决终端提示符出现-bash-4.2的问题
linux·centos·bash
wangjialelele4 小时前
OSI模型、网络地址、与协议
linux·服务器·网络·tcp/ip
何中应4 小时前
CentOS安装Jenkins
linux·centos·jenkins
不枯石5 小时前
Matlab通过GUI实现点云的ICP配准
linux·前端·图像处理·计算机视觉·matlab
ajassi20006 小时前
开源 C++ QT QML 开发(一)基本介绍
linux·qt·开源·qml