【信号】信号处理与进程通信:快速上手

目录

0. 信号概述

1. 产生信号的方式:

1.1 当用户按某些终端键时,将产生信号。

1.2 硬件异常将产生信号。

1.3 软件异常将产生信号。

1.4 调用kill函数将发送信号。

1.5 运行kill命令将发送信号。

2. 信号的默认(缺省)处理方式

2.1 终止进程:

2.2 缺省出来:

2.3 停止进程:

2.4 让停止的进程恢复运行:

3. 进程收到信号后的处理方式

3.1 执行系统默认动作

3.2 忽略此信号

3.3 执行自定义信号处理函数

4. 常见的信号: 

5. 信号的基本操作

5.1 kill函数

5.2 alarm函数

5.3 setitimer函数(定时器) 

 5.4 raise函数

5.5 abort函数

5.6 pause函数

5.7 signal函数

6. 信号集

6.1 信号集概述

6.2 信号集数据类型

6.3 定义路径:

 6.4 信号集相关的操作主要有以下几个函数:

7. 信号阻塞集(屏蔽集、掩码)

7.1 sigprocmask函数

7.2 sigpending函数

总结:


0. 信号概述

        信号是软件中断,它是在软件层次上对中断机制的一种模拟。

        信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

        信号是一种异步通信方式。

        进程不必等待信号的到达,进程也不知道信号什么时候到达。

        信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知 用户空间进程发生了哪些系统事件。

        每个信号的名字都以字符 SIG 开头。

        每个信号和一个数字编码相对应,在头文件 signum.h 中,这些信号都被定义为正整数。

信号名定义路径: /usr/include/x86_64-linux-gnu/bits/signum.h (ubuntu12.04)

在 Linux 下,要想查看这些信号和编码的对应关系,可使用命令:kill -l

        信号是由当前系统已经定义好的一些标识,每一个标识都会在特定的场合使用。并且都会对进程有一定的影响,当信号产生时,会让当前信号做出相应的操作。这些信号都是已经定义好的,我们不能自己在去创造,直接使用这些就可以了。

1. 产生信号的方式:

1.1 当用户按某些终端键时,将产生信号。

例如:终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT

           终端上按"Ctrl+\"键通常产 生中断信号 SIGQUIT

           终端上按"Ctrl+z"键通常产生中断信号 SIGSTOP。

1.2 硬件异常将产生信号。

        除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内 核产生适当的信号发送给相应的进程。

1.3 软件异常将产生信号。

        当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。

1.4 调用kill函数将发送信号。

        注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的 所有者必须是超级用户。

1.5 运行kill命令将发送信号。

        此程序实际上是使用 kill函数来发送信号。也常用此命令终止一个失控的后台进程。

2. 信号的默认(缺省)处理方式

当进程中产生了一个信号,就会让当前进程做出一定的反应。

默认处理进程的方式如下:

2.1 终止进程:

        当信号产生后,当前进程就会立即结束。

2.2 缺省出来:

        当信号产生后,当前进程不做任何处理。

2.3 停止进程:

        当信号产生后,使得当前进程停止。

2.4 让停止的进程恢复运行:

        当信号产生后,停止的进程会恢复执行(后台进程)。

注意:每一个信号只有一个默认的处理方式。

3. 进程收到信号后的处理方式

3.1 执行系统默认动作

        对大多数信号来说,系统默认动作是用来终止该进程。

3.2 忽略此信号

        接收到此信号后没有任何动作。

3.3 执行自定义信号处理函数

        用用户定义的信号处理函数处理该信号。

注意:SIGKILL和SIGSTOP这两个信号只能以默认的处理方式执行,不能忽略,也不能自定义。

4. 常见的信号: 

信号 性质 默认处理方式
SIGKILL 9 当产生这个信号后,当前进程会退出,不能被缺省和捕捉 退出进程
SIGSTOP 19 当产生这个信号后,当前进程会停止,不能缺省和捕捉 停止进程
SIGINT 2 键盘输入ctrl+c时产生信号 退出进程
SIGQUIT 3 键盘输入ctrl+\时产生信号 退出进程
SIGSTP 20 键盘输入ctrl+z时产生信号 停止进程
SIGCONT 18 当产生当前信号后,当前停止的进程会恢复运行 停止的进程恢复运行
SIGALRM 14 当调用alarm函数设置的时间到达时会产生当前信号 退出进程
SIGPIPE 13 当管道破裂时,会产生当前信号 退出进程
SIGABRT 6 当调用abort函数时会产生当前信号 退出进程
SIGCHLD 17 当使用fork创建一个子进程时,如果子进程状态改变(退出),会产生当前信号 缺省
SIGUSR1 10 用户自定义信号,不会自动产生,只能使用kill函数或命令给指定的进程发送当前信号 缺省
SIGUSR2 12 用户自定义信号,不会自动产生,只能使用kill函数或命令给指定的进程发送当前信号 缺省

