信号(signal,kill,raise)
信号:
中断:中止、暂停当前正在执行的进程,转而去执行其它的任务。
分类:1.硬中断:来自硬件设备的中断
2.软中断:来自其它程序的中断
而信号就属于软件中断,它提供了一种处理异步事件的方法。
信号的分类:
不可靠信号(这里不可靠指的是信号可能会丢失)
1.编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
2.不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。
3.进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。
可靠信号(实时)
1.位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。
2.可靠信号支持排除,不会丢失。
3.无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction函数进行处理。
LInux下可以通过 kill -l 查看信号列表
信号处理方式:
1.忽略:大多数信号都可使用这种方式进行处理,但SIGKILL、SIGSTOP这两个信号绝对不能被忽略:它们向内核和超级用户提供了使进程终止或停止的可靠方法。
2.终止:大多数信号的系统默认处理方式都是终止。
3.终止+core:表示在进程当前工作目录的core文件中复制了该进程的内存映像,大多数UNIX系统调试程序都使用core文件检查进程终止的状态。ubuntu默认不产生core,需要使用命令设置:ulimit -c unlimited 这样运行程序后就可以通过gdb ./a.out core 来查看程序出错的位置(gcc编译时要加上-g 产生调试信息)
4.捕获并处理:为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数,在用户函数中执行用户想对这个信号进行的处理(signal(),注册信号处理函数)。注意,SIGKILL/SIGSTOP这两个信号同样也不能被捕获。
signal函数:
头文件: #include <signal.h>
其原本的原型为
void (*signal(int signo,void (*func)(int)))(int);
上述这种形式过于复杂,一般都采用下列这种形式:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数功能:注册一个信号处理函数
signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。
handler:1.函数指针,即用户想要对此信号进行处理的函数
2.SIG_IGN 忽略信号
3.SIG_DFL 恢复信号默认的处理方式
返回值:之前的信号处理方式
函数指针、SIG_IGN、SIG_DFL、SIG_ERR
子进程的信号处理:
1.当一个进程调用fork时,因为子进程在开始时复制父进程的存储映像,信号捕捉函数的地址在子进程中是有意义的,所以子进程继承父进程的信号处理方式。
2.特殊的是exec,因为exec运行新的程序后会覆盖从父进程继承来的存储映像,那么信号捕捉函数在新程序中已无意义,所以exec会将原先设置为要捕捉的信号都更改为默认动作,但是,exec创建的子进程会继承父进程的信号忽略处理。
演示一个ctrl+Z、ctrl+C、ctrl+\ 都结束不了的程序
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
signal(SIGINT,SIG_IGN);//忽略SIGINT信号
signal(SIGQUIT,SIG_IGN);//忽略SIGQUIT信号
signal(SIGTSTP,SIG_IGN);//忽略SIGTSTP信号
printf("My pid:%d",getpid());
for(;;)
{
printf(".\n");
sleep(1);
}
return 0;
}
想要杀死这个进程,就要通过kill -9 +打印的进程号,或者直接关掉终端即可(终端为此进程的父进程) 。
信号的发送方式:
1、键盘
Ctrl+c 终端中断信号
Ctrl+z 终端暂停信号,fg命令再次开启
Ctrl+/ 终端退出信号
2、错误产生的信号
除0
非法内存访问
硬件总线
3、命令产生的信号
kill -信号 进程号
killall -信号 程序名(杀死所有同名的进程)。
4、函数产生的信号
kill函数:
int kill(pid_t pid, int sig);
函数功能:向指定的进程发送信号
pid:进程号
sig:信号
0表示空信号,不会向进程发送信号,但是会测试是否能向pid发送信号,这样可以检测一个进程是否存在
返回-1表示进程不存在,errno会被设为ESRCH。
返回值:-1,说明进程不存在
raise函数:
int raise(int sig);
功能:向自己发送信号
下面演示一下利用signal(),kill(),以及fork()创建的子进程会继承父进程信号处理方式的例子
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
void sigsegv(int num)//用户定义的段错误信号处理函数
{
printf("Receive segment fault signal:%d\n",num);
exit(0);
}
int main()
{
pid_t pid;
if(SIG_ERR == signal(SIGSEGV,sigsegv))//signal函数,表示对SIGSEGV的处理
{
perror("signal");
return -1;
}
if (0 == fork())//创建子进程
{
printf(" son pid:%d my father pid:%d\n",getpid(),getppid());
while(1);
}
printf(" father pid:%d my father pid:%d\n",getpid(),getppid());
while(1)
{
sleep(1);
puts("Please input pid to message:");
scanf("%d",&pid);//输入需要传递段错误信号的进程
kill(pid,SIGSEGV);//向pid进程发送信号
}
return 0;
}
结果显示:
子进程pid为21289 父进程pid为21288,第一次向子进程发送段错误信号,子进程立马通过signal函数调用了用户函数对段错误信号进行处理,如图即为打印接受到了段错误并退出,第二次向父进程发送段错误信号同理,程序结束。