信号(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函数调用了用户函数对段错误信号进行处理,如图即为打印接受到了段错误并退出,第二次向父进程发送段错误信号同理,程序结束。

猜你喜欢

转载自blog.csdn.net/canger_/article/details/81225809