Linux 信号机制

一、信号是什么

信号是操作系统向进程发送的异步通知 ,告诉进程"某个事件发生了"。

我们可以把信号理解为软件层面的中断------它不是帮进程做事,只是告诉进程"该做事了"。

信号从产生到处理,经历三个阶段:
信号产生 → 信号保存 → 信号处理

二、信号的处理方式

大部分信号的默认操作都是终止进程

就像红灯的默认操作是让车停下来一样,信号的"默认"就是系统预设的动作。

但进程也可以自己处理信号:

  • 调用 signal 函数注册一个处理函数(handler)

  • 当信号来临时,进程自己调用这个 handler 来处理

signal函数

复制代码
NAME
        signal - ANSI C signal handling
SYNOPSIS
        #include <signal.h>
        typedef void (*sighandler_t)(int);
        sighandler_t signal(int signum, sighandler_t handler);

参数说明

signum:信号编号,是数字,其实就是信号的编号,通过kill -l可以看到所有信号

handler:函数指针,表⽰更改信号的处理动作,当收到对应的信号,就回调执⾏handler⽅法,这个函数指针可以换成SIG_DFL (通常是 0)和**SIG_IGN**(通常是 1)

复制代码
1. SIG_DFL(通常是 0)

含义:Default,恢复信号的默认处理

数值:通常定义为 (void (*)(int))0或 0

效果:按照系统默认方式处理信号

2. SIG_IGN(通常是 1)

含义:Ignore,忽略这个信号

数值:通常定义为 (void (*)(int))1或 1

效果:收到信号时什么都不做,直接丢弃

三、信号的产生方式

1. 键盘产生

按键 信号编号 说明
Ctrl + C 2 (SIGINT) 中断进程
Ctrl + \ 3 (SIGQUIT) 退出进程(带 core dump)

2. kill 命令

复制代码
kill -9 1234   # 发送 9 号信号

底层调用的是 kill 系统调用。

3. 系统调用函数

① kill
复制代码
int kill(pid_t pid, int sig);

给任意进程发送任意信号,成功返回 0,失败返回 -1。

② raise
复制代码
int raise(int sig);

给自己发送任意信号,等价于:

复制代码
kill(getpid(), sig);
③ abort
复制代码
void abort(void);

给自己发送 6 号信号 SIGABRT,默认动作是终止进程。
即使 SIGABRT 被捕捉,handler 执行完后进程依然会终止**------这是它和 9、19 号不同的地方。相同的地方是和9和19一样是特殊信号,**前两个不会被捕捉

4. 软件条件产生信号

① SIGPIPE(13 号)

当管道读端关闭,写端还在写时产生。

② alarm(14 号 SIGALRM)闹钟
复制代码
alarm(3);  // 3 秒后给当前进程发 SIGALRM
  • 每个进程同时只能有一个闹钟

  • 每次调用都会刷新闹钟时间,所以只有第一个alarm生成了闹钟,剩下的都是重新上旋转发条

  • alarm(0) 取消闹钟

  • 返回值:上一次闹钟的剩余时间

    复制代码
    alarm(3);      // 设置 3 秒闹钟
    sleep(1);
    int n = alarm(10);  // 重置为 10 秒,n = 2(上次还剩 2 秒)

    闹钟的管理:

    内核为每个闹钟维护一个结构体:

    复制代码
    struct alarm {
        uint64_t timeout;       // 闹钟时间
        uint32_t who;           // 进程标识
        task_struct *pcb;       // 进程控制块指针
    };

    所有进程的闹钟用最小堆 管理,堆顶是剩余时间最短的闹钟。
    判断超时:用过期时间戳(当前时间 + timeout)比较,OS 时间戳不断增长,当 OS 时间 ≥ 堆顶的过期时间戳时,闹钟超时。

5. 异常产生信号

像除零、野指针这些操作,为什么会直接导致进程崩溃?

本质是:硬件报错 → OS 识别 → 发信号 → 进程终止

除零(8 号 SIGFPE)
复制代码
int a = 10;
a / 0;

CPU 执行除法时:

  • a 的值加载到寄存器 eax

  • 0 加载到另一个寄存器

  • 执行除法运算,结果无穷大,CPU 寄存器存不下这个结果

