我是靠谱客的博主 超级斑马,最近开发中收集的这篇文章主要介绍信号处理,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

信号是一种异步的通信方式(信号的到来是异步的,可以与管道对比下)

Linux系统中有许多信号,前31号信号都有一个特殊的名字,对应于一个特殊的时间,有时会将他们成为非实时信号,这些信号都是从UNIX系统继承下来的,他们还有个名称叫做不可靠信号。后面31个信号是Linux系统新增的实时信号,也被称作可靠信号,而非实时信号没有固定的次序

非实时信号特点

  • 非实时信号不排队,信号的响应会相互嵌套
  • 如果目标进程没有及时响应非实时信号,那么随后到达的该信号会被丢弃
  • 每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。但并不是说产生了该信号就发生了该事件,任何信号都可以通过kill来产生信号
  • 如果进程的挂起信号中有实时和非实时信号,那么进程会优先响应实时信号并且会从大到小依次响应,而非实时信号没有固定的次序
  • 信号SIGKILL和SIGSTOP是两个特殊的信号,他们不能被忽略、阻塞或捕捉,只能按照缺省动作来响应

非实时信号特点

  • 实时信号的响应次序按接收顺序排队,不嵌套
  • 即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应
  • 实时信号没有特殊的系统时间与之对应

信号的处理动作

  1. 如果该信号被阻塞,那么将该信号挂起,不对其做任何操作,等到解除对其阻塞为止。否则进入第二步
  2. 如果该信号被捕捉,那么进一步判断捕捉的类型
  • 如果设置了响应函数,那么执行该响应函数
  • 如果设置为忽略,那么直接丢弃该信号
  1. 执行该信号的缺省动作

在这里插入图片描述
在这里插入图片描述

  1. 每一个线程都使用一个 PCB(即 task_struct)来表示,因此 pending(不是指针)
    就是一个线程单独私有的,当我们使用 pthread_kill( )给一个指定的线程发送某信号时,这些信号将会被存储在这个链队列中

  2. signal 是一个指向线程共享的信号挂起队列相关结构体的指针,实际上,一个线程
    组(即一个进程)中的所有线程中的 signal 指针都指向同一个结构体,当我们使用诸如 kill( )来给一个进程发送某信号的时候,这些信号将会被存储在 shared_pending 这个线程共享的链队列中(内核链表串起来)。表示当前悬而未决的信号。 如果一个进程中有超过 1 条线程,那么这些共享的挂起信号将会被随机的某条线程响应,为了能确保让一个指定的线程响应来自进程之外的、发送给整个进程的某信号,一般的做法如下:除 了指 定要 响 应某 信号 的线 程 外 , 其他 线 程对 这些 信号 设 置阻 塞。 即 使 sigprocmask( )或者 pthread_sigmask( )将这些需要阻塞的信号添加到信号阻塞掩码blocked 当中。

  3. sighand 也是一个指针,因此也是进程中的所有线程共享的,他指向跟信号响应函
    数相关的数据结构,结构体 struct sighand_struct{}中的数组 action 有 64 个元素,一一对应 Linux 系统支持的 64 个信号(其中 0 号信号是测试用的, 32 号和 33 号信号保留),每一个元素是一个 sigaction{}结构体,其成员就是标准 C 库函数 sigaction( )中的第二个参数的成员,可见,该函数相当于是一个应用层给内核设置信号响应策略的窗口。

  4. 对于一个task_struct来说,其中的blocked成员表示需要阻塞的信号,我们可以发现blocked并不是一个指针,所以该成员是线程私有的

sigwait

int sigwait(const sigset_t *set, int *sig);

同步地等待一个异步事件,调用sigwait前会将set参数指定的信号集阻塞,然后等待其中某个信号的到来,如果有的话,那么*sig就是该信号的值。
推荐在多线程化的进程中使用sigwait来处理所有信号,而不是使用异步信号处理程序。

实时行为

只有保证使用实时信号并且安装信号处理程序时必须给sigaction指定SA_SIGINFO标志,才能保证实时行为,其他情况下,视实现的不同而不同

实时的含义:

  • 信号是排队的
  • 当有多个实时信号被挂起的时候,俺么较小信号优先于较大信号响应
  • 使用实时信号可以携带更多数据,额外的数据只有在SI_ASYNCIO,SI_MESGQ,SI_QUEUE,SI_USER时有效
  • 一些新函数定义成使用实时信号工作

