news 2026/4/29 10:06:40

【linux进程信号】————产生信号:signal自定义信号处理动作(自定义捕捉)、前后台进程、产生信号的方式(函数、软条件、硬件异常)....等等

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【linux进程信号】————产生信号:signal自定义信号处理动作(自定义捕捉)、前后台进程、产生信号的方式(函数、软条件、硬件异常)....等等

目录

1.信号

1.1 预备

1.2 基本结论:

2. 信号的产生

2.1 处理信号的三种动作:

2.2 a.信号都有哪些?

2.3 查看所有信号详情的命令:

3. signal 函数

3.1 无法被自定义的信号:

3.2 signal函数的返回值的作用:

4. c.目标进程??前台进程和后台进程

4.1 补充一部分命令,前后台移动

jobs命令:

fg 命令,将特定的进程提到前台

ctrl+z,将进程暂停+切换到后台

bg命令,让后台进程恢复运行

5. d.什么叫做给进程发送信号?

6. 产生信号的方式

7. 使用函数产生信号

kill 函数

raise 函数

abort 函数

8. 硬件异常产生信号

9. 由软件条件产生信号

概念

alarm 函数

pause 函数

快速理解系统闹钟

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 查看所有信号详情的命令:

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,说明设置失败(例如试图捕获不可捕获的信号)。
#include <signal.h> typedef void (*sighandler_t)(int);// 函数指针类型sighandler_t sighandler_t signal(int signum, sighandler_t handler);
#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+

[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.目标进程??前台进程和后台进程

  • 直接执行一个可执行文件,它就是前台进程,后面加上 '&' 它就是后台进程。
./xxx //前台进程 ./xxx & // 后台进程
  • 命令行shel进程是一个前台进程
  • 键盘产生的信号,只能发给前台进程(组合键,也是键盘输入!)
  • 当一个进程是后台进程,使用 ctrl+c 想要终止这个进程,进程不做处理,因为它收不到键盘产生的信号。
[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 命令查的话是查不到的。

jobs //查看当前shell的所有后台任务
[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(前台)。
fg 任务号 [1]+ Running ./testSignal & // 这个后台进程的任务号就是1 也就是可以这样用:fg 1
[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
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);// 进程id+信号编号

发送信号,本质是什么??

  • 向目标进程写信号
  • 修改位图!
  • 通过进程的pid,给进程发送信号的编号

6. 产生信号的方式

产生信号的方式:

  • 调用系统命令向进程发信号 ——kill
  • [硬件]异常,如:除0,野指针,会产生问题:崩掉!

7. 使用函数产生信号

kill 函数

kill 命令是调⽤ kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。

#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);// 进程id+信号编号

raise 函数

raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。

#include <signal.h> int raise(int sig);// sig信号

abort 函数

  • abort 函数使当前进程接收到信号而异常终止。
  • abort 函数的设计机制决定了它“必须”杀死进程,即使你注册了信号处理函数。
  • abort 函数发出的是6 号信号SIGABRT
#include <stdlib.h> void abort(void);
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; }
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal abort 发出的信号:6 Aborted [wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$

8. 硬件异常产生信号

OS怎么知道硬件异常了?

  • 因为产生了信号,除0会产生 8 号信号(SIGFPE,浮点数异常);野指针会产生 11 号信号(SIGSEGV,段错误)。

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

9. 由软件条件产生信号

概念

“软件条件”其实就是操作系统内核根据程序运行的逻辑状态或资源情况,主动判定“出事了”而发送的信号。 它不是硬件坏了(如除零、段错误),也不是人按了键,而是内核作为“管理员”发现不符合规则了。

最典型的三个“软条件”:

  • 路断了 (SIGPIPE):你往管道写数据,但读的那头已经挂了(关闭了),内核觉得你白忙活,就发信号终止你。
  • 时间到了 (SIGALRM):你设了闹钟(alarm),时间一到,内核就发信号提醒你。
  • 孩子变了 (SIGCHLD):子进程结束了,内核发信号通知父进程去“收尸”(回收资源)。

alarm 函数

#include <unistd.h> unsigned int alarm(unsigned int seconds);
  • 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号(14 号信号),该信号的默认处理动作是终⽌当前进程
  • 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。比如:

先设置 alarm(5) ,返回值为0。

过了 3 秒,再设置 alarm(10) ,返回值就是 2 ,意思是上次的闹钟还有 2 秒。

int main() { alarm(5); int cnt =1; while (true) { printf("second: %d\n",cnt++); sleep(1); } return 0; }
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal second: 1 second: 2 second: 3 second: 4 second: 5 Alarm clock [wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$

pause 函数

pause() 函数的主要作用是让当前进程挂起(暂停执行),直到它接收到一个信号为止。

#include <unistd.h> int pause(void);

快速理解系统闹钟

  • 假如现在的时间戳是 1000 ,调用 alarm(5) 后,就会在 1005 时给当前进程发 SIGALRM 信号。

OS会不会同时存在很多的闹钟?要不要对闹钟进程管理?

  • 所以我们需要先描述,再组织。
  • 在底层就可以使用小堆这种数据,将闹钟都插入,然后将当前的时间戳不断与堆顶闹钟比较。

10. 总结

  • 所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
  • 信号的处理是否是立即处理的?在合适的时候

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 10:04:56

DS4Windows终极指南:3步让PS手柄在Windows上完美兼容所有游戏

DS4Windows终极指南&#xff1a;3步让PS手柄在Windows上完美兼容所有游戏 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows 你是否曾经尝试在Windows电脑上使用PlayStation手柄&#xff0c;…

作者头像 李华
网站建设 2026/4/29 10:04:41

Fast-GitHub:为国内开发者解锁GitHub全速访问的技术利器

Fast-GitHub&#xff1a;为国内开发者解锁GitHub全速访问的技术利器 【免费下载链接】Fast-GitHub 国内Github下载很慢&#xff0c;用上了这个插件后&#xff0c;下载速度嗖嗖嗖的~&#xff01; 项目地址: https://gitcode.com/gh_mirrors/fa/Fast-GitHub GitHub作为全球…

作者头像 李华
网站建设 2026/4/29 10:04:35

网盘直链下载助手:8大网盘一键获取真实下载地址的终极解决方案

网盘直链下载助手&#xff1a;8大网盘一键获取真实下载地址的终极解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘…

作者头像 李华
网站建设 2026/4/29 9:57:23

告别冗长代码:用 Python 基础特性写出简洁风格

从循环到推导式&#xff1a;重塑数据构建逻辑 很多刚从 Java、C 或 C# 转投 Python 怀抱的开发者&#xff0c;最容易犯的错误就是“穿着新鞋走老路”。习惯了显式的 for 循环和临时列表变量&#xff0c;写出的代码往往冗长且充满样板气息。Python 最迷人的特性之一就是列表推导…

作者头像 李华
网站建设 2026/4/29 9:54:22

抖音下载终极指南:5分钟搞定无水印批量采集的免费神器

抖音下载终极指南&#xff1a;5分钟搞定无水印批量采集的免费神器 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppo…

作者头像 李华