目录
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 signalb.你怎么证明?
我想看到信号处理的过程,我们尝试着更改进程的默认信号处理动作。
用到的函数:
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: 60069fg 命令,将特定的进程提到前台
- 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是进程的管理者
- 信号的处理是否是立即处理的?在合适的时候