信号

信号


1. 信号基本概念

核心定义

信号是Linux/Unix系统中进程间异步通信的一种机制,事件的发生与处理无需事先协同(类似“门铃”——屋内人无法预知门铃何时响起,只能提前做好响应准备)。

输入子系统

核心特性:异步性

  • 信号的产生时机不确定(如键盘中断、子进程退出、定时器到期)。
  • 目标进程在正常执行流程中,需随时响应未决信号,无法提前统筹安排响应时机。

2. 信号分类与区别

Linux系统信号分为两大类,共64个(1-64),可通过 kill -l 命令查看所有信号。

Linux/Unix系统下,信号总共分成两大类,一类是最常用的标准信号,另一类是后面引入的实时信号,使用命令查看如下:

# 查看当前系统所支持的所有信号
gec@ubuntu:~$ kill -l
 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    

34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    

2.1 分类详情

类型 编号范围 核心特点
标准信号 1-31 1. 不排队,相同信号多次发送会被丢弃;
2. 响应可能嵌套;
3. 多数对应特定系统事件(SIGUSR1/2除外);
4. 优先级低于实时信号
实时信号 34-64 1. 排队响应,相同信号多次发送不会丢弃;
2. 按接收顺序响应,不嵌套;
3. 无默认关联系统事件,需用户自定义;
4. 又称“可靠信号”

标准信号(非实时、不可靠信号):前31个信号,它们是从Unix系统继承下来的经典系统元素,他们有如下的特点:

  • 不排队,信号的响应会相互嵌套。
  • 如果目标进程没有及时响应,那么随后到达的相同信号将会被丢弃。
  • 每个信号都对应一个系统事件(除了SIGUSR1和SIGUSR2),当这个事件发生时,将产生这个信号。
  • 在进程的挂起信号中,进程会优先响应实时信号(Ubuntu 虚拟机中确实如此,但是子系统并不是,貌似是按照到达的先后顺序)。

实时信号:后31个信号,它们是Linux系统新增的实时信号,也被称为“可靠信号”,这些信号的特征是:

  • 实时信号的响应次序按接收顺序排队,不嵌套。
  • 即使相同的实时信号被同时发送多次,也不会被丢弃(虚拟机),而会依次挨个响应。
  • 在子系统中与标准信号效果一致(需要关注到实际的运行平台)
  • 开发板与Ubuntu一致
  • 实时信号没有特殊的系统事件与之对应。
  • 同时阻塞多个实时信号时,会从大到小依次响应(值越大,优先级越高)

2.2 常用标准信号说明

信号编号 信号名 触发场景 默认动作
2 SIGINT 键盘中断(Ctrl+C) Term(终止进程)
9 SIGKILL 强制杀死进程(无法捕获/屏蔽/忽略) Term
15 SIGTERM 正常终止进程(默认kill命令发送) Term
17 SIGCHLD 子进程停止或终止 Ign(忽略)
19 SIGSTOP 暂停进程(无法捕获/屏蔽/忽略) Stop(暂停)
18 SIGCONT 恢复被暂停的进程 Cont(继续)
14 SIGALRM 定时器到期(alarm函数触发) Term
3 SIGQUIT 键盘退出(Ctrl+\) Core(终止+核心转储)

3. 信号的生命周期

信号从产生到响应完毕的完整流程:

  1. 产生信号:通过系统事件(如键盘操作、子进程退出)或函数调用(如kill、alarm)触发。
  2. 发送信号:内核将信号标记到目标进程的PCB中。
  3. 挂起信号:信号已发送但目标进程未响应(处于“未决态”)。
  4. 捕获信号:进程被调度时,内核检查未决信号,若未被屏蔽则触发处理。
  5. 响应信号:执行信号的处理动作(默认/忽略/自定义函数)。
图示

关键说明

  • 未决信号:已发送但未被处理的信号,若被屏蔽则一直处于未决态。
  • 信号屏蔽:通过信号集设置,屏蔽期间信号不会被响应,解除屏蔽后才处理。

4. 信号处理核心函数

4.1 信号捕获与处理:signal()

#include <signal.h>

// 信号处理函数类型定义(参数为信号编号)
typedef void (*sighandler_t)(int);

// 函数原型
sighandler_t signal(int signum, sighandler_t handler);

参数说明

  • signum:要处理的信号编号(如SIGINT、SIGCHLD)。
  • handler:信号处理方式(三选一):
    • 自定义处理函数地址(如myFunc);
    • SIG_IGN:忽略该信号;
    • SIG_DFL:执行信号的默认动作。

示例1:设置信号忽略

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 忽略SIGINT信号(Ctrl+C无法终止进程)
    signal(SIGINT, SIG_IGN);
    while (1) {
        printf("进程运行中...(Ctrl+C无效)\n");
        sleep(1);
    }
    return 0;
}

示例2:设置自定义响应函数

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// 自定义信号处理函数
void sig_handler(int sig_num) {
    printf("捕获到信号[%d],正在处理...\n", sig_num);
}

int main() {
    // 捕获SIGINT信号,执行自定义函数
    signal(SIGINT, sig_handler);
    while (1) {
        printf("师兄弟就来砍我...(Ctrl+C触发自定义处理)\n");
        sleep(1);
    }
    return 0;
}

