一、信号机制概述与架构全景

信号(Signal)是 Linux/Unix 系统中一种异步通知机制,用于通知进程发生了某种事件。它是最古老的进程间通信方式之一,也是内核调度的核心组成部分。信号机制的设计体现了Unix哲学中"一切皆文件"与"事件驱动"两大核心理念。

1.1 信号的生命周期

信号的完整生命周期包含五个阶段:

  • 生成(Generation):内核或另一个进程通过 kill()/tgkill()/sigqueue() 发送信号,内核调用 send_signal() 将信号加入目标进程的 pending 队列
  • 挂起(Pending):信号保持在 pending 集合中,等待目标进程的处理时机。如果同一信号在阻塞期间多次产生,标准信号仅记录一次,实时信号则排队保留
  • 递送(Delivery):当进程从内核态返回用户态时(系统调用返回或中断返回),内核检查 pending 信号并执行处理函数
  • 处理(Handling):进程执行注册的信号处理函数(signal handler),或执行默认动作(终止、忽略、core dump、停止、继续)
  • 完成(Completion):信号处理函数返回后,若为一次性处理(SA_RESETHAND),则恢复默认处理;若为 SA_RESTART,被中断的系统调用自动重启

1.2 信号在内核中的数据结构

信号相关的核心数据结构贯穿进程描述符(task_struct):


struct task_struct {
    struct signal_struct *signal;       // 共享信号状态(多线程共享)
    sigpending      pending;            // 私有挂起信号队列
    struct sigaction  sighand[SIGRTMAX]; // 信号处理动作表
    sigset_t        blocked;            // 信号掩码(阻塞集合)
    ...
};

struct signal_struct {
    atomic_t        sigcnt;             // 共享该 signal_struct 的线程数
    atomic_t        live;               // 存活线程计数
    wait_queue_head_t   wait_chldexit;  // 等待子进程退出队列
    struct sigpending  shared_pending;  // 共享挂起信号(线程组级别)
    struct hlist_head    multiprocess;  // 多进程链表
    int              group_exit_code;   // 组退出码
    struct tty_struct *tty;             // 关联终端
    ...
} ____cacheline_aligned_refcounted;

struct sigpending {
    struct list_head list;  // sigqueue 链表
    sigset_t signal;        // 挂起信号的 bitmask
};

标准信号(SIGKILL 之前)与实时信号(SIGRTMIN~SIGRTMAX,通常32~63)的关键区别在于:标准信号不排队——如果同一个信号在阻塞期间多次到达,只会被pending一次;实时信号则支持排队,每次发送都会创建一个新的 sigqueue 节点。

二、信号的生成与投递机制

2.1 内核信号发送路径

信号发送的核心路径如下:


用户空间                    内核空间
signal()/raise()    ————>   do_send_sig_info()
kill()/tgkill()     ————>   do_send_sig_info()
sigqueue()          ————>   do_send_sig_info()
                              ——————————————————
                                     |
                                    V
                            send_signal(sig, info, t)
                                     |
                                    V
                              __group_send_sig_info(sig, info, t)
                                     |
                                     |--- 检查 SIGKILL/SIGSTOP 的特殊处理
                                     |
                                     |--- 对快速路径:检查目标进程的信号处理是否可合并
                                     |
                                    V
                         complete_signal() / signal_wake_up()
                                     |
                                     |--- 将目标进程设为 TASK_INTERRUPTIBLE
                                     |--- 唤醒目标进程(TIF_SIGPENDING flag)

2.2 send_signal() 详细实现流程


static int send_signal(int sig, struct kernel_siginfo *info,
                       struct task_struct *t, enum pid_type type)
{
    struct sigpending *pending;
    struct sigqueue *q;

    // 1. 信号有效性检查
    if (!valid_signal(sig))
        return -EINVAL;

    // 2. 选择挂起队列:线程私有 vs 线程组共享
    //    type=PIDTYPE_PID → pending = &t->pending(私有队列)
    //    type=PIDTYPE_TGID → pending = &t->signal->shared_pending(共享队列)
    pending = (type != PIDTYPE_PID) ?
              &t->signal->shared_pending : &t->pending;

    // 3. 如果是标准信号且已在 pending 中,直接返回(不排队)
    if (legacy_queue(pending, sig))
        return 0;

