☰
【知识讲解】 Linux信号的认识与使用
2026/10/26 10:52:52 网站建设 项目流程


目录

前言

Part1. 信号基础概念

Part2. 信号的产生方式

Part2.1. 键盘按键

Part2.2. 硬件异常

Part2.3. 主动发送信号

Part2.4. 软件条件触发

Part3. 信号在内核中的保存

Part3.1. 信号位图

Part3.2. 信号集操作函数

Part3.3. sigprocmask

Part4. sigaction

Part5. 信号处理的坑点

Part6. 信号 与 System V IPC

Part7. 结语


前言

Linux下对于信号的了解是非常重要的,接下来来跟随小编的视角来看看吧。


let's go!!!!!!!!


Part1. 信号基础概念

信号是 Linux 下异步事件通知机制,和信号量无关。

• 本质:内核发给进程的简短通知,用来告知进程发生了某个事件。

• 特点:异步。进程正常运行时,信号随时可能抵达;进程不会立刻处理,先标记信号,等到合适时机(从内核态切回用户态前)再去处理。

• 类比:中断是CPU层面的异步通知;信号是进程层面的软件中断。


信号编号

信号用整数编号标识,Linux一共62个标准信号:

•1 ~ 31:可靠信号(传统不可靠信号),不支持排队,同一个信号多次产生可能丢失;

•34 ~ 64:实时信号,支持排队,不会丢失。


常用信号举例:SIGINT(2),对应终端 Ctrl + C。


收到信号后的三种处理方式

1.默认处理(Default):操作系统内置行为,大部分是终止进程;

2.自定义处理(Catch):注册信号处理函数,收到信号后执行我们自己写的回调;

3.忽略处理(Ignore):收到信号直接丢弃,不做任何动作。


注意:SIGKILL(9)、SIGSTOP(19) 这两个信号不能捕获、不能忽略,只能执行默认行为。


信号处理函数类型

typedef void (*sighandler_t)(int);

• sighandler_t:信号处理函数类型,接收一个int参数(信号编号)

• signal 函数原型:

sighandler_t signal(int signum, sighandler_t handler);

• 参数:

◦ signum:要注册的信号编号

◦ handler:处理函数,可以填 SIG_DFL(恢复默认)、SIG_IGN(忽略)或者自定义函数指针

• 返回值:返回这个信号旧的处理函数指针。


小坑:signal 函数在不同UNIX体系行为有差异,工程代码优先使用 sigaction,更稳定可控。


Part2. 信号的产生方式

一共4大类产生途径:


Part2.1. 键盘按键

◦ Ctrl + C → SIGINT 终止进程

◦ Ctrl + \ → SIGQUIT 退出并生成core转储文件

◦ Ctrl + Z → SIGTSTP 暂停进程(放到后台)


Part2.2. 硬件异常

◦段错误 SIGSEGV:访问非法内存、野指针、栈溢出;MMU硬件检测到虚拟地址非法,内核发出信号

◦除零错误 SIGFPE:除法除数为0

◦总线错误 SIGBUS:非法地址对齐访问


Part2.3. 主动发送信号

◦int raise(int sig);:给自己发信号

◦void abort(void);:内部会触发 SIGABRT,终止进程,一般用来断言失败

◦int kill(pid_t pid, int sig);:给指定pid的进程发送信号

pid > 0:发给pid对应的进程

pid = 0:发给调用进程所在进程组的所有进程

pid = -1:权限足够时,发给除init以外所有进程


Part2.4. 软件条件触发

◦定时器到期 SIGALRM、管道读端关闭写端收到 SIGPIPE 等。


补充:kill 函数名字有迷惑性,不是只能发杀死进程的信号,可以发送任意信号。


Part3. 信号在内核中的保存

Part3.1. 信号位图

每个进程PCB(task_struct)里面维护两张位图(bitmask),用一个整数/数组的每一位代表一个信号:

