【APUE笔记】第十章 信号

1.信号与signal函数

  信号是软件中断。每个信号都有一个名字。这些名字都以三个字符SIG开头。在头文件<signal.h>中,这些信号都被定义为正整数(信号编号)。没有一个信号其编号为0。

很多条件可以产生一个信号:
(1)当用户按某些终端键时,产生信号。在终端上按DELETE键通常产生中断信号(SIGINT)。
这是停止一个已失去控制程序的方法。(第11章将说明此信号可被映射为终端上的任一字符。)
(2)硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效存储访问的进程产生一个SIGSEGV。
(3)进程用kill函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
(4)用户可用kill命令将信号发送给其他进程。此程序是kill函数的界面。常用此命令终止一个失控的后台进程。
(5)当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并不是指硬件产生条件(如被0除),而是软件条件。例如SIGURG(在网络连接上传来非规定波特率的数据)、SIGPIPE(在管道的读进程已终止后一个进程写此管道),以及SIGALRM(进程所设置的闹钟时间已经超时)。

  信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能只是测试一个变量(例如errno)来判别是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行下列操作”。

  可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作:
(1)忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。
它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是未定义的。
(2)捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。例如,若编写一个命令解释器,当用户用键盘产生中断信号时,很可能希望返回到程序的主循环,终止系统正在为该用户执行的命令。
如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。又例如,如果进程创建了临时文件,那么可能要为SIGTERM信号编写一个信号捕捉函数以清除临时文件(kill命令传送的系统默认信号是终止信号)。
(3)执行系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。

  Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是“不可靠信号”的来源,它的主要问题是信号可能丢失。随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。我们可以使用kill-1命令查看当前系统支持的信号,需要注意的是不同的系统支持的信号是不一样的:

  信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

/*
 *Copyright:  (C) 2018 LingYun IoT System Studio
 *All rights reserved.
 *
 *File name:  signal.c
 *Description:  Use signal() and sigaction() to install signal.
 *
 *Version:  1.1
 *Author:  Guo Wenxue <guowenxue@gmail.com>
 *		   Copied by Zhu Tianyin <zhuty_cn@163.com>
 *ChangeLog:  1.0  Release initial version in 2018.
 *            1.1  Copy and change some details on "01/28/2021".
 */

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

int g_sigstop = 0;

void signal_stop(int signum)
{
	if( SIGTERM == signum )
	{
		printf("SIGTERM signal detected\n");
	}
	else if( SIGTERM == signum )
	{
		printf("SIGALRM signal detected\n");
		g_sigstop = 1;
	}
}

void signal_user(int signum)
{
	if( SIGUSR1 == signum )
	{
		printf("SIGUSR1 signal detected\n");
	}
	else if( SIGUSR2 == signum )
	{
		printf("SIGUSR2 signal detected\n");
	}
	g_sigstop = 1;
}

void signal_code(int signum)
{
	if( SIGBUS == signum )
	{
		printf("SIGBUS signal detected\n");
	}
	else if( SIGILL == signum )
	{
		printf("SIGILL signal detected\n");
	}
	else if( SIGSEGV == signum )
	{
		printf("SIGSEGV signal detected\n");
	}
	exit(-1);
}

int main(int argc, char **argv)
{
	struct sigaction sigact, sigign;

	/*Method1: Use signal() to install signal*/
	signal(SIGTERM, signal_stop);
	signal(SIGALRM, signal_stop);

	signal(SIGBUS, signal_code);
	signal(SIGILL, signal_code);
	signal(SIGSEGV, signal_code);

	/*Method2: Use sigaction() to install signal*/
	/*Initialize the catch signal structure*/
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = 0;
	sigact.sa_handler = signal_user;

	/*Set up the ignore signal*/
	sigemptyset(&sigign.sa_mask);
	sigign.sa_flags = 0;
	sigign.sa_handler = SIG_IGN;

	sigaction(SIGINT, &sigign, 0);    //Ignore SIGINT signal by CTRL+C

	sigaction(SIGUSR1, &sigact, 0);   //Catch SIGUSR1
	sigaction(SIGUSR2, &sigact, 0);   //Catch SIGUSR2

	printf("Program start running for 20 seconds...\n");
	alarm(20);

	while( !g_sigstop )
	{
		;
	}

	printf("Program start to stop running...\n");
	printf("Invalid pointer operator will raise SIGSEGV signal\n");
	return 0;
}

运行结果:

2.kill和raise函数

  功能:kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

3.alarm和pause函数

  使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。
  pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理程序并从其返回时, pause才返回。在这种情况下, pause返回-1,errno设置为EINTR。

4.信号集

  能表示多个信号的数据类型叫信号集。
  信号集处理函数:
sigemptyset() //初始化由set指向的信号集,使排除其中所有信号
sigfillset() //初始化由set指向的信号集,使其包括所有信号
sigaddset() //将一个信号添加到现存集中
sigdelset() //从信号集中删除一个信号
sigismember() //测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1, 否则返回0。

5.sigprocmask函数

  功能:检测或更改(或两者)进程的信号屏蔽字。

6.sigpending函数

  功能:返回对于调用进程被阻塞不能递送和当前未决的信号集。

7.sigaction函数

  功能:检查或修改(或两者)与指定信号相关联的处理动作。

8.sigsetjmp和siglongjmp函数

  功能:在信号处理程序中作非局部转移时应当使用这两个函数。

9.sigsuspend函数

  功能:在一个原子操作中实现恢复信号屏蔽字,然后使进程睡眠,这种功
能是由sigsuspend函数所提供的。

10.abort函数

  功能:使程序异常终止。

11.作业控制信号

• SIGCHLD 子进程已停止或终止。
• SIGCONT 如果进程已停止,则使其继续运行。
• SIGSTOP 停止信号(不能被捕捉或忽略)。
• SIGTSTP 交互停止信号。
• SIGTTIN 后台进程组的成员读控制终端。
• SIGTTOU 后台进程组的成员写控制终端。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值