Linux进程通信——信号(一)

原理

对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。
信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序。

概述

信号的名字和编号

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGUP(挂起) ”、“SIGINT(中断)、SIGQUIT(退出)”等等。
信号定义在signal.h头文件中,信号名都定义为正整数
具体的信号名称可以 使用kill -l查看信号的名字以及序号
信号是从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。

Linux进程通信——信号(一)_第1张图片

信号的处理

信号的处理方式有三种,分别是忽略、捕捉和默认动作

忽略信号

大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。

系统自带的忽略宏函数

 signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、2)忽略

捕捉信号

需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。( 在main函数外定义一个函数,用signal函数中的参数调用该函数并执行函数中的功能)

系统默认动作

对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。

例子如下:

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID 来杀死进程。比如,我在后台运行了一个 a.out 工具,通过 ps 命令可以查看他的 PID,通过 kill 9来发送了一个终止进程的信号来结束了 a.out 进程。如果查看信号编号和名称,可以发现9对应的是 SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。

kill -9 进程PID
kill -SIGKILL 进程PID

Linux进程通信——信号(一)_第2张图片Linux进程通信——信号(一)_第3张图片

可见,两者的执行结果相同。说明kill命令是发送信号的工具

signal函数

功能

设置某一信号的对应动作

头文件

#include 

原型

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函数解读

第一行是真实处理信号的函数:中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号,即注册函数的第二个参数可以调用信号处理函数并执行其中的功能。

第二行是信号处理注册的函数:

signum 信号的编号,如SIGKILL的编号是9
handler 中断函数的指针,写入后可以调用编写的真实处理信号函数并执行功能

signal()会依参数signum指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行

返回值

成功则返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。

代码示例

信号处理函数的注册

signal1.c

#include 
#include 

void handler(int signum)
{
	printf("get signum is %d\n",signum);
	printf("not quit\n");
        switch(signum)
        {
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
		        case 10:
			            printf("SIGUSR1\n");
			            break;
	    }
}

int main()
{
	signal(SIGINT,handler);
	signal(SIGUSR1,handler);
	while(1);
	
	return 0;
}

代码编译后查看运行a.out工具,通过ps查看其编号

运用kill指令分别对信号进行处理

注:第一种按下crtl+c执行结果相同。

可见调用signal函数后匹配的正确编号后会执行handler中的功能(将函数编号打印出来)。第三个与前两个结果不一样是因为SIGKILL指令无法被忽略,这里的kill -9发出的是指令,由于代码为死循环,若SIGKILL被忽视,则会导致代码无法终止循环,所以一旦SIGKILL指令发出,程序立刻停止(被杀死)。

发送信号处理函数

signal2.c

#include 
#include 

int main(int argc,char **argv)//由于需要此代码发送指令另一部分代码才会执行,所以需要进行传参,参数为kill参数,格式为./a.out pid signum
{
	int signum;
	int pid;

	signum = atoi(argv[1]);
	pid = atoi(argv[2]);
	
	kill(pid,signum);//调用kill函数,将信号处理编号和工具的pid值输入即可
	printf("send signal success\n");
	
	return 0;
}

 先编译signal1.c(上一模块的代码)并运行

调用ps指令查看该程序的信号值

编译运行signal2.c中的代码传参即可运行signal1.c代码中的功能

Linux进程通信——信号(一)_第4张图片

将signum与pid输入后即可实现signal1.c中的功能,实现信号捕捉处理。

功能与signal2.c一样的代码:

#include 
#include 

int main(int argc,char **argv)
{
	int signum;
	int pid;
	char cmd[128] = {0};

	signum = atoi(argv[1]);
	pid = atoi(argv[2]);
	
	sprintf(cmd,"kill -%d %d",pid,signum);//cmd的指令格式为“”里的格式,即调用kill指令
	system(cmd);//调用cmd指令

	printf("send signal success\n");
	
	return 0;
}

注:
1、atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数
2、sprintf指的是字符串格式化命令,函数原型为

 int sprintf(char *string, char *format [,argument,…]);

主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串

信号忽略函数的补充

代码展示

#include
#include 
void handler(int signum)
{
        printf("get signum=%d\n",signum);
        switch(signum)
        {
                case 2:
                        printf("SIGINT\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}
int main()
{
        signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、2)忽略
        signal(SIGUSR1,SIG_IGN);//将SIGUSR1信号(10)忽略

        while(1);

        return 0;
}

可见crtl+c和kill -10和kill -2都被忽略了,只有kill -9才能使该程序终止,印证的信号处理中的忽略部分不能忽略SIGKILL。

你可能感兴趣的:(Linux系统编程,linux)