linux系统编程--信号

信号的概念

什么是信号?信号其实是编号从1-64的一组数,用SIG开头的宏表示。信号是一个软中断,可以给我们提供一种停止当前执行流,进而去执行另一部分代码的方法。其中,1-31是基本信号,32-64是实时信号。实时信号暂不关注。

信号的产生

1、终端按键产生。
比如Ctrl+c产生SIGINT、Ctrl+\产生SIGQUIT信号、Ctrl+z产生SIGITSP信号等等。
2、硬件异常产生。
硬件检测到某些条件发生时,会产生信号。比如除以零、访问非法内存。信号由硬件产生通知内核,然后内核再把信号通知进程。
3、命令及函数产生。
shell下的kill命令及编写代码时用到的kill函数。
4、软件产生
检测到某些软件条件产生的时候会产生信号。比如网络连接上传来带外数据,产生SIGURG信号;往一个读断关闭的管道写数据,产生SIGPIPE信号;进程设置的时钟超时,产生SIGALARM信号。

这里面有几个相关函数:
1、给自己发送信号

#include<signal.h>
	int raise(int signo)
				返回值:成功返回0,失败返回-1。

在指定秒之后给进程自己发送SIGALARM信号。

	#include<unistd.h>
	unsigned int alarm(unsigned int secondes)
		返回值:返回0或者之前设置的闹钟时间的余留描述

2、给自己或者别的进程发送信号
使用kill给调用者自己发送信号的时候,在kill返回之前,signo指定的信号或者其他某个未决、非阻塞信号将被递送至进程。

#include<signal.h>
	int kill(pid_t pid, int signo)
			pid:指明发送信号给哪个进程或者进程组。
				pid > 0   信号发送给进程id为pid的进程。
				pid =  0  信号发送给与发送进程同一进程组内的所有进程,前提是调用kill的进程有权限向这些进程发送信号。
				pid < 0  信号发送给进程组id等于pid的绝对值,并且发送进程有权限向其发送信号的所有进程。
				pid == -1 该信号发送给进程有权发送给的所有进程。
			signo:要发送的信号
返回值:    成功返回零;出错返回-1.

信号的处理方式

linux提供了三种信号处理的方式:

1、忽略此信号。
信号会产生并且递送给进程,但进程不执行任何动作。
2、执行默认动作。
大多数信号的默认动作是终止进程。
3、捕捉信号。
进程可以为特定的信号注册信号处理函数。这样当信号被递送给进程的时候,进程转而去执行信号处理函数,从信号处理函数返回之后再继续原先的执行流

注意:SIGKILL和SIGSTOP不能被忽略也不能被捕捉。因为它们提供了一种可靠的终止进程的方式。

标准信号的不可靠性

对于标准信号(1-31),在信号处理函数执行期间,如果该信号再产生,会被加入到未决信号集中去,等处理函数返回后再递送给进程。但是,如果执行期间该信号产生了多次,只会递送一次,其他的都被丢弃了。

信号集

信号集就是信号的集合,用sigset_t类型表示,sigset_t是typedef的,它其实就是一个64位的数据,每一位代表一个信号,分别是1-64,没有编号为零的信号
信号集有一系列的操作函数,如下:

	#include<signal.h>
	int sigemptyset(sigset_t *set)
	int sigfillset(sigset_t *set)
	int sigaddset(sigset_t *set, int signo)
	int sigdelset(sigset_t *set, int signo)
											返回值:成功返回0,失败返回-1
	int sigismember(const sigset_t *set, int segno)
											返回值:若真,返回1;若假,返回0
												

每个进程都有两个信号集:阻塞信号集和未决信号集

阻塞信号集

每个进程维护一个阻塞信号集,在该集合内的信号不会被递送给进程。
可以通过sigprocmask来查看或者更改阻塞信号集:

#include<signal.h>
	int sigprocmask(int how, const  sigset_t *restrict set, sigset_t *restrict oldset);
																				返回值:成功返回0;出错返回-1

how有下面几个选项:

how 作用
SIG_BLOCK 将set中的信号添加到阻塞信号集中去
SIG_UNBLOCK 将set中的信号从阻塞信号集中移除
SIG_SETMASK 新的阻塞信号集是set指向的集合

调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前至少将其中之一递送给进程

未决信号集