•未决信号集(pending):某一位置1 → 这个信号已经产生,但是进程还没有处理;处理完毕后清零。

•阻塞信号集(屏蔽字,mask):某一位置1 →屏蔽这个信号。信号来了之后不会被处理,保留在pending位图里,等到解除屏蔽之后再处理。

逻辑判断:
信号到来 → 先看mask:
mask对应bit=0(不阻塞):合适时机立刻处理,pending清0
mask对应bit=1(阻塞):信号置在pending里挂起,暂时不处理


重点:不可靠信号不排队!
同一个不可靠信号在阻塞期间多次抵达,pending位只会置1一次,多次信号会丢失;实时信号支持排队。


Part3.2. 信号集操作函数

sigset_t 是信号位图类型,用来表示一组信号,配套函数:

int sigemptyset(sigset_t *set); // 清空信号集,全部置0 int sigfillset(sigset_t *set); // 填满信号集,全部置1 int sigaddset(sigset_t *set, int signum); // 添加一个信号 int sigdelset(sigset_t *set, int signum); // 删除一个信号 int sigismember(const sigset_t *set, int signum); // 判断信号是否在集合内

Part3.3. sigprocmask

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

• how:三种行为

◦ SIG_BLOCK:屏蔽,把set里的信号追加到原来的屏蔽字(mask |= set)

◦ SIG_UNBLOCK:解除屏蔽,把set里的信号从mask移除

◦ SIG_SETMASK:直接把mask替换成set的值

• oldset:出参,可以拿到修改之前旧的屏蔽字,方便后续恢复。


使用场景:临界区代码,我们不希望被信号打断 → 进入临界区屏蔽信号,执行完临界区解除屏蔽。


Part4. sigaction

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction { void (*sa_handler)(int); // 老式信号处理函数 void (*sa_sigaction)(int, siginfo_t *, void *); // 扩展版本,可以拿到发送方pid等信息 sigset_t sa_mask; // 【重点】执行当前信号处理函数期间,自动屏蔽的信号 int sa_flags; void (*sa_restorer)(void); };

• sa_mask:当我们正在跑这个信号回调的时候,自动屏蔽sa_mask里面的信号,防止信号嵌套打断当前handler;handler执行结束自动恢复。

• SA_SIGINFO:sa_flags开启这个标记,就会使用 sa_sigaction,拿到 siginfo_t,里面可以拿到发送信号的进程pid。


信号处理函数是在进程的用户态上下文执行,不是独立线程!handler执行的时候,主流程暂停。


Part5. 信号处理的坑点

1. 信号处理函数里面,不是所有函数都能调用!
只能调用可重入(异步信号安全)函数。printf、malloc、lock 这些非可重入函数禁止在信号handler调用,极易死锁、内存损坏。

2.不可靠信号会丢失,不要依赖它做计数;需要可靠通知优先实时信号或者管道。

3. 信号只做通知,不要在handler写复杂业务逻辑。工程经典方案:信号handler里面只写一个字节写到管道,主循环epoll监听管道读端,把业务逻辑放到主循环处理(信号转IO事件)。

4.子进程继承父进程的信号屏蔽字、信号处理方式;但是pending未决信号集不会继承。

5. fork之后子进程里面,继承信号处理函数;exec之后,所有捕获的信号重置为默认处理方式。


Part6. 信号 与 System V IPC

通信方式核心特点适用场景
信号异步、携带信息极少(只有信号编号),内核通知异常通知、终止、暂停、定时器事件
消息队列面向消息,带类型,支持选择性读取进程之间传递结构化数据
信号量计数器,用来做互斥、同步保护临界资源,控制并发访问

一句话区分:信号是通知事件;信号量是同步互斥工具,二者完全两码事


Part7. 结语

这篇文章我们深入的知道了信号的相关知识,接下来,小编还会带来更多Linux相关的知识,敬请期待~

最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询