目录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 命令将特定的进程提到前台ctrlz将进程暂停切换到后台bg命令让后台进程恢复运行5. d.什么叫做给进程发送信号6. 产生信号的方式7. 使用函数产生信号kill 函数raise 函数abort 函数8. 硬件异常产生信号9. 由软件条件产生信号概念alarm 函数pause 函数快速理解系统闹钟10. 总结1.信号1.1 预备信号vs信号量老婆老婆饼 -没有任何关系1.信号闹钟、红绿灯、上课铃声、狼烟、电话铃声、肚子叫、敲门声、脸色不好......2.什么叫做信号中断我们人正在做的事情是一种事件的异步通知机制信号是一种给进程发送的用来进行事件异步通知的机制信号的产生相对于进程的运行是异步的信号是发给进程的同步与异步示例老师让张三取物品同步场景全班自习等待张三返回后再继续上课异步场景课程继续进行张三独立完成取物任务本质区别同步并发进程相互依赖一方需等待另一方异步并发进程互不干扰1.2 基本结论信号处理进程在信号还没有产生的时候早就知道信号该如何处理了且进程必须把要信号记录下来。信号的处理不是立即处理而是可以等一会再处理合适的时候进程会进行信号的处理。人能识别信号是提前被 “教育” 过的进程也是如此。OS程序员设计的进程进程早已经内组织了对于信号的识别和处理方式信号源非常多 - 给进程产生信号的信号源也非常多2. 信号的产生当前阶段信号的产生方式非常多1.键盘产生信号ctrlc 是给目标进程前台进程发送信号的相当一部分信号的处理动作就是让在自己终止收到信号处理信号进程收到信号治好后合适的时候处理信号。2.1 处理信号的三种动作默认处理动作自定义信号处理动作自定义捕捉忽略处理2.2 a.信号都有哪些?1 ~ 31 号信号是普通信号剩余的是实时信号。ctrl c就是给进程发信号发哪一个信号——2号信号(SIGINT)2.3 查看所有信号详情的命令man 7 signalb.你怎么证明我想看到信号处理的过程我们尝试着更改进程的默认信号处理动作。用到的函数3. signal 函数signal 函数用于自定义信号处理动作。signum 是信号2号信号就传 SIGINThandler 是一个 void (int) 函数以后进程接收到 SIGINT 信号就会跳到 handler() 函数中执行而不是中止进程了我也是我们说的自定义信号处理动作。3.1 无法被自定义的信号SIGKILL9号信号强制杀死进程。SIGSTOP19号信号强制暂停进程。3.2 signal函数的返回值的作用保存旧的处理方式返回值是修改前的信号处理函数指针handlervoid (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; }结果我们看见本来能够终止进程的 ctrlc 现在无法终止进程了变成了 “获得一个信号: 2”想要终止进程我们可以 ctrl[wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ ./t bash: ./t: No such file or directory [wsjiZgw05f0yp422tzyebhkoaZ 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 [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$4. c.目标进程前台进程和后台进程直接执行一个可执行文件它就是前台进程后面加上 它就是后台进程。./xxx //前台进程 ./xxx // 后台进程命令行shel进程是一个前台进程键盘产生的信号只能发给前台进程组合键也是键盘输入当一个进程是后台进程使用 ctrlc 想要终止这个进程进程不做处理因为它收不到键盘产生的信号。[wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal [1] 59188 [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188 hello world!, 进程id: 59188 hello world!, 进程id: 59188 hello world!, 进程id: 59188 ^C [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188 ^C [wsjiZgw05f0yp422tzyebhkoaZ 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 [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$最后是使用kill -9 进程号杀掉这个进程的。谈谈前后台问题后台进程无法从标准输入中获取内容前台进程能从键盘获取标准输入但是都可以向标准输出上打印。为什么后台进程无法从标准输入中获取内容因为键盘只有一个输入数据一定是给一个确定的进程的前台进程必须只有一个后台进程可以有多个。前台进程的本质就是要从键盘获取数据的比如使用 fork() 创建了子进程但父进程作为前台进程却先退出了此时子进程被 1号 进程接管也就是自动提到后台此时 ctrlc 杀不掉这个子进程了。4.1 补充一部分命令前后台移动jobs命令:jobs 命令只能查看当前 Shell 会话中并且仍在运行或处于暂停状态的后台任务。所以如果你开一个 shell 运行一个后台任务另一个 shell 使用 jobs 命令查的话是查不到的。jobs //查看当前shell的所有后台任务[wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal [1] 60069 hello world!, 进程id: 60069 [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60069 jobs // 我在这里使用了jobs命令 hello world!, 进程id: 60069 [1] Running ./testSignal // 这就是当前shell的所有后台任务 [wsjiZgw05f0yp422tzyebhkoaZ 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[wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal [1] 60310 hello world!, 进程id: 60310 // 后台进程使用 ctrlc 无反应 [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310 ^C [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310 hello world!, 进程id: 60310 ^C [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310 hello world!, 进程id: 60310 // 使用 fg 将其提到前台 fg 1 ./testSignal hello world!, 进程id: 60310 hello world!, 进程id: 60310 // ctrlc 有反应了也可以使用ctrl\结束进程 ^C获得一个信号: 2 hello world!, 进程id: 60310 hello world!, 进程id: 60310 ^\Quit [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ctrlz将进程暂停切换到后台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::coutabort 发出的信号sigstd::endl; } int main() { signal_t sigsignal(6,Abort); abort(); return 0; }[wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal abort 发出的信号6 Aborted [wsjiZgw05f0yp422tzyebhkoaZ 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; }[wsjiZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal second: 1 second: 2 second: 3 second: 4 second: 5 Alarm clock [wsjiZgw05f0yp422tzyebhkoaZ lesson26]$pause 函数pause() 函数的主要作用是让当前进程挂起暂停执行直到它接收到一个信号为止。#include unistd.h int pause(void);快速理解系统闹钟假如现在的时间戳是 1000 调用 alarm(5) 后就会在 1005 时给当前进程发 SIGALRM 信号。OS会不会同时存在很多的闹钟要不要对闹钟进程管理所以我们需要先描述再组织。在底层就可以使用小堆这种数据将闹钟都插入然后将当前的时间戳不断与堆顶闹钟比较。10. 总结所有信号产生最终都要有OS来进行执行为什么OS是进程的管理者信号的处理是否是立即处理的在合适的时候