21-关于linux信号的基本使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35733751/article/details/82755898

1. 什么是信号

  简而言之,信号是事件发生时用于给进程发送各种通知的机制,以便通知进程发生何种事件。

  站在操作系统的角度来说,信号也称为软件中断,因为信号可以改变程序的执行流程,大多数情况下无法预测信号到达的精确时间。当信号传递给进程时,它会中断进程当前正在进行的任何操作,并强制进程处理或忽略信号,又或者在某些情况下终止进程。


2. 向进程发送信号

  比如写一个A程序每隔1秒打印一次hello world,然后在终端按Ctrl + C时,系统就会产生一个SIGINT信号并发送给A程序,我们无法预测SIGINT信号何时到达A程序,但是当A程序收到SIGINT信号时,无论A程序此时执行到哪里了,SIGINT信号都会中断A程序的执行流程,同时强制A程序处理SIGINT信号,由于SIGINT信号的默认处理动作是终止一个进程,那么A程序会立即终止结束,而不是按正常的执行流程终止结束。


3. 产生信号相关的事件

  虽然信号是进程间通信(IPC)的一种方式,进程也可以向自身发送信号,但是通常是由内核来产生信号的,一般产生信号的事件大概有以下几种:
  1 . 按键产生,如Ctrl+c,Ctrl+z,Ctrl+\等

  2 . 系统调用产生,如kill、raise、abort等系统调用函数,在程序中通过调用某些系统函数给进程发送信号

  3 . 硬件异常产生,如非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)

  4 . 在终端上输入命令产生信号,如kill命令


  拿按键产生信号来说,比如想要终止一个进程就可以通过按键Ctrl+c的方式使系统内核产生一个信号并发送给这个进程,而这个进程接收到这个信号并执行这个信号的默认处理动作:终止一个进程。
  Ctrl+\也是终止一个进程,区别在于Ctrl+c是发送信号终止进程的执行,相当于直接把进程干掉,而Ctrl+\是发送信号让进程尽快的结束运行,没有那么直接。
  Ctrl+z会向正在运行的进程发送SIGTSTP信号,默认情况下,此信号会导致进程暂停执行。


4. linux支持的信号

  有同学可能会问,linux系统中到底有多少信号呢?如果我们想要查看当前系统支持的信号可以使用kill –l命令查看当前系统可使用的信号:
在这里插入图片描述

  linux中的信号的编号是从1开始的,其中编号是1 - 31的称之为常规信号(也叫普通信号或标准信号),34-64号称之为实时信号,驱动编程与硬件相关。另外32和33编号的信号作为保留并未使用,不同的linux系统有些信号可能会不太一样,如果你想要详细了解关于信号更多的资料可以参考《linux/unix系统编程手册(上册)》,这本书对于信号的介绍非常详细。


5 . 使用系统调用kill发送信号

  前面我们讲过shell命令也可以产生信号,比如kill命令,而kill函数实现一样的功能,给指定进程发送指定信号。

函数原型:

int kill(pid_t pid, int sig);	 

返回值:成功0;失败-1并设置errno

参数sig:
  指定要发送的信号,如果sig为0,通常用来测试是否有权限(权限保护)向进程发信号。

参数pid:
  pid > 0表示发送信号给pid指定的进程。

  pid = 0表示发送信号给与调用kill函数的进程属于同一进程组的所有进程

  pid < -1表示取|pid|发给对应进程组

  pid = -1表示向系统中所有进程发送信号,前提是有权限


6. 实验

循环创建5个子进程,任一子进程用kill函数终止其父进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

#define N 5

int main(void) {
	int i;		
	//默认创建5个子进程
	for(i = 0; i < N; i++){
		if(fork() == 0){
			break;			
		}
	}

    if (i == 3) {
        sleep(1);
        printf("-----------child ---pid = %d, ppid = %d\n", getpid(), getppid());
		//子进程终止父进程
        kill(getppid(), SIGKILL);

		//父进程
    } else if (i == N) {
        printf("I am parent, pid = %d\n", getpid());
        while(1);
    }
	return 0;
}

程序执行结果:
在这里插入图片描述


7. raise函数的用法

  raise 函数是给当前进程发送指定信号(自己给自己发),注意raise函数和kill函数的区别。

  当进程调用raise函数向自身发送信号时,信号将会立即递送,即在raise返回前。另外raise出错不一定返回-1,调用raise唯一的错误就是EINVAL,即参数isg无效。

函数原型:

int raise(int sig); 成功:0,失败非0

代码实验:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

int main(void){
	
	int ret;
	while(1){
		
		sleep(1);
		//打印进程id
		printf("pid = %d\n", getpid());
		ret = raise(SIGINT);		
		//失败直接break
		if(ret != 0){
			break;
		}
	}
	return 0;
}

程序执行结果:
在这里插入图片描述


8. 信号的处理方式

信号的处理方式一般有以下几种:

  1. 执行默认动作,对于大多数信号的系统默认动作是终止该进程。

  2. 忽略(丢弃),大多数信号都可使用这种方式来处理

  3. 捕捉(调用自定义信号处理函数)


9. 信号捕捉函数——signal

  根据信号的处理方式可知,如果我们不想信号执行默认的处理动作时,就要进行捕捉信号,并注册自定义信号处理函数,而signal函数就是用来做这件事的。

  需要注意的是,signal函数由ANSI定义,由于历史原因在不同版本的Unix实现中可能存在着差异,这意味着如果程序需要考虑可移植性的话,那么应该尽量避免使用它,取而代之使用sigaction函数。

函数原型:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler); 

参数signum:表示要捕捉的信号

参数handler:捕捉后默认处理动作函数指针

另外系统还为handler参数提供了两个宏,分别是 SIG_DFL和 SIG_IGN :
  1. 如果handler指定为SIG_DFL,系统将为该信号指定默认的信号处理函数。

  2. 如果 handler指定为SIG_IGN,系统将忽略该信号即内核会将信号丢弃,而进程不会知道曾经产生了该信号。


返回值说明:
   成功返回函数指针,即先前的信号处理函数,也有可能是SIG_DFL或SIG_IGN;失败则返回SIG_ERR说明注册失败,设置errno


signal函数实现捕捉信号实验:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

void do_sig(int a)
{
    printf("hello, SIGINT\n");
}

int main(void)
{
    if (signal(SIGINT, do_sig) == SIG_ERR) {
        perror("signal");
        exit(1);
    }

    while (1) {
        printf("---------------------\n");
        sleep(1);
    }

    return 0;
}

程序执行结果:
在这里插入图片描述

   当使用signal函数捕捉了SIGINT信号后,此时在终端输入Ctrl-C发送SIGINT信号不再是让进程退出了,而是调用注册的信号处理函数,打印hello SIGINT。

猜你喜欢

转载自blog.csdn.net/qq_35733751/article/details/82755898