此时 CPU 硬件会将状态寄存器(EFLAGS)的溢出标志位置为 1,表示计算结果不可信、硬件报错。然后怎么办呢,下一段会讲

复制代码
状态寄存器(EFLAGS)里有很多的比特位,每个比特位代表一种标志,
其中一种标志叫做溢出标志位,为0,代表计算结果可信,但如果是/0这种计算,
那么会将溢出标志位置为1,表示本次计算在硬件计算上报错了,不可信
野指针(11 号 SIGSEGV)

访问非法内存地址,同样触发硬件异常。

四、异常信号的处理流程

  1. CPU 执行指令 → 发生异常(如除零)

  2. CPU 硬件置溢出标志位为 1,触发异常

  3. CPU 保存当前进程的上下文(寄存器、指令位置等)

  4. 跳转到内核 ,OS 通过全局指针 current 找到当前正在被调度的进程(current会永远指向当 前被调度的进程

  5. OS 向该进程发送对应信号(除零发 8 号,野指针发 11 号)

  6. 查看进程的信号处理表

    • 如果进程没有注册 handler → 执行默认动作(终止进程)

    • 如果进程注册了 handler → 恢复上下文,让进程执行自己的 handler

五、一个关键问题:为什么保存上下文?OS 不是要杀掉进程吗?

OS 发信号不是为了立即杀掉进程,而是给进程一个"自行了断"的机会。

如果进程注册了 handler,说明它想自己处理这个信号。

OS 必须配合:

  • 恢复之前保存的上下文

  • 让 CPU 重新回到用户态,执行 handler 的代码

这就引出了一个经典场景:

进程除零 → CPU 置溢出标志位 → OS 发 SIGFPE

进程捕捉了信号,handler 里没做特殊处理(没 exit、没跳转)

handler 返回后,CPU 恢复上下文,重新执行同一条除法指令

再次除零 → 再次置溢出标志 → 再次触发异常 → 再次发信号

死循环

这不是 OS 反复发信号,而是 CPU 反复执行同一条指令导致硬件反复报错。

六、信号保存的本质

信号在内核里用位图管理:

  • 每个信号对应一个比特位

  • 信号产生时,OS 将对应比特位设为 1

  • 信号处理完,对应比特位清 0

这就是"信号被读取"的本质。

八、pause 函数

复制代码
int pause(void);

暂停当前进程,等待信号

当有信号递达到当前进程时,pause 结束暂停。

九、总结一波

复制代码
信号产生(键盘/kill/系统调用/软件条件/异常)
    ↓
信号保存(内核位图置 1)
    ↓
信号处理(默认/忽略/自定义 handler)
    ├─ 默认 → 终止/停止/忽略
    ├─ 忽略 → 啥也不做
    └─ 自定义 → 执行 handler,返回后可能恢复上下文重新执行

核心理解

信号不是 OS 强行介入的工具,而是一个协作机制 ------OS 告诉进程"你出事了",进程可以选择自己处理,也可以选择让 OS 按默认处理。

但如果进程自己处理了却没解决问题,就会陷入死循环。

相关推荐
数智化管理手记2 小时前
精益生产中的TPM管理是什么?一文破解设备零故障的密码
服务器·网络·数据库·低代码·制造·源代码管理·精益工程
Vect__3 小时前
深刻理解进程、线程、程序
linux
w6100104663 小时前
CKAD-2026-Ingress
运维·k8s·ckad
@insist1234 小时前
网络工程师-生成树协议(STP/RSTP/MSTP)核心原理与应用
服务器·开发语言·网络工程师·软考·软件水平考试
末日汐4 小时前
传输层协议UDP
linux·网络·udp
zzzsde6 小时前
【Linux】库的制作和使用(3)ELF&&动态链接
linux·运维·服务器
CQU_JIAKE6 小时前
4.3【A]
linux·运维·服务器
AI周红伟6 小时前
OpenClaw是什么?OpenClaw能做什么?OpenClaw详细介绍及保姆级部署教程-周红伟
大数据·运维·服务器·人工智能·微信·openclaw
Elastic 中国社区官方博客7 小时前
当 TSDS 遇到 ILM:设计不会拒绝延迟数据的时间序列数据流
大数据·运维·数据库·elasticsearch·搜索引擎·logstash
qing222222227 小时前
Linux中修改mysql数据表
linux·运维·mysql