6.7 信号的安装

6.7 信号的安装

前面讲了传统信号的很多弊端,讲了signal的兼容性问题,有问题就会有解决方案。

对此,Linux提供了新的信号安装方法:sigaction函数。

和signal函数相比,这个函数的优点在于语义明确,可以提供更精确的控制。

先来看一下sigaction函数的定义:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 
struct sigaction { 
	void (*sa_handler)(int); 
	void (*sa_sigaction)(int, siginfo_t *, void *); 
	sigset_t sa_mask;
	int sa_flags; 
	void (*sa_restorer)(void); 
};

上面给出的sigaction结构体的定义并非严格意义上的定义,即结构体必须要有上述的成员变量,但成员变量的具体顺序取决于实现。

sa_mask

顾名思义,sa_mask就是信号处理函数执行期间的屏蔽信号集。

前文介绍bsd_signal的时候曾提到,为SIGINT安装处理函数时,内核会自动将SIGINT添加入屏蔽信号集,在SIGINT信号处理函数执行期间,SIGINT信号不会被递送给进程。但是,也仅仅是SIGINT,如果执行SIGINT信号处理函数期间,需要屏蔽SIGHUP、SIGUSR1等其他信号,那bsd_signal函数就爱莫能助了

这个屏蔽其他信号的需求对sigaction函数而言,根本就不是问题,只需如下代码即可做到:

struct sigaction sa; 
sa.sa_mask = SIGHUP|SIGUSR1|SIGINT;
  • 需要特别指出的是,并不是所有的信号都能被屏蔽

对于**SIGKILL和SIGSTOP,**不可以为它们安装信号处理函数,也不能屏蔽掉这些信号

原因是,系统总要控制某些进程,如果进程可以自行设计所有信号的处理函数,那么操作系统可能无法控制这些进程。换言之,操作系统是终极boss,需要杀死某些进程的时候,要能够做到,SIGKILL和SIGSTOP不能被屏蔽,就是为了防止出现进程无法无天而操作系统徒叹奈何的困境。

注意:

SIGKILL和SIGSTOP也不是万能的。如果进程处于TASK_UNINTERRUPTIBLE的状态,进程就不会处理信号。如果进程失控,长期处于该状态,SIGKILL也无法杀死该进程。详情可以回顾第5章。

sa_flag

若通过sigaction强行给SIGKILL或SIGSTOP注册信号处理函数,则会返回-1,并置errno为EINVAL。

  • 在sigaction函数接口中,比较有意思的是sa_flags。
  • sigaction函数之所以可以提供更精确的控制,大部分都是该参数的功劳。

下面简要介绍一下sa_flags的含义,其中很多标志位并不是新面孔,前面已经讨论过了。

(1)SA_NOCLDSTOP

这个标志位只用于SIGCHLD信号。4.7节“等待子进程”中曾经提到过,父进程可以监测子进程的三种事件:

  • 子进程终止(即子进程死亡)

  • 子进程停止(即子进程暂停)

  • 子进程恢复(即子进程从暂停中恢复执行)

其中SA_NOCLDSTOP标志位是用来控制第二种和第三种事件的

即一旦父进程为SIGCHLD信号设置了这个标志位,那么子进程停止和子进程恢复这两件事情,就无须向父进程发送SIGCHLD信号了。

(2)SA_NOCLDWAIT

这个标志只用于SIGCHLD信号,它可控制上面提到的子进程终止时的行为。

如果父进程为SIGCHLD设置了SA_NOCLDWAIT标志位,那么子进程退出时,就不会进入僵尸状态,而是直接自行了断。

但是子进程还会不会向父进程发送SIGCHLD信号呢?

这取决于具体的实现。对于Linux而言,仍然会发送SIGCHLD信号。这点和上面的SA_NOCLDSTOP略有不同。

(3)SA_ONESHOT和SA_RESETHAND

这两个标志位的本质是一样的,表示信号处理函数是一次性的.

信号递送出去之后,信号处理函数便恢复成默认值SIG_DFL。

(4)SA_NODEFER和SA_NOMASK

这两个标志位的作用是一样的,在信号处理函数执行期间,不阻塞当前信号。

(5)SA_RESTART

这个标志位表示,如果系统调用被信号中断,则不返回错误,而是自动重启系统调用。

(6)SA_SIGINFO

这个标志位表示信号发送者会提供额外的信息。

这种情况下,信号处理函数应该为三参数的函数,代码如下:

void handle(int, siginfo_t *, void *);

此处重点讲述一下带SA_SIGINFO标志位的信号安装方式

本章引言中提到过,signal的本质是一种进程间的通信。

一个进程向另外一个进程发送信号,能够传递的信息,不仅仅是signo,它还可以发送更多的信息,而接收进程也能获取到发送进程的PID、UID及发送的额外信息。

来看下面的例子:

 void  sig_handler(int signo, siginfo_t *info, void *context)
{
	printf("\nget signal : %d\n", signo);
	printf("signal number is %d\n", info->si_signo);
	printf("pid=%d\n", info->si_pid);
	printf("sigval = %d\n", info->si_value.sival_int);
}

int main(int argc, char *argv[])
{
	struct sigaction new_act;
	sigemptyset(&new_act.sa_mask);

	new_act.sa_sigaction = sig_handler;
	new_act.sa_flags    |= SA_SIGINFO | SA_RESTART;
        if (sigaction(36, &new_act, NULL) == -1)
	{
		perror("sigaction");
		exit(-1);
	}
	while(1)
		pause();
	printf("Done\n");
	exit(0);
}

这个例子比较简单,为36号信号注册了信号处理函数

因为sa_flags带上了SA_SIGINFO标志位,所以必须使用三参数的信号处理函数。

void sig_handler(int signo,siginfo_t *info,void *context)

本例中的信号处理函数中,

  1. info->si_pid记录着信号发送者的PID
  2. info->si_value.sival_int是信号发送进程时额外发送的int值
  3. 发送进程和接收进程约定好,发送者使用sigqueue发送信号,同时带上int型的额外信息,接收进程就能获得发送进程的PID及int型的额外信息。

如果调用sigaction函数时,sa_flags带了SIGINFO标志位,那么进程可以获得哪些信息?

6.8.3小节介绍sigqueue函数时,会展开讲述。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值