4.2 信号屏蔽与信号集操作

核心函数:sigprocmask()

用于设置进程的信号屏蔽集(阻塞/解除阻塞信号):

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数解析

  • how:操作类型(信号屏蔽的核心控制):
    • SIG_BLOCK:在现有屏蔽集基础上,追加set中的信号;
    • SIG_SETMASK:用set替换现有屏蔽集;
    • SIG_UNBLOCK:解除set中信号的屏蔽。
  • set:要操作的信号集(sigset_t类型);
  • oldset:非空则保存原有屏蔽集(用于后续还原),NULL表示不保存。

信号集操作函数组

用于创建和修改sigset_t类型的信号集:

#include <signal.h>

// 清空信号集(初始化为无任何信号)
int sigemptyset(sigset_t *set);

// 填充信号集(包含所有64个信号)
int sigfillset(sigset_t *set);

// 向信号集添加指定信号
int sigaddset(sigset_t *set, int signum);

// 从信号集移除指定信号
int sigdelset(sigset_t *set, int signum);

// 检查信号是否在信号集中(返回1存在,0不存在,-1出错)
int sigismember(const sigset_t *set, int signum);

示例:信号屏蔽与解除屏蔽

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sig_handler(int sig_num) {
    printf("捕获到信号[%d],处理完成\n", sig_num);
}

int main() {
    // 1. 注册SIGINT信号的自定义处理函数
    signal(SIGINT, sig_handler);

    // 2. 初始化信号集并添加SIGINT
    sigset_t set;
    sigemptyset(&set);       // 清空信号集
    sigaddset(&set, SIGINT); // 添加SIGINT到信号集

    // 3. 屏蔽SIGINT信号(5秒内Ctrl+C无效)
    printf("开始屏蔽SIGINT信号(5秒内Ctrl+C无效)...\n");
    sigprocmask(SIG_SETMASK, &set, NULL);
    sleep(5);

    // 4. 解除SIGINT信号屏蔽(之后Ctrl+C有效)
    printf("解除SIGINT信号屏蔽(Ctrl+C有效)...\n");
    sigprocmask(SIG_UNBLOCK, &set, NULL);

    // 5. 等待信号触发
    while (1) pause(); // 暂停进程,直到收到信号

    return 0;
}

4.3 信号发送函数

1. kill():向指定进程发送信号

#include <sys/types.h>
#include <signal.h>

// 函数原型:向pid指定的进程发送signum信号
int kill(pid_t pid, int signum);
  • pid 取值:
    • pid > 0:发送给PID为pid的进程;
    • pid = 0:发送给当前进程组的所有进程;
    • pid = -1:发送给所有有权限发送的进程;
    • pid < -1:发送给进程组ID为|pid|的所有进程。
  • 示例:kill(1234, SIGINT):向PID=1234的进程发送Ctrl+C信号。

2. raise():向当前进程发送信号

#include <signal.h>

// 函数原型:向当前进程发送signum信号
int raise(int signum);
  • 示例:raise(SIGTERM):当前进程给自己发送终止信号。

3. alarm():设置定时器,到期发送SIGALRM信号

#include <unistd.h>

// 函数原型:seconds秒后向当前进程发送SIGALRM信号
unsigned int alarm(unsigned int seconds);
  • 示例:alarm(5):5秒后触发SIGALRM信号(默认终止进程)。

5. 信号的默认处理动作

信号的默认动作由系统定义,可通过 man 7 signal 查看,核心动作分类如下:

动作标识 含义
Term 终止进程(如SIGINT、SIGTERM)
Ign 忽略信号(如SIGCHLD、SIGURG)
Core 终止进程并生成核心转储文件(用于调试,如SIGSEGV、SIGFPE)
Stop 暂停进程(如SIGSTOP、SIGTSTP)
Cont 恢复被暂停的进程(如SIGCONT)

关键注意

  • 不可捕获/屏蔽/忽略的信号SIGKILL(9)和 SIGSTOP(19),只能执行默认动作(终止/暂停),用于强制控制进程。
图示

6. 知识:信号安全函数

核心概念

信号处理函数执行时,可能打断进程的正常执行流程(如正在执行printf时触发信号)。若处理函数中调用了“非信号安全函数”,可能导致内存错乱、死锁等问题。

常用信号安全函数(部分)

  • 基础IO:write()read()_exit()
  • 信号操作:signal()sigprocmask()sigaddset()等;
  • 注意:printf()fprintf()malloc()free() 均为非信号安全函数,禁止在信号处理函数中调用

替代方案

  • write(1, "内容", 长度) 替代 printf()(1表示标准输出);
  • 信号处理函数中仅执行简单逻辑(如设置标志位),复杂处理放在主进程循环中。

7. 核心工具:信号相关命令

命令 功能 示例
kill -l 列出所有信号 kill -l
kill -signum pid 向指定进程发送信号 kill -2 1234(向PID=1234发送SIGINT)
pkill -signum 进程名 向指定名称的进程发送信号 pkill -9 firefox(强制杀死火狐进程)
strace -e signal 程序 跟踪程序的信号交互(调试用) strace -e signal ./a.out
posted @ 2025-11-22 08:23  Jaklin  阅读(61)  评论(0)    收藏  举报