    // 4. 分配 sigqueue 节点(GFP_ATOMIC | __GFP_NOTRACK)
    q = __sigqueue_alloc(sig, t, GFP_ATOMIC, NULL, override_rlimit);

    if (q) {
        // 5a. 成功分配,加入链表
        list_add_tail(&q->list, &pending->list);
        // 5b. 拷贝信号信息
        switch (siginfo_zero_test(info)) {
        case 1: copy_siginfo(&q->info, info); break;  // 用户态来源
        case SIGKILL: case SIGSTOP: /* 直接设置 info pointer */ break;
        }
    } else if (sig >= SIGRTMIN && (info->si_code != SI_USER)) {
        // 5c. 实时信号且来自内核/非资源超限 → 调用 __send_signal 处理
        ret = __send_signal(sig, info, t, type, override_rlimit);
        // 6. 设置对应比特位
        sigaddset(&pending->signal, sig);
    } else {
        ret = -EAGAIN; // 内存不足,丢弃
    }
    return ret;
}

2.3 complete_signal() 与唤醒机制


static void complete_signal(int sig, struct task_struct *p)
{
    struct signal_struct *signal = p->signal;

    // 1. 如果目标正在退出,跳过
    if (unlikely(signal->flags & SIGNAL_GROUP_EXIT))
        return;

    // 2. 选择唤醒线程:优先唤醒未阻塞该信号的线程
    //    从 signal->thread_head 链表中遍历线程组
    if (wants_signal(sig, p))
        return;  // 继续唤醒逻辑

    // 3. 在所有线程中找一个未阻塞该信号的线程唤醒
    signal_wake_up_state(p, __fatal_signal_pending(p) ?
                        TASK_WAKE_KILL : __TASK_STOPPED | TASK_INTERRUPTIBLE);

    // 4. SIGKILL/SIGSTOP 特殊处理:唤醒所有线程
    if (sig == SIGKILL)
        signal_wake_up(p, 1);
}

三、信号处理函数与执行上下文

3.1 sigaction 结构体详解


struct sigaction {
    union {
        __sighandler_t sa_handler;     // 简单处理:SIG_DFL/SIG_IGN/函数指针
        void (*sa_sigaction)(int, siginfo_t *, void *);  // SA_SIGINFO 模式
    } __sigaction_handler;
    sigset_t   sa_mask;                // 处理函数执行期间额外阻塞的信号
    int        sa_flags;               // 控制标志位
    void     (*sa_restorer)(void);     // 恢复函数(已废弃,保留历史兼容)
};

3.2 sa_flags 关键标志位

标志位功能说明使用场景
SA_RESTART被信号中断的系统调用自动重启(如 read/write/select)替代手动 EINTR 重试循环,简化编程模型
SA_RESETHAND处理函数执行完毕后,恢复为 SIG_DFL(一次性处理)调试场景,防止处理函数递归执行
SA_NODEFER处理期间不自动阻塞同种信号(允许递归进入)实现定时器链式触发、长期运行的异步任务
SA_SIGINFO使用 sa_sigaction 三参数版本,附带 siginfo_t 上下文需要获取发送者 PID、信号来源 UID 的场景
SA_NOCLDWAIT子进程退出时不产生 SIGCHLD,不变成僵尸进程服务器守护进程忽略子进程终止
SA_ONSTACK在替代信号栈(sigaltstack)上执行信号处理函数主栈溢出、线程信号栈隔离、多线程安全
SA_NOCLDSTOP子进程停止/继续时不产生 SIGCHLD作业控制场景,减少不必要的中断

3.3 信号栈(Signal Stack)

当信号处理函数执行时,内核需要为信号处理提供一个执行上下文。默认情况下,信号处理在用户线程栈上执行,这在以下场景会出问题:

  • 线程栈已满(信号处理→立即栈溢出→SIGSEGV→递归)
  • 多线程信号栈与线程栈冲突
  • 特殊架构(如 x86 的红区 red zone)与信号处理冲突

解决方案是通过 sigaltstack() 注册一个替代信号栈:


#include <signal.h>

stack_t ss;
static char alt_stack[SIGSTKSZ * 4];  // 16KB 替代信号栈