信号的阻塞掩码,会被继承给子进程

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

void catch_sig(int sig)
{
	fprintf(stderr, "%d catch SIGQUIT.n", getpid());
}

int main(int argc, char **argv)
{
	signal(SIGQUIT, catch_sig);

	/*
	** sigmask WILL be inherit to child process
	** therefore, child process will block SIGQUIT
	** until proceeding unblock action.
	*/
	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGQUIT);
	sigprocmask(SIG_BLOCK, &sig, NULL);

	pid_t pid;
	pid = fork();

	if(pid == 0)
	{
		fprintf(stderr, "child: %dn", getpid());

		/*
		** during this period of 20s, if this process
		** receive signal SIGQUIT, it will be blocked.
		** until child process call sigprocmask() to
		** unblock it.
		**
		** NOTE: child process WILL inherit sigmask from
		** their parent, and then won't associate with
		** each other.
		*/
		int count = 10;
		while(count > 0)
		{
			printf("%dn", count--);
			sleep(1);
		}

		sigprocmask(SIG_UNBLOCK, &sig, NULL);

		pause();
	}
	
	else if(pid > 0)
	{
		fprintf(stderr, "parent: %dn", getpid());
		sigprocmask(SIG_UNBLOCK, &sig, NULL);

		pause();
	}

	return 0;
}

挂起的信号是不会被子进程继承的

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

void catch_sig(int sig)
{
	fprintf(stderr, "%d catch SIGQUIT.n", getpid());
}

int main(int argc, char **argv)
{
	signal(SIGQUIT, catch_sig);

	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGQUIT);
	sigprocmask(SIG_BLOCK, &sig, NULL);

	/*
	** during this period, we will send SIGQUIT to the
	** parent process, and SIGQUIT will pending on the
	** sharing sig-queue, and this pending signal will
	** NOT inherit to its child process.
	*/
	int count = 5;
	while(count > 0)
	{
		printf("count: %dn", count--);
		sleep(1);
	}

	pid_t pid;
	pid = fork();

	if(pid == 0)
	{
		fprintf(stderr, "child: %dn", getpid());

		/*
		** unblock the signal SIGQUIT, child process
		** will NOT call the function catch_sig, this
		** is because the pending signals will be
		** cleared when process fork() a new process.
		**
		** NOTE: pending signals will NOT be inherit
		*/
		sigprocmask(SIG_UNBLOCK, &sig, NULL);

		pause();
	}
	
	else if(pid > 0)
	{
		fprintf(stderr, "parent: %dn", getpid());
		sigprocmask(SIG_UNBLOCK, &sig, NULL);

		pause();
	}

	return 0;
}

不同的信号会相互嵌套,但是已经在响应的相同信号不会嵌套
当我们在执行一个信号的处理函数的时候,然后发出另一个信号,那么就会嵌套,但是如果在嵌套的时候在发送第一个信号,那么就不会嵌套(这个是可以控制的),即除正在处理的信号之外,其他的信号会嵌套,但是同一个信号会顺序处理。

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

void sighand(int sig)
{
	if(sig == SIGUSR1)
	{
		char str[] = "11111";
		int i = 0;
		while(str[i] != '')
		{
			fprintf(stderr, "%c", str[i++]);
			sleep(1);
		}
	}

	if(sig == SIGUSR2)
	{
		char str[] = "22222";
		int i = 0;
		while(str[i] != '')
		{
			fprintf(stderr, "%c", str[i++]);
			sleep(1);
		}
	}
	return;
}

int main(void)
{
	signal(SIGUSR1, sighand);
	signal(SIGUSR2, sighand);

	printf("my pid: %d, send me SIGUSR1 and SIGUSR2.n", getpid());

	while(1)
		pause();

	return 0;
}

对于非实时信号而言,挂起的信号不会重复。对于实时信号而言,挂起的信号可以重复
如果在很短时间内向一个进程发送多个同样的非实时信号,那么只会挂起一次 。但是对于实时信号来说发送多少次就会响应多少次

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

void sighand(int sig)
{
	if(sig == SIGRTMIN)
		printf("receive a SIGRTMIN!n");

	return;
}

