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

Linux 信号机制

引言

在 Linux 操作系统的世界中,信号(Signal)机制是进程间通信(IPC)和内核与用户空间交互的基础组成部分。作为一个高效、异步的事件通知系统,信号允许内核或进程向另一个进程发送简短的消息,通知其发生特定事件,如硬件异常、软件中断或用户干预。这不仅仅是技术细节,更是确保系统稳定性和响应性的关键。想象一下,当一个进程试图除以零时,内核如何即时响应?或者当用户按下 Ctrl+C 时,如何优雅地终止程序?这些都依赖于信号机制。

为什么 Linux 信号机制如此重要?首先,它是 POSIX 标准的一部分,确保了跨 Unix-like 系统的兼容性。其次,在多任务环境中,信号提供了一种轻量级的异步通信方式,比管道或消息队列更高效。再次,随着云计算和容器化的兴起,如 Docker 和 Kubernetes,理解信号有助于调试 Pod 终止或资源限制问题。

Linux 信号机制的基本概念

信号在 Linux 中的历史可以追溯到 Unix 的早期版本。Linux 内核从 0.01 版开始就支持信号,随着版本演进,如从 2.6 到 6.x,信号处理不断优化,支持更多实时特性。

信号的定义与作用

信号是一种软件中断,用于异步通知进程事件发生。每个信号是一个整数值,从 1 到 64(在 x86_64 上),对应特定含义。例如,SIGINT (2) 表示键盘中断,SIGKILL (9) 表示强制终止。

信号的作用包括:

  1. 异常处理:如 SIGSEGV (11) 处理段错误(Segmentation Fault)。

  2. 进程控制:SIGSTOP (19) 暂停进程,SIGCONT (18) 继续。

  3. 定时器与报警:SIGALRM (14) 用于 alarm() 函数。

  4. IPC:进程间发送自定义信号,如 SIGUSR1 (10)。

信号是异步的:进程在接收信号时,可能正在执行其他代码,内核会中断其执行,转而调用信号处理函数(Handler)。

信号的生命周期

一个信号的生命周期包括:

  • 产生(Generation):由内核或进程生成。

  • 投递(Delivery):信号被发送到目标进程。

  • 挂起(Pending):如果进程阻塞信号,它会挂起等待。

  • 处理(Handling):进程执行默认动作、忽略或自定义处理。

内核使用 sigpending 结构维护每个进程的信号队列。

与中断的区别

信号类似于硬件中断,但它是软件实现的。硬件中断由 CPU 处理,信号由内核调度器管理。

Linux 信号的类型

Linux 支持的标准信号有 31 个(1-31),实时信号从 32 到 64。使用 kill -l 命令列出所有信号。

标准信号

标准信号是非实时的,不支持排队,如果多个相同信号挂起,只处理一个。

分类:

  1. 终止信号

    • SIGTERM (15):优雅终止,允许清理。

    • SIGKILL (9):强制杀死,不可捕获或忽略。

    • SIGINT (2):Ctrl+C 产生。

  2. 异常信号

    • SIGSEGV (11):无效内存访问。

    • SIGBUS (7):总线错误,如未对齐访问。

    • SIGFPE (8):浮点异常,如除零。

  3. 作业控制信号

    • SIGSTOP (19):暂停进程,不可忽略。

    • SIGTSTP (20):Ctrl+Z 产生。

    • SIGCONT (18):继续暂停进程。

  4. 报警与定时

    • SIGALRM (14):alarm() 超时。

    • SIGVTALRM (26):虚拟定时器。

  5. 用户定义

    • SIGUSR1 (10)、SIGUSR2 (12):自定义用途。

  6. 其他

    • SIGHUP (1):终端挂起或控制进程死亡。

    • SIGPIPE (13):向无读进程写管道。

    • SIGCHLD (17):子进程状态变化。

每个信号有默认动作:Term(终止)、Ign(忽略)、Core(终止并 dump core)、Stop(停止)、Cont(继续)。

实时信号

实时信号(RT Signals)从 SIGRTMIN (34) 到 SIGRTMAX (64),支持 POSIX.1b 标准。

特点:

  • 排队:多个相同信号会排队,不丢失。

  • 优先级:较低编号优先。

  • 携带数据:使用 sigqueue() 发送时,可附带 union sigval 数据。

实时信号用于高优先级任务,如实时系统(RT Linux)。

信号的可靠性

不可靠信号:标准信号,可能丢失(如果相同信号多次产生,只投递一次)。

可靠信号:实时信号,不会丢失。

信号的产生和发送

信号可以由内核、进程或用户产生。