ss.ss_sp   = alt_stack;
ss.ss_size = sizeof(alt_stack);
ss.ss_flags = 0;
sigaltstack(&ss, NULL);

// 注册处理函数时指定 SA_ONSTACK
struct sigaction sa;
sa.sa_sigaction = signal_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);

四、信号掩码与阻塞机制

4.1 sigset_t 信号集操作

信号集通过 sigset_t 位掩码表示,提供以下原子操作函数:


#include <signal.h>

sigset_t set;
sigemptyset(&set);              // 清空信号集
sigfillset(&set);               // 包含所有信号
sigaddset(&set, SIGINT);        // 添加 SIGINT
sigdelset(&set, SIGTERM);       // 移除 SIGTERM
int ret = sigismember(&set, SIGUSR1);  // 检查是否包含某信号

// 原子的按位操作(注意不是系统调用,而是内联函数)
sigset_t src, dst;
sigandset(&dst, &a, &b);         // 交集
sigrandset(&dst, &a, &b);        // 重命名后的交集
sigorset(&dst, &a, &b);          // 并集
sigandset(&dst, &a);             // dst &= a

4.2 sigprocmask() 与 pthread_sigmask()

多线程环境下信号掩码操作需要特别注意:


// 单线程/进程内主线程:使用 sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
// how: SIG_BLOCK(添加), SIG_UNBLOCK(移除), SIG_SETMASK(替换)

// 多线程程序:每个线程有独立的阻塞掩码,必须用 pthread_sigmask
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

// 正确的多线程信号处理实践:
// 1. 主线程在创建子线程前,阻塞所有需要在特定线程处理的信号
// 2. 各个线程通过 pthread_sigmask 单独解除阻塞
// 3. 信号处理函数保持最小化——仅设置标志位或写管道

// 示例:创建时隔离信号
sigset_t all_signals;
sigfillset(&all_signals);
pthread_sigmask(SIG_BLOCK, &all_signals, NULL);  // 主线程阻塞所有

// 后来创建的工作线程各自解开需要的信号
sigset_t worker_set;
sigfillset(&worker_set);
sigdelset(&worker_set, SIGQUIT);
sigdelset(&worker_set, SIGUSR1);
pthread_sigmask(SIG_SETMASK, &worker_set, NULL);  // 仅解除需要的

五、实时信号与队列管理

5.1 实时信号队列的排队与丢弃策略

实时信号(SIGRTMIN 到 SIGRTMAX,通常 32~63)的核心优势是排队:每次发送 sigqueue() 都会创建独立的 sigqueue 内核节点。队列上限受以下约束:


// 查看/修改实时信号队列上限
ulimit -a | grep "pending signals"   // 默认: 通常 65536

// /proc/sys/kernel/rtsig-max        // 全局上限
// /proc/sys/kernel/rtsig-nr         // 当前使用计数
// RLIMIT_SIGpending                  // 每进程限制(setrlimit)

// 队列满时的行为:
// 1. 如果 sigqueue() 指定的结构有效,返回 EAGAIN
// 2. 如果发送者无权限(kill权限检查失败),返回 EPERM
// 3. stdlib abort(): 某些实现会在队列满时调用 _exit(SIGRTMAX+1)

5.2 siginfo_t 上下文传递

SA_SIGINFO 模式允许在信号处理函数中获取丰富的上下文信息:


// siginfo_t 核心字段(字段名因信号类型不同而不同)
void handler(int signo, siginfo_t *info, void *ucontext) {
    // 通用字段
    int si_signo;      // 信号编号
    int si_code;       // 信号来源代码
    pid_t si_pid;      // 发送者 PID(kill/sigqueue 用户态)
    uid_t si_uid;      // 发送者 UID

    // 特定信号类型字段
    // SI_USER: 由 kill()/raise() 发送
    // SI_KERNEL: 由内核直接发送(如 SIGSEGV)
    // SI_QUEUE: 由 sigqueue() 发送
    // SI_TIMER: 由 POSIX 定时器到期
    // SI_ASYNCIO: 由 AIO 完成
    // SI_MESGQ: 由 POSIX 消息队列

    if (info->si_code == SI_QUEUE) {
        // sigqueue 发送时附加的数据
        int value = info->si_value.sival_int;
        void *ptr = info->si_value.sival_ptr;
        // 可用于命令通道、事件通知等
    }

    if (info->si_code == SI_KERNEL && info->si_signo == SIGSEGV) {
        // 内核发送的段错误
        void *addr = info->si_addr;  // 导致故障的虚拟地址
        // 可用于 mmap 保护页面的处理、JIT 编译器的异常触发
    }
}

