目录
1、信号的产生
2、core 核心转储
3、信号的保存
4、信号的处理
信号是linux系统提供的,让用户或进程给其他进程发送异步信息的一种方式。
常见的信号处理方式:
1、默认行为
2、忽略
3、自定义
1、信号的产生
1、kill命令
我们可以使用命令 kill -l 查看信号,也可以使用 kill -signum pid 对指定进程发送信号。
例如:用 kill -9 将指定的死循环进程杀死。
同样,我们也可以用 kill 命令发送其他信号。
查看信号可以用 man 7 查看 man 手册。
2、键盘
先介绍一个系统调用:signal(),它可以改变信号的默认行为,变为自定义。
参数:signum 为指定进程编号,想改变哪个信号的默认行为,就传哪个编号。
handler 为函数指针,如果进程收到了信号 signum,就执行这个函数。
返回值:该信号的上一个处理方法。
我们在终止进程时经常使用 ctrl + c ,这其实就是向进程发送了一个2号命令。
ctrl + \ :这是3号命令。
证明:当我们把 2号命令和 3号命令的默认行为改成打印,使用 ctrl + c 和 ctrl + \ 能打印出来。
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
using namespace std;
void handler2(int sig)
{
cout << "I am signal2 " << endl;
}
void handler3(int sig)
{
cout << "I am signal3 " << endl;
}
int main()
{
// 改变信号2和3的默认行为
signal(2, handler2);
signal(3, handler3);
while(1)
{
;
}
return 0;
}
3、系统调用
a、kill(),向指定进程发送指定信号
参数: pid为要指定的进程的id,sig为要发送的信号的编号。
返回值:成功返回0;失败返回 -1,并设置错误码。
示例:5 秒后给自己发送 2号信号终止自己
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
using namespace std;
int main()
{
// 5 秒后给自己发送 2号信号终止自己
int i = 0;
while(1)
{
cout << "mypid is " << getpid() << " i = " << i << endl;
if(i == 5)
{
kill(getpid(), 2);
}
++i;
}
return 0;
}
b、raise() :向调用者发送一个指定信号(谁使用这个函数就向谁发)
参数:发送信号的编号。
返回值:成功返回 0;失败返回非0。
示例:5 秒后给自己发送 2号信号终止自己
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
using namespace std;
int main()
{
// 5 秒后给自己发送 2号信号终止自己
int i = 0;
while(1)
{
cout << "mypid is " << getpid() << " i = " << i << endl;
if(i == 5)
{
raise(2);
}
++i;
}
return 0;
}
c、abort():通过发送6号信号终止自己
使用abort发送6号信号。
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
using namespace std;
void handler6(int sig)
{
cout << "I am signal6 " << endl;
}
int main()
{
// 改变信号6的默认行为
signal(6, handler6);
abort();
return 0;
}
d、通过alarm() 设置闹钟,时间到了就发送14号信号
参数:想设定的闹钟的秒数。
返回值:如果上一个闹钟还有剩余时间,返回上一个闹钟的剩余时间,如果上一个闹钟剩余时间为0,返回0。
示例:设置一个 5 秒的闹钟,在死循环中每隔 1 秒打印 1 次信息
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
using namespace std;
int main()
{
// 设置一个5秒的闹钟
alarm(5);
int i = 1;
while(i)
{
cout << "mypid is " << getpid() << ", i = " << i << endl;
sleep(1);
++i;
}
return 0;
}
2、core 核心转储
core功能就是核心转储,将进程在内核中的核心数据,转储到磁盘中,形成core文件。通过core,我们可以定位到进程为什么退出,以及执行到哪行代码退出的。
打开 linux 的 core 功能:
ulimit -a : 查看 core file size 选项,为 0 表示 core 功能关闭,不为 0 表示打开。
ulimit -c 10240 :打开 core 功能,设置文件大小( -c 后面为文件大小 )
我们上面讲过的 abort() 报错信号就是 有core功能的。
如果要生成 core 文件,我们需要先停止 apport 服务:sudo service apport stop
示例代码:
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main()
{
int sum = 0;
int n = 10;
abort();
for(int i = 0; i < n; ++i)
{
sum += i;
}
return 0;
}
可以用 gdb 查看错误信息
3、信号的保存
我们把实际处理信号的动作叫信号的递达(deliver)
从产生到递达之间的状态叫做信号未决(pending)
在进程控制块 task_struct 中,有三个表:
block位图:阻塞位图,置为 1 表示阻塞,不处理该信号。
pending位图:表示是否收到,置为 1 表示收到该信号。
handler方法:序号为几的下标报存着序号为几的信号的执行方法。
因此,我们发送信号的本质就是修改 pending 位图,阻塞信号的本质就是修改 block 位图!在信号递达时,现将 pending 位图清 0 ,再递达。
那么我们如何对信号进行修改呢?
未决与阻塞可以用相同的数据类型表示:sigset_t 也就是位图,系统内也存在着各种对位图进行操作的方法。
1、sigemptyset :将所给参数中的 set 集合全置为 0.
2、sigfillset: 将所给参数中的 set 集合全置为 1.
3、sigaddset:将集合中的 signum 信号置为1,也就是添加该信号。
4、sigdelset:将集合中的 signum 信号置为0,也就是删除该信号。
5、sigismember:判断 signum 是不是 set 中的成员,也就是判断 set 中 signum 标志位是否为1.
sigprocmask可读取或更改进程的信号屏蔽字。
参数:how可以为SIG_BLOCK表示 set 包含了我们想添加的信号屏蔽字。SIG_UNBLOCK表示 set 包含了我们想解除的信号屏蔽字。SIG_SETMASK表示直接将 set 设置为当前的信号屏蔽字。oldset表示:读取当前的信号屏蔽字。
返回值:成功为 0 ,失败返回 -1.
其中,9号以及19号信号无法被屏蔽。
示例:屏蔽2号信号
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, 2);
sigprocmask(SIG_BLOCK, &set, nullptr);
while(true)
{
sleep(1);
}
return 0;
}
多次输入ctrl + c(2号信号),没有终止进程
4、信号的处理
信号是什么时候被处理的呢?
进程从内核态到用户态的时候,信号会被检查并处理。