产生方式

  1. 硬件异常:如除零(SIGFPE)、无效内存(SIGSEGV),由 CPU 陷阱触发内核。

  2. 软件条件:如 alarm() 超时产生 SIGALRM。

  3. 终端输入:Ctrl+C (SIGINT)、Ctrl+Z (SIGTSTP)。

  4. 进程发送:使用 kill()、raise()、sigqueue()。

  5. 内核事件:子进程结束(SIGCHLD)、I/O 就绪(SIGIO)。

发送函数

  1. kill(pid, sig):向 pid 发送 sig。pid>0:特定进程;pid=0:同组进程;pid=-1:所有进程(需权限);pid< -1:进程组。

    示例:

    #include <signal.h>
    #include <sys/types.h>
    kill(getpid(), SIGUSR1);
    
  2. raise(sig):向自身发送,等同 kill(getpid(), sig)。

  3. sigqueue(pid, sig, value):发送实时信号,value 是 sigval(整数或指针)。

    示例:

    union sigval val;
    val.sival_int = 42;
    sigqueue(pid, SIGRTMIN, val);
    
  4. killpg(pgid, sig):向进程组发送。

  5. pthread_kill(thread, sig):向线程发送(多线程环境)。

权限:发送者需与接收者相同 UID,或 root。

信号的处理

进程收到信号后,可采取三种方式:默认、忽略、捕获。

默认处理

每个信号有默认动作,如 SIGTERM 终止进程。

忽略信号

使用 signal() 或 sigaction() 设置 SIG_IGN。

示例:

signal(SIGINT, SIG_IGN);

注意:SIGKILL 和 SIGSTOP 不可忽略。

捕获信号

设置自定义处理函数。

  1. signal() 函数:简单但不推荐(不安全,异步信号问题)。

    void handler(int sig) { /* 处理 */ }
    signal(SIGINT, handler);
    
  2. sigaction():推荐,POSIX 兼容。

    结构 sigaction:

    struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
    };
    

    示例:

    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);
    

    sa_flags:

    对于实时信号,sa_flags | SA_SIGINFO 获取数据。

    • SA_RESTART:系统调用被中断后重启。

    • SA_SIGINFO:使用 sa_sigaction,获取 siginfo_t(信号信息,如发送者 pid)。

    • SA_NOCLDSTOP:不因子进程停止产生 SIGCHLD。

信号处理函数执行时,信号被阻塞,防止重入。sa_mask 指定额外阻塞信号。

信号处理的安全性

处理函数应异步安全:避免 malloc、printf 等非 reentrant 函数。使用 write() 或 sig_atomic_t 变量。

信号的阻塞和挂起

进程可阻塞信号,防止投递。

信号掩码

每个进程/线程有信号掩码(sigset_t),阻塞的信号集。

操作函数:

  • sigemptyset(set):清空。

  • sigfillset(set):填充所有。

  • sigaddset(set, sig):添加。

  • sigdelset(set, sig):删除。

  • sigismember(set, sig):检查。

设置掩码

  1. sigprocmask(how, new, old)

    示例:

    sigset_t newmask;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    sigprocmask(SIG_BLOCK, &newmask, NULL);
    
    • how: SIG_BLOCK(添加阻塞)、SIG_UNBLOCK(移除)、SIG_SETMASK(设置)。

  2. sigsuspend(set):原子替换掩码并暂停等待信号。

阻塞信号会挂起(pending),使用 sigpending(set) 检查。

高级信号机制

多线程中的信号

在 pthread 中,信号掩码是 per-thread 的,但信号投递到进程,任一非阻塞线程处理。

使用 pthread_sigmask() 设置线程掩码。

专用信号处理线程:阻塞所有线程信号,除一个线程处理。

实时信号扩展

sigqueue() 发送数据,接收时 siginfo_t.si_value 获取。

队列:使用 sigtimedwait() 或 sigwaitinfo() 等待特定信号。

示例:

sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGRTMIN);
siginfo_t info;
sigwaitinfo(&set, &info);
printf("Value: %d\n", info.si_value.sival_int);

信号与系统调用

慢系统调用(如 read())被信号中断,返回 EINTR。使用 SA_RESTART 自动重启。

信号栈

默认处理在用户栈上,SA_ONSTACK 使用备用栈(sigaltstack() 设置)。

用于栈溢出处理。

信号在内核中的实现

内核信号结构:task_struct->signal、pending、blocked。

发送:send_signal() 添加到队列。

检查:信号在返回用户空间时检查(syscall exit 或中断返回)。

TIF_SIGPENDING 标志触发 do_signal() 处理。

常见问题与解决方案

问题 1: 信号丢失

原因:标准信号不排队。

解决方案:使用实时信号。

问题 2: 竞争条件

