目录

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)

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

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

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

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

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. 常见的信号: 

5. 信号的基本操作

5.1 kill函数

代码示例: 

#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 <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<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<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函数

注意:

        即使 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<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<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)

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

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

总结:

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

06-28 05:52