六、signalfd:文件描述符化信号监听

6.1 signalfd 的设计哲学

signalfd 是 Linux 特有的扩展,它将信号封装为文件描述符,使得信号可以通过 read()、epoll、select() 等 I/O 多路复用机制监听,完美融合了

  • 事件驱动架构:将信号转换为可读事件,避免异步处理函数带来的竞态条件
  • 与 epoll 集成:与网络、定时器、自定义 fd 统一管理,简化事件循环
  • 线程隔离:多个线程可独立创建 signalfd,互不干扰
  • 避免信号安全(async-signal-safe)限制:处理不再局限于 async-signal-safe 函数

6.2 signalfd API 使用指南


#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int create_signalfd(sigset_t *mask) {
    // 1. 必须先阻塞这些信号(防止被默认处理)
    if (sigprocmask(SIG_BLOCK, mask, NULL) == -1) {
        perror("sigprocmask");
        return -1;
    }

    // 2. 创建 signalfd(-1 = 新建,fd = 已存在的 fd 进行修改)
    int sfd = signalfd(-1, mask, SFD_NONBLOCK | SFD_CLOEXEC);
    if (sfd == -1) {
        perror("signalfd");
        return -1;
    }
    return sfd;
}

// 读取信号信息
void read_signal(int sfd) {
    struct signalfd_siginfo sfd_info;
    ssize_t s;

    while ((s = read(sfd, &sfd_info, sizeof(sfd_info))) > 0) {
        if (s != sizeof(sfd_info)) {
            fprintf(stderr, "Read size mismatch\n");
            continue;
        }

        printf("Received signal %d from PID %d (UID %d)\n",
               sfd_info.ssi_signo,
               sfd_info.ssi_pid,
               sfd_info.ssi_uid);

        // 处理信号
        switch (sfd_info.ssi_signo) {
        case SIGINT:
        case SIGTERM:
            // 优雅退出
            goto cleanup;
        case SIGUSR1:
            // 重新加载配置
            reload_config();
            break;
        case SIGRTMIN:
            // 实时信号,获取附加数据
            printf("Data: %d\n", sfd_info.ssi_int);
            break;
        }
    }
}

6.3 signalfd + epoll 完整事件循环


#include <sys/epoll.h>
#include <sys/signalfd.hpp>
#include <signal.h>
#include <unistd.h>
...
#define MAX_EVENTS 64

int main() {
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGUSR1);
    sigprocmask(SIG_BLOCK, &mask, NULL);  // 阻塞这些信号的默认行为

    int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
    int epoll_fd = epoll_create1(EPOLL_CLOEXEC);

    // 注册 signalfd 到 epoll
    struct epoll_event ev = { .events = EPOLLIN, .data.fd = sfd };
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sfd, &ev);

    // 如果需要继续添加其他 fd(socket、timerfd、eventfd)...
    // epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_fd, &timer_ev);

    struct epoll_event events[MAX_EVENTS];
    int running = 1;

    while (running) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == sfd) {
                // 信号处理
                struct signalfd_siginfo info;
                read(sfd, &info, sizeof(info));
                if (info.ssi_signo == SIGINT || info.ssi_signo == SIGTERM)
                    running = 0;
            }
            // 可按 events[i].data.fd 分发到其他处理器
        }
    }

    close(sfd);
    close(epoll_fd);
    return 0;
}

七、生产级信号处理最佳实践

7.1 死亡信号的安全交接(SIGCHLD 处理)


/**
 * 最佳实践:在 SIGCHLD 处理中循环 waitpid() 以收割所有僵尸进程
 * 原因:标准信号不排队,多个子进程同时退出可能只触发一次处理
 */
