🔥🔥 欢迎来到小林的博客!!
🛰️博客主页:✈️林 子
🛰️博客专栏:✈️ Linux
🛰️社区 :✈️ 进步学堂
🛰️欢迎关注:👍点赞🙌收藏✍️留言
目录
- 信号是什么?
- 信号是如何产生的?
- 1.键盘输入
- 2.程序(进程)异常
- 3.系统调用
- 4. 由软件条件来产生信号
- 信号捕获
- 总结:
信号是什么?
什么是信号? 信号是一种抽象概念,在我们的生活中就有各种各样的信号。 例如红绿灯,凌晨六点的鸡鸣声,你考试成绩不好时你爸妈的脸色。 这些都是信号。 而信号有一重要的特征就是,当这些信号出现时,你知道接下来会发生什么!然后采取相应的措施。
比如说:
红灯来了,你知道你不能过马路,所以你停止前进。
凌晨六点鸡叫了, 你知道已经天亮了,你可以起床,也可以继续睡觉,这都是处理信号的一种方式。
你考试成绩不好回家,你爸妈的脸色也可以告诉你,你爸妈现在心情很不开心,所以你知道你不能惹你爸妈生气。
以上都是现实中的信号,那么在我们的计算机程序中,也有信号!
而信号在产生之前,我们的程序就知道如何处理这个信号。
信号是如何产生的?
那么我们程序中的信号是如何产生的呢?
1.键盘输入
我们在键盘中有一些快捷键,比如 ctrl + c,会强制关掉当前前台运行的程序,本质是利用键盘输入,让OS向目标进程发送了2号信号。
我们可以写个死循环程序演示一下。
#include<iostream>int main()
{while(1){printf("hello linux\n");}return 0;
}
然后我们运行程序,按ctrl + c ,程序会被终止。实际上是产生了信号。
2.程序(进程)异常
当程序出现异常 , 例如 **空指针解引用,除0,等异常都会产生信号。 而本质是程序在执行中,发生了硬件错误。空指针解引用会发生内存错误, 除0会发生cpu计算错误。 所以就会产生信号,再由OS来处理这些信号。**这种情况就不演示了,因为这种异常在学习C语言的过程中想必大家都遇见过。
3.系统调用
我们可以用一些系统调用接口来产生信号,例如 我们命令行上的 kill 命令 。 本质就是系统调用int kill(pid_t pid, int signo)
函数来产生信号。还有 int raise(int signo)
函数来自己产生信号, 还有 void abort(void)
来产生 3号信号。
**int kill(pid_t pid, int signo) 演示 **
这个函数的第一个参数是一个进程的pid ,第二个参数是发送的参数,类似于我们 kill命令行。所以我们需要以命令行的方式 获取被发送信号进程的pid。
我们写一个kill 函数,参数用我们命令传进去的 argv[1] 和 argv[2]
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>int main(int argc , char* argv[])
{if(argc != 3){printf("./mykill pid signal");return 1;}kill(atoi(argv[1]),atoi(argv[2]));return 0;
}
然后我们执行一个进程,每秒打印一次 hello linux。然后用 mykill 对 mypro 发送9号信号!
这样我们就模拟实现了一个 kill 命令。本质就算调用了系统调用接口 kill。
int raise(int signo) 演示
我们让程序打印5次 hello linux 后调用这个函数,并传参数9进去。看看进程会不会收到9号信号。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>int main()
{int count = 0;while(1){printf("hello linux\n");sleep(1);count ++;if(count == 5){raise(9);}}return 0;
}
然后我们运行这个程序
不难发现,五秒过后收到了9号信号,这就是自己产生信号的一种方式。
4. 由软件条件来产生信号
SIGPIPE是一种由软件条件产生的信号, 当我们在管道传输中,关闭了读端,而写端还在继续写入。那么就会产生SIGPIPE信号。
还有 alarm函数
产生 SIGALRM
信号。
SIGPIPE信号暂且不演示,我们演示一下 alarm函数。
alarm函数是一个闹钟函数,参数传一个秒数,表示定时多少秒,时间到了之后则会收到alarm信号。返回值则是剩余的秒数,就是当闹钟被中断时。返回剩余的秒数,参数为0时则终端闹钟。
unsigned int alarm(unsigned int seconds)
我们先测试一下该函数。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>int main()
{alarm(5);while(1){printf("hello linux1\n");printf("hello linux2\n");}return 0;
}
运行程序
我们发现五秒后收到信号。
那么我们取消闹钟试试
#include<stdio.h>
#include<unistd.h>
#include<signal.h>int main()
{alarm(30); //注册闹钟int count = 0;while(1){printf("hello linux1\n");printf("hello linux2\n");sleep(1);count++;if(count == 5){int ret = alarm(0); //取消闹钟printf("ret = %d\n",ret); //打印剩余秒数}}return 0;
}
然后我们执行。
5s后闹钟取消了,返回了剩余的秒数,25秒。
信号捕获
信号的捕获函数:
#include <signal.h> //头文件
typedef void (*sighandler_t)(int); // 对函数指针typedef
sighandler_t signal(int signum, sighandler_t handler); //函数
signal函数的作用就是,如果接收到 signum , 那么就用handler函数去处理该信号。
举个例子:
我们执行下面代码,然后尝试用 kill命令对该进程发送2号信号。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>//处理信号函数
void handler(int sign)
{printf("get a sign : %d\n", sign);exit(0);
}int main()
{signal(2,handler); //如果收到二号信号,那么用handler来处理while(1){printf("hello world\n");sleep(1);}return 0;
}
然后我们测试一下。
我们发现最后输出了 get a sign 2 。 说明信号被捕捉了。
我们用 kill -l 可以查看系统中的信号。
我们只看前面31个,那么我们可以把前面31个都用 handler 处理。然后进行一个 除0 操作,会收到几号信号。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>//处理信号函数
void handler(int sign)
{printf("get a sign : %d\n", sign);exit(0);
}int main()
{for(int i = 1; i<= 31 ; i++)signal(i,handler); //如果收到二号信号,那么用handler来处理sleep(5) ; int a = 5 / 0; //5秒之后制造一个信号return 0;
}
然后我们运行一下。
5 秒过后,我们程序没有报错,相反是打印了 收到 8号信号的信息。而 8 号对应的就是SIGFPE信号,浮点数错误。
现在知道信号可以捕获,那么问题来了, 9 号信号能否捕获呢? 我们可以用下面代码试一下。
我们写了一个死循环代码,然后用命令行发送9号信号,看看是否会被捕获。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>//处理信号函数
void handler(int sign)
{printf("get a sign : %d\n", sign);exit(0);
}int main()
{for(int i = 1; i<= 31 ; i++)signal(i,handler); //如果收到二号信号,那么用handler来处理while(1){sleep(1);printf("hello world\n");}return 0;
}
我们试验一下。
最后我们发现, 9 号信号没有被捕获,进程还是被杀死了。
所以得出结论: 9号信号无法被捕获,操作系统不允许有金刚不坏的进程存在!
总结:
信号的四种产生方式:
1.终端键盘输入
2.进程异常
3.系统调用
4.软件条件产生
信号捕获函数:
sighandler_t signal(int signum, sighandler_t handler),其中 handler 是一个函数指针类型
特殊信号:
9号信号无法被捕获!