5. 信号的基本操作

5.1 kill函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signum);
功能:
    给指定进程发送信号。
参数:
    pid:
        pid 的取值有 4 种情况: 
        pid>0: 将信号传送给进程 ID 为 pid 的进程。
        pid=0: 将信号传送给当前进程所在进程组中的所有进程。
        pid=-1: 将信号传送给系统内所有的进程。
        pid<-1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
    signum:信号的编号
返回值:
    成功:返回 0
    失败:返回 -1

代码示例: 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

int main(int argc, char* argv[])
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		perror("fork is error:");
		exit(1);
	}
	else if (pid == 0)//子进程的代码区
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			printf("in son process\n");
			sleep(1);
		}
	}
	else//父进程的代码区
	{
		printf("in father process\n");
		sleep(2);
		printf("kill sub process now \n");
		kill(pid, SIGINT);
	}
	return 0;
}

注意:

        使用 kill 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者 发送信号进程的所有者是超级用户。

5.2 alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能: 
    定时器,闹钟,当设定的时间到达时,会产生SIGALRM信号
参数:
    seconds:设定的秒数
返回值: 
    如果alarm函数之前没有alarm设置,则返回0
    如果有,则返回上一个alarm剩余的时间

代码示例: 

#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
	int seconds = 0;
	seconds = alarm(5);
	printf("seconds = %d\n", seconds);
	sleep(2);
	seconds = alarm(5);
	printf("seconds = %d\n", seconds);
	while (1);
	return 0;
}

5.3 setitimer函数(定时器) 

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval
*old_value);
功能:
    设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
参数:
    which:指定定时方式
        a) 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
        b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间
        c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
    new_value:struct itimerval, 负责设定timeout时间
    struct itimerval {
        struct timerval it_interval; // 闹钟触发周期
        struct timerval it_value; // 闹钟触发时间
    };
    struct timeval {
        long tv_sec; // 秒
        long tv_usec; // 微秒
    }
        itimerval.it_value: 设定第一次执行function所延迟的秒数
        itimerval.it_interval: 设定以后每几秒执行function
    old_value: 存放旧的timeout值,一般指定为NULL
返回值:
    成功:0
    失败:-1

代码示例: 

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

void myfunc(int sig)
{
	printf("hello\n");
}
int main(int argc, char* argv[])
{
	struct itimerval new_value;
	//定时周期
	new_value.it_interval.tv_sec = 1;
	new_value.it_interval.tv_usec = 0;
	//第一次触发的时间
	new_value.it_value.tv_sec = 2;
	new_value.it_value.tv_usec = 0;
	signal(SIGALRM, myfunc); //信号处理
	setitimer(ITIMER_REAL, &new_value, NULL); //定时器设置
	while (1);
	return 0;
}

 5.4 raise函数

#include <signal.h>
int raise(int signum);
功能:
    给调用进程本身送一个信号。
参数:
    signum:信号的编号。
返回值:
    成功:返回 0
    失败:返回 非0

raise(sig) <==> kill(getpid(),sig)

代码示例: 

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>

int main(int argc, char const* grgv[]) 
{
	int num = 0;
	while (1) {
		printf("hello world\n");
		sleep(1);
		num++;

		//循环执行5秒后,进程退出
		if (num == 5) {
			raise(SIGINT);
			//kill(getpid(), SIGINT);
		}
	}

	return 0;
}

5.5 abort函数

#include <stdlib.h>
void abort(void);
功能:
    向进程发送一个 SIGABRT 信号,默认情况下进程会退出
参数:
    无
返回值:
    无

注意:

        即使 SIGABRT 信号被加入阻塞集,一旦进程调用了 abort 函数,进程也还是会被终止,且在终止前会刷新缓冲区,关文件描述符。

代码示例: 

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>

int main(int argc, char const* grgv[]) 
{
	int num = 0;
	while (1) {
		printf("hello world\n");
		sleep(1);
		num++;

		//循环执行5秒后,进程退出
		if (num == 5) {
			abort();
		}
	}

	return 0;
}

5.6 pause函数

