目录
[1.1 预备](#1.1 预备)
[1.2 基本结论:](#1.2 基本结论:)
[2. 信号的产生](#2. 信号的产生)
[2.1 处理信号的三种动作:](#2.1 处理信号的三种动作:)
[2.2 a.信号都有哪些?](#2.2 a.信号都有哪些?)
[2.3 查看所有信号详情的命令:](#2.3 查看所有信号详情的命令:)
[3. signal 函数](#3. signal 函数)
[3.1 无法被自定义的信号:](#3.1 无法被自定义的信号:)
[3.2 signal函数的返回值的作用:](#3.2 signal函数的返回值的作用:)
[4. c.目标进程??前台进程和后台进程](#4. c.目标进程??前台进程和后台进程)
[4.1 补充一部分命令,前后台移动](#4.1 补充一部分命令,前后台移动)
[fg 命令,将特定的进程提到前台](#fg 命令,将特定的进程提到前台)
[5. d.什么叫做给进程发送信号?](#5. d.什么叫做给进程发送信号?)
[6. 产生信号的方式](#6. 产生信号的方式)
[7. 使用函数产生信号](#7. 使用函数产生信号)
[kill 函数](#kill 函数)
[raise 函数](#raise 函数)
[abort 函数](#abort 函数)
[8. 硬件异常产生信号](#8. 硬件异常产生信号)
[9. 由软件条件产生信号](#9. 由软件条件产生信号)
[alarm 函数](#alarm 函数)
[pause 函数](#pause 函数)
[10. 总结](#10. 总结)
1.信号
1.1 预备
信号vs信号量=老婆:老婆饼 ->没有任何关系!
1.信号:闹钟、红绿灯、上课铃声、狼烟、电话铃声、肚子叫、敲门声、脸色不好......
2.什么叫做信号:中断我们人正在做的事情,是一种事件的异步通知机制
- 信号是一种给进程发送的,用来进行事件异步通知的机制!
- 信号的产生,相对于进程的运行,是异步的!
- 信号是发给进程的!
同步与异步示例:老师让张三取物品
同步场景:
全班自习等待张三返回后再继续上课
异步场景:
课程继续进行,张三独立完成取物任务
本质区别:
- 同步:并发进程相互依赖(一方需等待另一方)
- 异步:并发进程互不干扰
1.2 基本结论:
- 信号处理:进程在信号还没有产生的时候,早就知道信号该如何处理了,且进程必须把要信号记录下来。
- 信号的处理,不是立即处理,而是可以等一会再处理,合适的时候,进程会进行信号的处理。
- 人能识别信号,是提前被 "教育" 过的,进程也是如此。OS程序员设计的进程,进程早已经内组织了对于信号的识别和处理方式!
- 信号源非常多 -> 给进程产生信号的,信号源,也非常多!
2. 信号的产生
当前阶段:

信号的产生方式非常多:
1.键盘产生信号
ctrl+c 是给目标进程(前台进程)发送信号的,相当一部分信号的处理动作,就是让在自己终止!
收到信号,处理信号,进程收到信号治好后,合适的时候,处理信号。
2.1 处理信号的三种动作:
- 默认处理动作
- 自定义信号处理动作(自定义捕捉)
- 忽略处理
2.2 a.信号都有哪些?
- 1 ~ 31 号信号是普通信号,剩余的是实时信号。
- ctrl + c就是给进程发信号,发哪一个信号?------2号信号(SIGINT)

2.3 查看所有信号详情的命令:
bash
man 7 signal

b.你怎么证明?
我想看到信号处理的过程,我们尝试着更改进程的默认信号处理动作。
用到的函数:
3. signal 函数
- signal 函数用于自定义信号处理动作。
- signum 是信号,2号信号就传 SIGINT,handler 是一个 void (int) 函数,以后进程接收到 SIGINT 信号就会跳到 handler() 函数中执行,而不是中止进程了,我也是我们说的自定义信号处理动作。
3.1 无法被自定义的信号:
- SIGKILL(9号信号):强制杀死进程。
- SIGSTOP(19号信号):强制暂停进程。
3.2 signal函数的返回值的作用:
- 保存旧的处理方式 :返回值是修改前的信号处理函数指针(handler,void (int))。你可以保存它,以便在临时修改后恢复原来的行为。
- 判断是否设置成功:如果返回 SIG_ERR,说明设置失败(例如试图捕获不可捕获的信号)。
cpp
#include <signal.h>
typedef void (*sighandler_t)(int);// 函数指针类型sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler_t)(int); // 函数指针类型
void handlerSigal(int sig)
{
std::cout << "获得一个信号: " << sig << std::endl;
}
int main()
{
sighandler_t sig = signal(SIGINT, handlerSigal); // handlerSigal(SIGINT)
int cnt = 0;
while (true)
{
std::cout << "hello world!,cnt: " << cnt++ << std::endl;
sleep(1);
}
return 0;
}
结果:
我们看见,本来能够终止进程的 ctrl+c ,现在无法终止进程了,变成了 "获得一个信号: 2",想要终止进程,我们可以 ctrl+
bash
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./t
bash: ./t: No such file or directory
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./TestSignal
hello world!,cnt: 0
hello world!,cnt: 1
hello world!,cnt: 2
^C获得一个信号: 2
hello world!,cnt: 3
^C获得一个信号: 2
hello world!,cnt: 4
^C获得一个信号: 2
hello world!,cnt: 5
^C获得一个信号: 2
hello world!,cnt: 6
hello world!,cnt: 7
hello world!,cnt: 8
^\Quit
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$
4. c.目标进程??前台进程和后台进程
- 直接执行一个可执行文件,它就是前台进程,后面加上 '&' 它就是后台进程。
bash
./xxx //前台进程
./xxx & // 后台进程
- 命令行shel进程是一个前台进程
- 键盘产生的信号,只能发给前台进程(组合键,也是键盘输入!)
- 当一个进程是后台进程,使用 ctrl+c 想要终止这个进程,进程不做处理,因为它收不到键盘产生的信号。
bash
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal &
[1] 59188
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
[1]+ Killed ./testSignal
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$
最后是使用 kill -9 进程号杀掉这个进程的。
谈谈前后台问题:
- 后台进程,无法从标准输入中获取内容;前台进程,能从键盘获取标准输入;但是都可以向标准输出上打印。
- 为什么后台进程无法从标准输入中获取内容?因为键盘只有一个,输入数据一定是给一个确定的进程的!
- 前台进程必须只有一个!!后台进程可以有多个。
- 前台进程的本质,就是要从键盘获取数据的!
比如使用 fork() 创建了子进程,但父进程作为前台进程却先退出了,此时子进程被 1号 进程接管,也就是自动提到后台,此时 ctrl+c 杀不掉这个子进程了。
4.1 补充一部分命令,前后台移动
jobs命令:
jobs 命令只能查看当前 Shell 会话中,并且仍在运行或处于暂停状态的后台任务。
所以如果你开一个 shell 运行一个后台任务,另一个 shell 使用 jobs 命令查的话是查不到的。
bash
jobs //查看当前shell的所有后台任务
bash
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal &
[1] 60069
hello world!, 进程id: 60069
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60069
jobs // 我在这里使用了jobs命令
hello world!, 进程id: 60069
[1]+ Running ./testSignal & // 这就是当前shell的所有后台任务
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
fg 命令,将特定的进程提到前台
- fg 命令是 Ctrl + Z 的"好搭档"。如果说 Ctrl + Z 是把任务"藏"到后台,那么 fg 就是把它"抓"回前台。
- 它的全称是 Foreground(前台)。
bash
fg 任务号
[1]+ Running ./testSignal & // 这个后台进程的任务号就是1
也就是可以这样用:fg 1
bash
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal &
[1] 60310
hello world!, 进程id: 60310
// 后台进程,使用 ctrl+c 无反应
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310
hello world!, 进程id: 60310
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310
hello world!, 进程id: 60310
// 使用 fg 将其提到前台
fg 1
./testSignal
hello world!, 进程id: 60310
hello world!, 进程id: 60310
// ctrl+c 有反应了,也可以使用ctrl+\结束进程
^C获得一个信号: 2
hello world!, 进程id: 60310
hello world!, 进程id: 60310
^\Quit
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$
ctrl+z,将进程暂停+切换到后台
Ctrl + Z 的作用正是:将当前正在前台运行的进程"暂停"(挂起),并将其放入后台。
bg命令,让后台进程恢复运行
- bg 是 background 的缩写,它是 fg 的"兄弟",也是 Ctrl + Z 的最佳拍档。
- 如果说 Ctrl + Z 是把任务按下了"暂停键",那么 bg 就是帮你在后台按下了"播放键"。
5. d.什么叫做给进程发送信号?
- 信号产生后,并不是立即处理的,所以要求,进程必须把信号记录下来!!(合适的时候处理!)
- 记录在哪里?记录的本质就是修改位图,接收到信号就将对应 bite 位置1

- 而 task_struct 结构体,属于操作系统内的数据结构!
- 修改位图,本质:修改内核的数据!!
- 不管信号怎么产生,发送信号,在底层,必须让OS发送!!!
- 所以操作系统自己,会提供发送信号的系统调用!!------kill
cpp
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);// 进程id+信号编号
发送信号,本质是什么??
- 向目标进程写信号
- 修改位图!
- 通过进程的pid,给进程发送信号的编号
6. 产生信号的方式
产生信号的方式:
- 调用系统命令向进程发信号 ------kill
- **[硬件]**异常,如:除0,野指针,会产生问题:崩掉!
7. 使用函数产生信号
kill 函数
kill 命令是调⽤ kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。
cpp
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);// 进程id+信号编号
raise 函数
raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。
cpp
#include <signal.h>
int raise(int sig);// sig信号
abort 函数
- abort 函数使当前进程接收到信号而异常终止。
- abort 函数的设计机制决定了它"必须"杀死进程,即使你注册了信号处理函数。
- abort 函数发出的是 6 号信号 (SIGABRT)
cpp
#include <stdlib.h>
void abort(void);
cpp
typedef void(*signal_t)(int);
void Abort(int sig)
{
std::cout<<"abort 发出的信号:"<<sig<<std::endl;
}
int main()
{
signal_t sig=signal(6,Abort);
abort();
return 0;
}
bash
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal
abort 发出的信号:6
Aborted
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$
8. 硬件异常产生信号
OS怎么知道硬件异常了?
- 因为产生了信号,除0会产生 8 号信号(SIGFPE ,浮点数异常);野指针会产生 11 号信号(SIGSEGV,段错误)。

操作系统怎么知道犯错了?

9. 由软件条件产生信号
概念
"软件条件"其实就是操作系统内核根据程序运行的逻辑状态或资源情况,主动判定"出事了"而发送的信号。 它不是硬件坏了(如除零、段错误),也不是人按了键,而是内核作为"管理员"发现不符合规则了。
最典型的三个"软条件":
- 路断了 (SIGPIPE):你往管道写数据,但读的那头已经挂了(关闭了),内核觉得你白忙活,就发信号终止你。
- 时间到了 (SIGALRM):你设了闹钟(alarm),时间一到,内核就发信号提醒你。
- 孩子变了 (SIGCHLD):子进程结束了,内核发信号通知父进程去"收尸"(回收资源)。
alarm 函数
cpp
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
- 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号(14 号信号),该信号的默认处理动作是终⽌当前进程。
- 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。比如:
先设置 alarm(5) ,返回值为0。
过了 3 秒,再设置 alarm(10) ,返回值就是 2 ,意思是上次的闹钟还有 2 秒。
cpp
int main()
{
alarm(5);
int cnt =1;
while (true)
{
printf("second: %d\n",cnt++);
sleep(1);
}
return 0;
}
bash
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal
second: 1
second: 2
second: 3
second: 4
second: 5
Alarm clock
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$
pause 函数
pause() 函数的主要作用是让当前进程挂起(暂停执行),直到它接收到一个信号为止。
cpp
#include <unistd.h>
int pause(void);
快速理解系统闹钟
- 假如现在的时间戳是 1000 ,调用 alarm(5) 后,就会在 1005 时给当前进程发 SIGALRM 信号。
OS会不会同时存在很多的闹钟?要不要对闹钟进程管理?
- 所以我们需要先描述,再组织。
- 在底层就可以使用小堆这种数据,将闹钟都插入,然后将当前的时间戳不断与堆顶闹钟比较。
10. 总结
- 所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
- 信号的处理是否是立即处理的?在合适的时候