原因:信号异步。

解决方案:使用 sigatomic_t,阻塞信号关键区。

问题 3: SIGCHLD 处理

子进程结束,wait() 回收避免僵尸。

示例:handler 中 waitpid(-1, NULL, WNOHANG)。

问题 4: 信号与 fork/exec

fork 继承掩码和处理,但 pending 清空。

exec 重置处理为默认,掩码保留。

问题 5: 调试信号

使用 strace -e signal 追踪。

gdb:handle SIGINT nostop。

优化技巧与高级应用

优化信号处理

  • 最小化处理函数:快速返回,避免阻塞。

  • 使用 signalfd():将信号转换为文件描述符,集成 select/epoll。

    示例:

    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigprocmask(SIG_BLOCK, &mask, NULL);
    int sfd = signalfd(-1, &mask, 0);
    // read(sfd, &fdinfo, sizeof(struct signalfd_siginfo));
    
  • timerfd:定时器信号转换为 fd。

应用场景

  1. 守护进程:SIGHUP 重载配置。

  2. 网络服务器:SIGUSR1 转储统计。

  3. 实时系统:实时信号优先调度。

  4. 容器管理:Kubernetes 使用 SIGTERM 优雅关停。

与其他 IPC 比较

信号 vs 管道:信号异步,轻量,但无数据传输(除实时)。

信号 vs 消息队列:信号更快,但队列有限。

实际案例分析

案例 1: Ctrl+C 处理

简单 shell:捕获 SIGINT 打印消息,继续运行。

代码:

#include <signal.h>
#include <stdio.h>
void handler(int sig) { printf("Caught SIGINT\n"); }
int main() {signal(SIGINT, handler);while(1) pause();
}

案例 2: 定时器

使用 alarm() 发送 SIGALRM。

代码:

void alarm_handler(int sig) { printf("Alarm!\n"); }
int main() {signal(SIGALRM, alarm_handler);alarm(5);pause();
}

案例 3: 子进程管理

父进程等待 SIGCHLD。

代码:

#include <sys/wait.h>
void child_handler(int sig) {wait(NULL);
}
int main() {signal(SIGCHLD, child_handler);if (fork() == 0) exit(0);pause();
}

案例 4: 实时信号通信

进程间传递数据。

发送端:

union sigval val = { .sival_int = 123 };
sigqueue(pid, SIGRTMIN, val);

接收端:使用 sigwaitinfo 获取。

案例 5: 多线程信号

主线程阻塞,worker 处理。

代码:

#include <pthread.h>
void* thread_func(void* arg) {sigset_tset;sigemptyset(&set);sigaddset(&set, SIGUSR1);int sig;sigwait(&set, &sig);printf("Received in thread\n");
}
int main() {sigset_t mask;sigfillset(&mask);pthread_sigmask(SIG_BLOCK, &mask, NULL);pthread_t t;pthread_create(&t, NULL, thread_func, NULL);// send signalpthread_join(t, NULL);
}
http://www.dtcms.com/a/618651.html

相关文章:

  • SpringBoot19-HttpClient 详解及 SpringBoot 使用指南
  • 17做网店一样的网站网站按域名跳转不同的页面
  • 13.2 国产之光崛起:深度求索与通义千问的技术突破
  • 旅游网站建设的结论阿里云商标注册官网
  • 第五次:郑州银行杯2025郑州马拉松
  • Three.js使用教程
  • Reqable 工具报错 Netbare Code Error Unknown
  • 宝山网页设计制作黄石seo诊断
  • git-Git约定式提交
  • wap建站教程0元玩手游平台
  • nw.js桌面软件开发系列 第.节 HTML和桌面软件开发的碰撞
  • 设计一套网站费用北京网页
  • 7.3、Python-函数的返回值
  • 网站建设咨询话术技巧网站开发程序设计
  • 【Qt】配置安卓开发环境
  • 基于Qt,调用千问7B大模型,实现智能对话
  • Ubuntu 美化
  • 网站互动营销专门app软件开发公司
  • .net开发微信网站流程网站怎么做收费
  • 变分自编码器(VAE)的原理方法(一)
  • OpenCV 张氏标定法(三)
  • 网站做成app网站建设与管理设计
  • 建设礼品网站的策划书如何用阿里云做网站
  • C++:智能指针的使用及其原理
  • 25.Linux逻辑卷管理
  • 苏州旺道seo做网站排名优化的公司
  • 6. Linux 硬盘分区管理
  • 中山微网站建设报价银行网站建设前期合同
  • 25年11月软考架构真题《论秒杀场景及其技术解决方案》考后复盘总结
  • 怎么做公司网站竞价最新国际新闻10条简短