#include <unistd.h>
int pause(void);
功能:
    将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到。
参数:
    无
返回值:
    直到捕获到信号,pause 函数才返回-1,且 errno 被设置成 EINTR。

代码示例: 

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>

int main(int argc, char* argv[])
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		perror("fail to fork");
		exit(1);
	}
	else if (pid > 0)//父进程的代码区
	{
		printf("in father process\n");
		pause();
	}
	else//子进程的代码区
	{
		printf("in son process\n");
		sleep(3);
		kill(getpid(), SIGINT);
	}
	return 0;
}

5.7 signal函数

#include <signal.h>
void (*signal(int sig,void(*func)(int)))(int)
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum,sighandler_t handler);
功能: 
    当进程中产生某一个信号时,对当前信号进行处理
参数: 
    sig:指定要处理的信号
    handler:处理方式
        SIG_INT 当信号产生时,以缺省(忽略)方式处理
        SIG_DFL 当信号产生时,以当前信号默认的方式处理
        void handler(int sig):当信号产生时,通过信号处理函数自定义方式处理,
                                函数名可以随便写,参数表示当前的信号
返回值:
    成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回
值,必须在前面先声明此函数指针的类型。
    失败:SIG_ERR

代码示例: 

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>

void handler(int sig);

int main(int argc, char* argv[])
{
	//以默认的方式处理信号
#if 0
	if (signal(SIGINT, SIG_DFL) == SIG_ERR) {
		perror("fail to signal");
		exit(1);
	}
	if (signal(SIGQUIT, SIG_DFL) == SIG_ERR) {
		perror("fail to signal");
		exit(1);
	}
#endif

	//以忽略的方式处理信号
#if 0
	if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
		perror("fail to siganl");
		exit(1);
	}
	if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) {
		perror("fail to signal");
		exit(1);
	}
#endif

	//以用户自定义方式处理信号
#if 0
	if (signal(SIGINT, handler) == SIG_ERR) {
		perror("fail to signal");
		exit(1);
	}
	if (signal(SIGQUIT, handler) == SIG_ERR) {
		perror("fail to signal");
		exit(1);
	}
#endif 

	while (1) {
		printf("hello world\n");
		sleep(1);
	}
	void handler(int sig) {
		if (sig == SIGINT) {
			printf("SIGINT正在处理\n");
		}
		if (sig == SIGQUIT) {
			printf("SIGQUIT正在处理\n");
		}
	}
	return 0;
}

返回值:

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>

void handler(int sig);
void* ret_handler;
void handler(int sig) {
	printf("******************************\n");
	printf("nihao beijing\n");
	printf("welcome to hangzhou\n");
	printf("******************************\n");

	if (signal(SIGINT, ret_handler) == SIG_ERR) {
		perror("fail to signal");
		exit(1);
	}
}
int main(int argc, char* argv[])
{
	if ((ret_handler = signal(SIGINT, handler)) == SIG_ERR){
		perror("fail to siganl");
		exit(1);
	}
	while (1) {
		printf("hello world\n");
		sleep(1);
	}

	return 0;
}

6. 信号集

6.1 信号集概述

        一个用户进程常常需要对多个信号做出处理。为了方便对多个信号进行处理,在Linux系统中引入了信号集。

        信号集是用来表示多个信号的数据类型。

6.2 信号集数据类型

        sigset_t

6.3 定义路径:

        /usr/include/x86_64-linux-gnu/bits/sigset.h   (ubuntu12.04)

 6.4 信号集相关的操作主要有以下几个函数:

  • sigemptyset
  • sigfillset
  • sigismember
  • sigaddset
  • sigdelset
1----sigemptyset()
#include <signal.h>
int sigemptyset(sigset_t *set);
功能: 
    初始化由 set 指向的信号集,清除其中所有的信号即初始化一个空信号集。
参数: 
    set:信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。
返回值: 
    成功:返回 0
    失败:返回 -1

2----sigfillset()
#include <signal.h>
int sigfillset(sigset_t *set);
功能: 
    初始化信号集合 set, 将信号集合设置为所有信号的集合。
参数: 
    信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。
返回值:
    成功:返回 0
    失败:返回 -1

3----sigismember()
#include <signal.h>
int sigismember(const sigset_t *set,int signum);
功能: 
    查询 signum 标识的信号是否在信号集合 set 之中。
参数: 
    set:信号集标识符号的地址。
    signum:信号的编号。
返回值: 
    在信号集中返回 1,不在信号集中返回 0
    错误:返回 -1