何为未决?信号产生了但是还未递送给进程,称为未决的。如果为进程产生了一个信号,并且对信号的动作是捕捉或者默认动作,那么该信号就会被加入到未决信号集,直到进程对该信号解除阻塞,或者信号的动作更改为忽略;在别的进程运行期间产生的信号,也会被加入到未决信号集。

可以使用sigpending来查看未决信号集。

	int sigpending(sigset_t *set)
												返回值:成功返回0;出错返回-1。

信号与系统调用

如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断,如果该系统调用是可自动重启动的,则重新执行该系统调用。否则该系统调用返回出错,并将errno设置为EINTR.
低速系统调用:可能会是系统永远阻塞的一类系统调用。
如管道、终端、网络设备的数据不存在时,则读操作就有可能永远阻塞。

被中断的系统调用

当进程执行低速系统调用而阻塞的时候,进程的状态是TASK_INTERRUPTIBLE,
但是,当进程收到一个信号,并且该信号不在阻塞集中时,内核会把进程的状态改成TASK_RUNNING,即使系统调用请求的资源没有准备好。这样当进程被调度运行的时候从系统调用返回前会检查有没有未处理的信号,进而去执行信号处理函数。

信号处理方式的更改

1、signal函数

#define SIG_ERR  (void (*)())-1
#define SIG_DFL  (void (*)())0
#define SIG_IGN  (void (*)())1

#include<signal.h>

		typedef void Sigfunc (int);
		Sigfunc *signal(int signo, Sigfunc *func)
			
			func:没有返回值且只有一个int型参数的函数指针,指向信号处理函数。也可以是SIG_IGN或者SIG_DFL。
			返回值:成功返回之前的信号处理配置,失败返回SIG_ERR

signal的缺点:
1、被signal注册的信号处理函数中断的系统调用默认是自动重启动的,当该类型的系统调用被中断之后,不会返回错误,会重新执行下该系统调用。
2、在某个信号处理函数执行期间不能阻塞其他信号。
3、不能给信号处理函数传递额外参数。
下面的sigaction函数可以解决上面的问题。

2、带参数的信号处理函数

2.1 sigaction函数

#include<signal.h>
int sigaction(int signo, const struct sigaction *restrict act,
						struct sigaction *oact)
	返回值:成功返回0,失败返回-1.

2.2 struct sigaction 结构

struct sigaction{
	void (*sa_handler)(int)  /*addr of sighandler or SIG_IGN,SIG_DFL*/
	sigset_t sa_mask;
	int sa_flags
	void (*sa_sigaction)(int, siginfo_t * void *)
}

sa_mask:当信号的动作是捕捉的时候,在信号处理函数返回之前,这一信号集指定的信号会被加到信号屏蔽字当中去,从信号处理函数返回时屏蔽字回复为原先值。同时,信号处理函数执行期间,新信号屏蔽字包含正被递送的信号。
sa_flags:选项有很多,主要列出如下常用几个
SA_INTERRUPT:由此信号中断的系统调用不重新启动。
SA_RESTART:由此信号中断的系统调用自动重启动。
SA_SIGINFO:此选项对信号处理程序提供附加信息。并不是说指定了该选项之后只能使用sa_sigaction函数,也可以使用sa_handler函数,只不过这么使用没什么意义。

2.3 sa_sigaction成员

	void sa_sigaction(int signo, siginfo_t *info,void *context)
		signo:信号
		info:要传递的信息
		context:它表示发送进程在发送信号时的上下文,这个参数暂时不关心。

siginfo_t 实际上是一个结构体,如下:

	typedef struct siginfo
	{
		int si_signo;
		int si_errno;
		int si_code;
		pid_t si_pid;
		uid_t si_uid;
		void *si_addr;
		int si_status;
		union sigval si_value;
	}siginfo_t;
	
union sigval
{
	int sival_int;
	void *sigval_ptr;
}

传递参数给信号处理函数的时候,sa_flags中的SA_SIGINFO要置位
示例:

2.4 发送信号时如何携带参数

使用kill函数无法发送携带额外数据的信号。linux提供了另外一个函数:sigqueue

	int sigqueue(pid_t pid, int signo, const union sigval value)
										返回值:成功返回0;出错返回-1.

猜你喜欢

转载自blog.csdn.net/weixin_42036016/article/details/82914889