int main(void)
{
	signal(SIGRTMIN, sighand);

	// block SIGRTMIN
	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGRTMIN);
	sigprocmask(SIG_BLOCK, &sig, NULL);

	printf("my pid: %d, send me two SIGRTMIN in 20s.n", getpid());
	int i = 20;
	while(i--)
	{
		printf("%dn", i);
		sleep(1);
	}

	/*
	** If there are more than one SIGRTMIN have been sent
	** to this process before it calls the following
	** function, ONE SIGRTMIN will be delivered, and the
	** other will simply be discarded.
	**
	** Therefore, the message "receive a SIGRTMIN" should
	** be printed once when the following function return.
	*/
	sigprocmask(SIG_UNBLOCK, &sig, NULL);
	

	while(1)
		pause();

	return 0;
}

实时信号的响应优先于非实时信号,挂起的实时信号的响应顺序依照从大到小来响应;非实时信号没有优先级,非实时信号也叫不可靠信号,可能会丢失
mechine_gun.c

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

int main(int argc, char **argv)
{
	if(argc != 2)
	{
		printf("Usage: %s <target-PID>n", argv[0]);
	}
	int i;
	for(i=SIGHUP; i<=SIGRTMAX; i++)
	{
		if(i == SIGKILL || i == SIGSTOP ||
		   i == 32      || i == 33)
			continue;

		kill(atoi(argv[1]), i);
	}

	return 0;
}

target.c

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

void sighandler(int sig)
{
	fprintf(stderr, "catch %d.n", sig);
}

int main(int argc, char **argv)
{
	sigset_t sigs;
	sigemptyset(&sigs);

	int i;
	for(i=SIGHUP; i<=SIGRTMAX; i++)
	{
		if(i == SIGKILL || i == SIGSTOP)
			continue;

		signal(i, sighandler);
		sigaddset(&sigs, i);
	}

	printf("[%d]: blocked signals for a while...n", getpid());
	sigprocmask(SIG_BLOCK, &sigs, NULL);
	sleep(10);

	printf("[%d]: unblocked signals.n", getpid());
	sigprocmask(SIG_UNBLOCK, &sigs, NULL);
	
	return 0;
}

sigaction中的临时阻塞掩码的使用

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>

void f(int sig)
{
	int count = 5;

	if(sig == SIGUSR1)
	{
		while(count > 0)
		{
			fprintf(stderr, "a");
			sleep(1);
			count--;
		}
	}

	if(sig == SIGUSR2)
	{
		while(count > 0)
		{
			fprintf(stderr, "b");
			sleep(1);
			count--;
		}
	}
}

int main(int argc, char **argv)
{
	signal(SIGUSR1, f);
	//signal(SIGUSR2, f);

	struct sigaction act;
	bzero(&act, sizeof(act));
	act.sa_handler = f;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGUSR1);

	sigaction(SIGUSR2, &act, NULL);

	while(1)
	{
		pause();
	}

	return 0;
}

信号安全

由于信号的异步性,使得异步信号响应函数的编写应该相当谨慎的,因为他可能会在进程执行某个系统函数的任意时刻触发。如果一个信号打断了一个信号非安全函数或者响应函数调用了信号安全函数的话,进程的执行是不可预料的

实际编程中如果使用了信号非安全的函数,那么我们就要保证这些函数在执行过程中是不会被信号中断的,可以通过设置信号阻塞掩码来保护这些信号非安全函数。另外在编写信号响应函数的时候应该非常谨慎的访问进程的共享资源,必要时加锁保护。响应函数跟进程的其他部分微观上虽然是串行执行的关系,但是由于信号触发点的异步性,就使得信号响应函数的执行和进程的其他部分函数在宏观上是并行执行关系

区别:前面的信号没有优先级,可以嵌套(类似中断),信号发生嵌套的概率很低!如果向一个进程同时发送多个相同信号,除了第一个可以被响应,其余信号会被忽略,对用户无效,就好像看不到一样,不知道。除非你等待这个信号处理完成之后再发送该信号,然后就可以再次响应
后面的信号按次序接收处理,不嵌套。向一个进程“同时”发送同一信号多次,那么就会响应多次

最后

以上就是超级斑马为你收集整理的信号处理的全部内容,希望文章能够帮你解决信号处理所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部