4----sigaddset()
#include <signal.h>
int sigaddset(sigset_t *set, int signum);
功能: 
    将信号 signum 加入到信号集合 set 之中。
参数: 
    set:信号集标识的地址。
    signum:信号的编号。
返回值: 
    成功:返回 0
    失败:返回 -1

5----sigdelset()
#include <signal.h>
int sigdelset(sigset_t *set, int signum);
功能: 
    将 signum 所标识的信号从信号集合 set 中删除。
参数: 
    set:信号集标识的地址。
    signum:信号的编号。
返回值: 
    成功:返回 0
    失败:返回 -1

代码案例: 

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

v
int main(int argc, char* argv[])
{
	//创建一个信号集
	sigset_t set;
	int ret = 0;
	//初始化一个空的信号集
	sigemptyset(&set);
	//判断SIGINT信号是否在信号集中
	ret = sigismember(&set, SIGINT);
	if (ret == 0) {
		printf("SIGINT is not a member of sigprocmask\n ret=%d\n", ret);
	}
	//将指定的信号添加到信号集中
	sigaddset(&set, SIGINT);
	sigaddset(&set, SIGQUIT);

	ret = sigismember(&set, SIGINT);
	ir(ret == 1) {
		printf("SIGINT is a member of sigprocmask\n ret=%d\n", ret);
	}
	return 0;
}

7. 信号阻塞集(屏蔽集、掩码)

        每个进程都有一个阻塞集,它用来描述哪些信号递送到该进程的时候被阻塞(在信 号发生时记住它,直到进程准备好时再将信号通知进程)。

         所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻 塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

7.1 sigprocmask函数

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能: 
    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
参数: 
    how:信号阻塞集合的修改方法。
        SIG_BLOCK:向信号阻塞集合中添加 set 信号集
        SIG_UNBLOCK:从信号阻塞集合中删除 set 集合
        SIG_SETMASK:将信号阻塞集合设为 set 集合
    set:要操作的信号集地址。
    oldset:保存原先信号集地址。
    注:若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset 中。
返回值: 
    成功:返回 0
    失败:返回 -1

代码示例: 

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

int main(int argc, char* argv[])
{
	int i = 0;
	//创建一个信号集
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	while(1){
		//将set信号集添加到信号阻塞集中
		sigprocmask(SIG_BLOCK, &set, NULL);
		for (i = 0; i < 5; i++) {
			printf("SIGINT signal is blocked\n");
			sleep(1);
		}
		//将set信号集从信号阻塞集中删除
		sigprocmask(SIG_UNBLOCK, &set, NULL);
		for (i = 0; i < 5; i++) {
			printf("SIGINT signal unblocked\n");
			sleep(1);
		}
	}
	return 0;
}

7.2 sigpending函数

#include <signal.h>
int sigpending(sigset_t *set);
功能:
    读取当前进程的未决信号集
参数:
    set:未决信号集
返回值:
    成功:0
    失败:-1

代码示例: 

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

int main()
{
	// 自定义信号集
	sigset_t myset, old;
	sigemptyset(&myset);// 清空 -》 0
	// 添加要阻塞的信号
	sigaddset(&myset, SIGINT);
	sigaddset(&myset, SIGQUIT);
	sigaddset(&myset, SIGKILL);
	// 自定义信号集设置到内核中的阻塞信号集
	sigprocmask(SIG_BLOCK, &myset, &old);
	sigset_t pend;
	int i = 0;
	while (1)
	{
		// 读内核中的未决信号集的状态
		sigpending(&pend);
		for (int i = 1; i < 32; ++i)
		{
			if (sigismember(&pend, i))
			{
				printf("1");
			}
			else if (sigismember(&pend, i) == 0)
			{
				printf("0");
			}
		}
		printf("\n");
		sleep(1);
		i++;
		// 10s之后解除阻塞
		if (i > 10)
		{
			// sigprocmask(SIG_UNBLOCK, &myset, NULL);
			sigprocmask(SIG_SETMASK, &old, NULL);
		}
	}
	return 0;
}

总结:

        信号机制是操作系统中用于处理异步事件的一种强大工具,它提供了一种处理程序中非预期事件(如硬件错误、特定的用户交互等)的方法。理解和有效使用信号机制,可以帮助我们编写更健壮、更稳定的程序。总的来说,信号是进程通信的重要方式之一,它的理解和掌握对于系统编程人员至关重要。

猜你喜欢

转载自blog.csdn.net/crr411422/article/details/131422130