void sigchld_handler(int signo) {
    (void)signo;
    int saved_errno = errno;    // 保存 errno(处理函数可能修改它)
    siginfo_t info;
    int status;

    // 循环收割,直到没有更多僵尸子进程
    while (1) {
        info.si_pid = 0;
        // WEXITED | WNOHANG:不阻塞,立即返回
        if (waitid(P_ALL, 0, &info, WEXITED | WNOHANG | WNOWAIT) < 0)
            break;

        if (info.si_pid == 0)
            break;  // 没有更多僵尸

        // 记录退出状态(注意:此时进程仍未完全消亡)
        // 实际收割:
        waitpid(info.si_pid, &status, WNOHANG);
    }

    errno = saved_errno;  // 恢复 errno
}

7.2 自管道技巧(Self-Pipe Trick)

在 signalfd 之前的标准做法,至今仍适用于不支持 signalfd 的场景(如某些 BSD 系统):


#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>

static int pipefd[2];  // 自管道:写端在信号处理器,读端在主循环

void signal_handler(int sig) {
    int saved_errno = errno;
    // 将信号编号写入管道(async-signal-safe 操作)
    // 忽略 write() 可能的 EINTR/ENOMEM - 主循环通过 epoll 感知
    unsigned char sig_byte = (unsigned char)sig;
    write(pipefd[1], &sig_byte, 1);
    errno = saved_errno;
}

void setup_self_pipe() {
    pipe2(pipefd, O_NONBLOCK | O_CLOEXEC);

    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sa.sa_flags = SA_RESTART;  // 避免系统调用被中断
    sigemptyset(&sa.mask);

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGHUP, &sa, NULL);  // 配置重载
    sigaction(SIGUSR1, &sa, NULL); // 日志轮转
}

// 主循环中读取管道
void event_loop() {
    char sig_buf[16];
    while (read(pipefd[0], sig_buf, sizeof(sig_buf)) > 0) {
        for (int i = 0; i < sizeof(sig_buf); i++) {
            switch (sig_buf[i]) {
                case SIGINT: case SIGTERM: goto shutdown;
                case SIGHUP: reload_config(); break;
                case SIGUSR1: rotate_log(); break;
            }
        }
    }
shutdown:;
}

7.3 多线程程序的信号分发策略

多线程环境下的信号处理设计模式:


模式一:主线程集中处理
┌─────────────────────────────────────────────────────────┐
│  主线程:epoll_wait + signalfd + signalfd_siginfo解析    │
│  工作线程:全部阻塞所有信号,专注业务逻辑                  │
│  优点:信号状态一致性,避免竞态                            │
│  缺点:主线程瓶颈                                        │
├─────────────────────────────────────────────────────────┤
│  适用场景:Web服务器(Nginx/Apache模式)                   │
│  实现:master进程监控signal + 分发至worker                │
└─────────────────────────────────────────────────────────┘

模式二:信号分发线程
┌─────────────────────────────────────────────────────────┐
│  信号监控线程:block all signals → signalfd → epoll      │
│  线程池:各线程各司其职(网络、定时、计算)                 │
│  信号类型决定转发目标:管理信号→管理线程,任务信号→工作线程  │
│  通信方式:eventfd + 队列                                  │
├─────────────────────────────────────────────────────────┤
│  适用场景:游戏服务器、交易系统                           │
└─────────────────────────────────────────────────────────┘

模式三:每线程独立 signalfd
┌─────────────────────────────────────────────────────────┐
│  各线程独立 sigset_t mask → 各自 signalfd                │
│  信号路由:通过 kill(pid/tgid) 或 tgkill() 定向发送      │
│  实时信号排队保证不丢失                                    │
│  需要注意:SIGSTOP/SIGKILL/SIGSTOP 为进程级信号            │
├─────────────────────────────────────────────────────────┤
│  适用场景:线程池、协程运行时                             │
└─────────────────────────────────────────────────────────┘

八、高级专题

8.1 rt_sigqueueinfo() 实时信号队列监控

通过 /proc/sys/kernel/rtsig-max 和 /proc/<pid>/status 可监控信号队列状态:


# 查看进程信号状态
cat /proc/<pid>/status | grep -E 'Sig|Shd'
# SigPnd: 进程级挂起信号的 bitmask
# SigBlk: 当前阻塞掩码
# SigCgt: 已捕获(非忽略)信号的 bitmask
# ShdPnd: 线程级挂起信号的 bitmask(2.6.32+)

