目录
一:信号引入
二:信号保存方式
三:信号处理方式
四:查看Linux信号
五:信号捕捉
六:信号产生
一:终端按键产生信号
二:系统函数产生信号
2.1:kill()
2.2:raise()
2.3:abort()
三:软件条件产生信号
一:信号引入
生活中有很多信号:红绿灯,闹钟,警灯,外卖员的电话等等。把这些称作信号是有一个共同特点:接收到这些信号后,会执行一个动作。因此我们引出Linux中的信号。
1:为什么会执行这个动作?说明我们(进程)可以识别这个信号。
2:我们识别到信号,能够做出对应的动作。说明我们(进程)可以处理信号。
3:当我们在打游戏时接收到外卖员电话,我们可能会说等一等,我过会去拿。这说明信号可能随时产生,我们(进程)会记录信号。
4:在我们让外卖员等一等的时候,我们仍然在干别的事情,说明我们(进程)在识别信号---处理信号之间有个时间窗口。
5:整个拿外卖的过程是异步的,也就是说进程信号的产生是异步的。(因为进程执行到任何地方,都有可能接收SIGINT而终止)
二:信号保存方式
在系统中一定会产生多个进程,对多个进程发信号,就要把这么多信号保存起来,因此采用先描述再组织的方法。
Linux中采用位图,用0和1表示信号的有无,通过不同的比特位为1表示不同的信号。
所谓的发信号就是写入信号,修改特定进程的位图中对应的比特位为1。而这种task_struct数据内核结构,只能由OS来修改。无论有多少信号写入进程,最后都是由OS来完成最后的发送过程。
三:信号处理方式
- 默认动作
- 忽略信号
- 用户自定义动作
四:查看Linux信号
之前我们学进程章节有用过kill -9来杀死一个进程,现在可以用kill -l查看信号。
共61个信号,没有0信号,32信号,33信号。
1--31信号为普通信号。
34-64信号为实时信号。
通过man 7 signal查看详细的信号信息:
Term:正常退出
Core:异常退出,可以使用核心转储功能定位错误(core dump)
Lgn:内核级忽略
2)SIGINT 终止信号,即键盘输入ctrl+c
3)SIGQUIT 终止信号,即键盘输入ctrl+\
6)SIGABRT 终止信号 调用abort即可收到该信号
8)SIGFPE 终止信号 除0错误即可收到该信号
11)SIGSEGV 终止信号 段错误即可收到该信号
13)SIGPIPE 终止信号 匿名管道读端关闭,写端即可收到该信号
14)SIGALRM 终止信号 alarm()函数(定时器)
17)SIGCHLD 内核级忽略信号 子进程退出时会向父进程发送该信号
18)SIGURG 继续进程(进程切换至后台运行,通过9号信号杀掉)
19)SIGSTOP 暂停进程
可以发现,很多信号的功能(Action)是一样的,为什么这么多信号,功能却要设置一样,就定义几个信号不就行了吗?这是因为不同的信号对应不同的事件,而不同的事件处理结果可以是一样的。
五:信号捕捉
man signal
SIGNAL(2)
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
首先要对void进行typedef,定义为参数为int的函数指针。
参数一:需要捕捉的信号的编号(1-64)
参数二:对捕捉到的信号设置自定义动作
handler设置为SIG_DFL表示信号默认处理方式,SIG_ING设置为忽略处理
#include<signal.h>
#include<iostream>
#include<unistd.h>
typedef void (*sighandler_t)(int);
void handler(int signo)
{
std::cout<<"进程捕捉到信号并执行自定义处理动作"<<std::endl;
}
int main()
{
signal(2,handler);
while(1)
{
std::cout<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
将二号信号的动作更改为我们用户自定义的动作,而二号信号就是ctrl +c,或者kill -2,当输入ctrl+c的时候没有终止进程,而是执行我们的动作。
如果写一个循环,把每个信号都捕捉呢?那肯定不行,在Linux中,9号始终是杀死进程,19号是暂停进程。
注意:当用了signal函数后,handler并没有执行!
六:信号产生
一:终端按键产生信号
SIGINT默认处理动作是终止进程
SIGQUIT默认处理动作是终止进程并且Core Dump
这个知识点和下篇的核心转储一起讲。
注意:ctrl+c默认动作只能杀掉前台进程
运行一个进程的时候如果+&,就会后台运行。 这里是杀不掉的,只能用kill -9
二:系统函数产生信号
2.1:kill()
man 2 kill
参数1是进程pid,参数2是信号编号
成功返回0,失败返回-1
我们借用kill()来仿写一个命令行的kill命令
mykill.cc
void usage(const std::string s)
{
std::cout<<"\t usage: \n\t";
std::cout<<s<<"-信号编号 进程pid"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(1);
}
pid_t pid = atoi(argv[2]);
int signo = atoi(argv[1]);
int n = kill(pid,signo);
if(n!=0)
{
std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
exit(2);
}
return 0;
}
loop.cc
int main()
{
while(true)
{
std::cout<<"我是一个进程 pid :"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
argc和argv在环境变量章节有讲到(后面补),是命令行的参数个数,和命令行分割后的结果 ,需要使用c语言的atoi把命令行参数变为整形的(pid和signo)。
2.2:raise()
参数就是信号编号
给当前进程发送信号。
成功返回0,错误返回-1
2.3:abort()
让当前信号接收到信号而终止。因为有exit,所以abort很少用,abort函数总是会成功的所以没有返回值。
三:软件条件产生信号
alarm是14号信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动
作是终止当前进程。
可以通过alarm,来查看一个机器的算力。如:
static int n = 0;
void myhandler(int signo)
{
std::cout<<"n = :"<<n<<std::endl;
exit(0);
}
int main()
{
signal(SIGALRM,myhandler);
alarm(1);
while(1)
{
++n;
}
return 0;
}
捕捉alarm的信号改为用户自定义信号。否则默认动作退出进程不知道n是多少。
可以看到n是4亿多。
再看alarm函数,返回值是上一个闹钟剩下的时间,验证一下:
void myhandler(int signo)
{
int n = alarm(10);
std::cout<<"last remain time: "<<n<<std::endl;
}
int main()
{
std::cout<<getpid()<<std::endl;
signal(SIGALRM,myhandler);
alarm(10);
// while(1)
// {
// ++n;
// }
while(true)
{
sleep(1);
}
return 0;
}
本节讲完,坐等下篇!