欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

信号(signal,kill,raise)

程序员文章站 2022-07-12 11:18:01
...

信号:

中断:中止、暂停当前正在执行的进程,转而去执行其它的任务。
         分类:1.硬中断:来自硬件设备的中断
                     2.软中断:来自其它程序的中断

 而信号就属于软件中断,它提供了一种处理异步事件的方法。

信号的分类:      

不可靠信号(这里不可靠指的是信号可能会丢失)
        1.编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
        2.不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。    
        3.进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。

可靠信号(实时)
        1.位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。
        2.可靠信号支持排除,不会丢失。
        3.无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction函数进行处理。

LInux下可以通过 kill -l 查看信号列表

信号(signal,kill,raise)

信号处理方式:

     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;
}

结果显示:

信号(signal,kill,raise)

子进程pid为21289 父进程pid为21288,第一次向子进程发送段错误信号,子进程立马通过signal函数调用了用户函数对段错误信号进行处理,如图即为打印接受到了段错误并退出,第二次向父进程发送段错误信号同理,程序结束。

 

 

相关标签: signal