# 查看实时信号队列使用量
cat /proc/sys/kernel/rtsig-nr    # 当前队列使用
cat /proc/sys/kernel/rtsig-max   # 全局队列最大数

8.2 信号传递的权限检查流程

内核在信号投递前进行严格的安全检查:


// __send_signal_permission_check() 核心逻辑
static bool kill_ok_by_cred(struct task_struct *t, int sig,
                            const struct kernel_siginfo *info) {
    const struct cred *cred = current_cred();
    const struct cred *tcred = __task_cred(t);

    // 1. root 用户(CAP_KILL)可以发送给任何进程
    if (cred->euid == 0 || cred->uid == tcred->suid)
        return true;

    // 2. 发送者 UID 必须等于目标 UID 或保存的 suid
    if (cred->uid == tcred->uid || cred->uid == tcred->euid ||
        cred->suid == tcred->uid || cred->suid == tcred->euid)
        return true;

    // 3. SI_USER: SIGCONT 例外(同一会话可发)
    if (info->si_code == SI_USER && sig == SIGCONT &&
        same_session(t))
        return true;

    return false;
}

8.3 与 TIOCNOTTY 和会话控制的信号交互

控制终端相关的信号:

  • SIGHUP:终端挂断时发送给会话首进程。守护进程经典用法:SIGHUP 触发配置文件重载
  • SIGTTIN/SIGTTOU:后台进程读取/写入控制终端时发送。可通过 stty -tostop 禁用
  • SIGTSTP:Ctrl+Z 触发的终端停止信号。与普通停止的区别:可被捕获和忽略

九、总结与速查表

9.1 关键函数对照

函数用途替代/演进
signal()旧版信号处理(语义因系统而异)永远使用 sigaction()
sigaction()POSIX 标准信号处理生产环境首选
sigprocmask()修改进程阻塞掩码多线程用 pthread_sigmask()
sigqueue()发送排队实时信号需要传递附带数据时
sigtimedwait()同步等待信号替代 pause()+sigsuspend()
signalfd()文件描述符化信号(Linux特有)与 epoll 集成的最佳实践
sigaltstack()注册替代信号栈栈溢出保护、多线程
pause()/sigsuspend()挂起等待信号推荐用 signalfd + epoll

9.2 信号动作速查

默认动作信号典型用途
终止+CoreSIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL程序错误/硬件异常
终止SIGTERM, SIGINT, SIGQUIT, SIGHUP标准退出、终端中断、配置重载
忽略SIGCHLD, SIGURG, SIGWINCH子进程退出、窗口大小变化
停止SIGTSTP, SIGTTIN, SIGTTOU, SIGSTOP作业控制(Ctrl+Z)、后台读写终端
继续SIGCONT恢复已停止进程
不可捕获SIGKILL, SIGSTOP强制终止/停止的最后手段

9.3 常见陷阱与注意事项

  1. 不可重入函数:信号处理函数中只能调用 async-signal-safe 函数(如 write()、signal()、_exit()),绝对不能调用 malloc()、printf()、pthread_mutex_lock()
  2. errno 保存:进入处理函数前必须保存 errno,退出时恢复。否则被中断的系统调用返回的错误码可能被覆盖
  3. 标准信号不排队:如果同一标准信号在阻塞期间多次到达,只被 pending 一次。必须使用实时信号保证可靠性
  4. 多线程的 SIGKILL/SIGSTOP:这两个信号作用于整个进程(所有线程),无法被阻塞、捕获或忽略
  5. TLS 修改约束:信号处理函数修改 TLS(线程局部存储)是安全的,因为信号处理在当前线程栈上执行
  6. SA_RESAVE 不存在:某些资料提到 SA_RETHAND,正确拼写是 SA_RESETHAND(一键恢复)

十、参考资料

  • Linux 内核源码:kernel/signal.c、include/linux/signal.h
  • The Linux Programming Interface — Michael Kerrisk, Chapters 20-22
  • UNIX Network Programming, Vol.1 — W. Stevens, Chapter 16-19
  • POSIX.1-2017 Signal Specifications

本文基于 Linux 6.x 内核源码分析,结合生产环境最佳实践,深入剖析信号机制的完整技术栈。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部