一、信号快速认识

- 信号(现实生活中):闹钟、红绿灯、上课铃声、狼烟、电话铃声、肚子叫、敲门声、脸色不好 ....

1.1 生活中的信号 ------ 快递的例子
想象你网购了很多商品:
-
你能识别快递:你知道快递员打电话时该怎么处理。即使快递没有到来,你也知道快递来临的时候,你该如何处理快递 。 ------ 这是内置的识别能力。
-
信号产生前你就知道处理方法: 即使快递没到,你也知道"收到快递要拆开" ------ 进程在信号产生前就已经准备好了处理函数。
-
**不必立即处理:**你正在打游戏,可以5分钟后下楼取快递 ------信号不要求立即处理,而是在"合适的时候"处理。
-
记住有信号:你知道有一个快递到了但还没取 ------ 进程会保存未决信号。
-
处理方式有三种:
-
默认动作:开心拆快递
-
自定义动作:(买了零食,分给同学一些)
-
忽略:不理快递
-
基本结论:
-
识别信号是内核程序员内置的特性。
-
信号产生前,进程已经知道如何处理。
-
信号处理不是立即的,而是在合适的时候(从内核态返回用户态时)。
-
信号到来 → 信号保存 → 信号处理。
-
信号处理方式:忽略 ; 默认 ; 自定义【后续都叫信号捕捉】
快递到来的整个过程,对你来说,是异步的, 你不确定 , 快递员什么时候会给你打电话
1.2 键盘产生的信号
Makefile
testSign:testSign.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f testSign
#include <iostream>
#include <unistd.h>
int main()
{
int cnt = 0;
while (true)
{
std::cout << "hello world," << cnt++ << std::endl;
sleep(1);
}
return 0;
}

ctrl + c , 是给目标进程发送信号的 。 相当一部分的信号处理动作 , 就是让进程自己终止!
1.3 信号都有哪些?
kill -l :查看信号列表 【数字)大写的信号名】

关键说明:
- 每个信号都有编号 和宏定义名称 (如 2 号信号对应 SIGINT),宏定义在
signal.h头文件中; - 34 及以上为实时信号,用于实时进程通信,日常开发主要使用 34 以下的常规信号;
- 每个信号都有默认处理动作,可通过
man 7 signal查看详细说明(如 SIGKILL 的默认动作是终止进程,且无法被捕捉和忽略)。


常用信号:
| 编号 | 名称 | 默认动作 | 含义 |
|---|---|---|---|
| 2 | SIGINT | 终止 | 终端中断(Ctrl+C) |
| 3 | SIGQUIT | 终止+Core Dump | 终端退出(Ctrl+\) |
| 9 | SIGKILL | 终止 | 强制终止(不可捕捉、不可阻塞) |
| 11 | SIGSEGV | 终止+Core Dump | 段错误(非法内存访问) |
| 14 | SIGALRM | 终止 | 闹钟信号 |
| 20 | SIGTSTP | 停止进程 | 终端停止(Ctrl+Z) |
ctrl + c : 给目标进程发送 2)信号 !!!
收到信号,处理动作(捕捉):

1.4 证明:ctrl+c -> 二号信号


修改一下testSig.cc的代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handlerSig(int sig)
{
std::cout << "获得了一个信号: " << sig << std::endl;
}
int main()
{
signal(SIGINT,handlerSig);
int cnt = 0;
while (true)
{
std::cout << "hello world, " << cnt++ << std::endl;
sleep(1);
}
return 0;
}


二、前台 & 后台进程?
2.1 现象查看




2.2 核心定义
| 特性 | 前台进程 (Foreground) | 后台进程 (Background) |
|---|---|---|
| 标准输入 | ✅ 独占键盘输入 | ❌ 无法从 stdin 读取 |
| 标准输出 | ✅ 可以输出到终端 | ✅ 可以输出到终端 |
| 键盘信号 | ✅ 响应 Ctrl+C 等 | ❌ 不响应键盘信号 |
| 数量限制 | 必须只有一个 | 可以有多个 |
| 创建方式 | ./program |
./program & |
- 前台进程 :唯一能接收键盘输入、获取标准输入的进程,同一时间 Shell 中只能有一个前台进程。
- 后台进程 :无法从键盘获取标准输入的进程,同一时间可以有多个后台进程。
- Shell 命令行进程本身:默认是前台进程,负责接收用户输入的命令。
2.3 为什么前台进程只能有一个?
键盘只有一个,输入数据一定是给一个确定的进程的!
这是硬件层面的限制------键盘是独占设备 。如果多个进程同时等待键盘输入,操作系统无法决定把按键发给谁。因此 Shell 设计了**作业控制(Job Control)**机制:
-
前台作业:当前与终端交互的进程组
-
后台作业:在后台运行,不占用终端输入
2.4 进程状态转换与作业控制命令
| 命令 | 作用 | 示例 |
|---|---|---|
jobs |
查看所有后台任务 | [1]+ Running ./test & |
fg %n |
将后台任务 n 提到前台 | fg %1 |
bg %n |
让暂停的后台任务继续运行 | bg %1 |
Ctrl+Z |
暂停当前前台进程,放入后台 | 进程变为 Stopped |
Ctrl+C |
向前台进程发送 SIGINT 信号 | 终止进程 |


2.5 关键场景:孤儿进程与后台化
# 场景1:正常前台进程
$ ./testsig # bash 子进程作为前台进程
# Ctrl+C 可以终止,因为键盘信号发给前台进程
# 场景2:后台运行
$ ./testsig & # 立即放入后台
# Ctrl+C 杀不掉!因为键盘信号只发给前台进程
父进程先退出 → 孤儿进程自动后台化
// 代码逻辑示意
int main() {
if (fork() == 0) {
// 子进程
sleep(10); // 模拟工作
} else {
// 父进程先退出
exit(0);
}
}
现象:
-
父进程(bash 的子进程)先退出
-
子进程变成孤儿进程,被 init/systemd 收养
-
自动提到后台(因为失去了控制终端的前台关联)
-
Ctrl+C 杀不掉 (已经是后台进程)
三、给进程发送信号的理解
发送信号的本质:修改目标进程的内核数据结构 (向目标进程写信号 = 修改位图)
信号是一种异步事件通知机制:
-
信号产生后,不是立即处理的
-
进程必须先把信号记录下来
-
在合适的时候(系统调用返回、中断返回)统一处理

为什么必须是操作系统来发送?

信号在内核中的存储:位图结构
// include/linux/sched.h
struct task_struct {
// ...
unsigned long signal; // 共享挂起信号(线程组共享)
unsigned long blocked; // 被阻塞的信号掩码
struct sigpending pending; // 私有挂起信号队列
// ...
};
位图解析

四、信号VS通信IPC
| 对比维度 | 信号(Signal) | 通信 IPC(管道/消息队列/共享内存等) |
|---|---|---|
| 本质 | 通知机制 | 数据传输机制 |
| 传递内容 | 只传递"发生了什么"(事件类型) | 传递"具体的内容"(任意数据) |
| 存储方式 | 用位图记录(1 bit) | 用缓冲区存储(多字节) |
| 数据承载 | 不能带数据(除 siginfo 外) | 可以传输任意数据 |
| 同步方式 | 异步、立即记录 | 可能阻塞、支持同步/异步 |
| 控制权 | 操作系统强制干预 | 进程间协作完成 |
| 类比 | 门铃(有人来了) | 快递(有具体物品) |
