Skip to content

LV021-进程对信号的处理

本文主要是进程通信——信号的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

一、进程对信号的处理

1. 信号捕捉

如果信号的处理是自定义的,当信号递达时就调用某个用户自定义函数,这就是信号的捕捉。信号的捕捉流程大概如下图所示:

image-20220607161056542

对于我们编程来说,可以看做是有两步:

  • (1)定义新的信号的执行函数handle

  • (2)使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。

注意事项】一般而言,将信号处理函数设计越简单越好,这就好比中断处理函数,越快越好,不要在处理函数中做大量消耗CPU时间的事情,设计的越简单也将降低引发信号竞争条件的风险。

2. signal() 函数

2.1 函数说明

linux下可以使用man signal命令查看该函数的帮助手册,大概会有两种函数声明形式,但是其实是一样的,习惯上会使用形式一。

2.1.1 声明形式一

【说明】使用的命令为man signal或者man 2 signal查询到的系统调用的函数声明形式。

c
/* 需包含的头文件 */
#include <signal.h>
/* 函数声明 */
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
2.1.2 声明形式二

【说明】使用man 3 signal查询到的库函数中的函数声明形式。

c
/* 需包含的头文件 */
#include <signal.h>
/* 函数声明 */
void (*signal(int sig, void (*func)(int)))(int);
2.1.3 两种声明的关系

我们先来看声明形式二:

c
void (*signal(int sig, void (*func)(int)))(int);

我们先拆分一下:

c
/* 注意一下函数指针的形式:<数据类型> (*<函数指针名称>) (<参数说明列表>);  */
void (  ) (int)
      |
      *signal(int sig,   )
                       |
                       void (*func)(int)
  • 由于()的存在,*func是一个指针变量,后边的括号和int说明这个指针变量可以指向一个带有int形参的函数,前边的void说明这个函数指针指向的函数返回值为void类型,总的来说就是void (*func)(int)定义了一个函数指针变量func,它可以指向一个带有一个int参数的返回值为void类型的函数。可以指向的函数形式如下:
c
void functionName(int arg);
  • 再往上一层看,这就到了signal了,*的优先级是低于()的,所以signal先与后边的(int sig, void (*func)(int))相结合,说说明signal是一个函数,并且带有两个参数,一个是int类型的变量,一个是void (*func)(int)类型的函数指针变量。
  • 再看signal前边的*表示这是一个指针变量,也就是说这个signal函数的返回值是一个指针变量。
  • 接着就是最后的(int),这表示,signal函数返回的指针变量可以指向一个带有int类型参数的函数。
  • 最前边的void表示,signal函数返回的指针变量可以指向一个带有int类型参数且没有返回值的函数。

总的来说,定义了一个指针函数signal,指针函数的返回值是一个函数指针,可以指向一个带有int类型参数且无返回值的函数;而signal含有两个参数,一个是普通的整型变量,另一个是函数指针类型,可以指向带有一个int类型且无返回值的函数。

经过分析,会发现,signal的返回值和signal函数第二个参数的类型是一样的,他们都是函数指针,都可以指向一个带有一个int类型参数的没有返回值的函数。前边我们使用typedef简化过这样的定义的,所以这里,我们可以这样也做一个简化:

c
typedef void (*pfunc)(int)

这样,上边的函数就可以写为:

c
pfunc signal(int sig, pfunc);/* 需要注意的是,声明只需要类型即可,不一定需要写出形参*/

这样是不是就跟前边形式一很像了呢?我们把pfunc换成sighandler_t其实就得到了形式一的声明,这完全是可以的,毕竟指针变量名只要符合标识规则即可

2.1.4 参数和返回值

函数说明】该函数可以修改进程对信号的处理方式,可将信号的处理方式设置为捕获信号、忽略信号以及系统默认操作。

函数参数

  • signumint类型,此参数指定需要进行设置的信号,可使用信号名(宏)或信号的数字编号,不过一般建议使用信号名。
  • handlersighandler_t类型,是一个函数指针,指向信号对应的信号处理函数,当进程接收到信号后会自动执行该处理函数;也可以指向几个预定义函数。handler 可指向的预定义函数:
SIG_DFL表示设置为系统默认操作
SIG_IGN表示设置为忽视信号
【**返回值**】`sighandler_t`类型,是一个函数指针,成功情况下的返回值则是指向在此之前的信号处理函数;如果出错则返回`SIG_ERR`,并会设置`errno`。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <signal.h>

/* 至少应该有的语句 */
typedef void (*sighandler_t)(int);/* 不需要使用sighandler_t定义变量时,好像不写也可以 */
void sigintHandle(int sig);

/* 需要函数返回值 */
sighandler_t oldact;
oldact = signal(SIGINT, sigintHandle);
/* 或者不需要函数返回值 */
signal(SIGINT, sigintHandle);

注意事项】信号处理函数声明一般如下:

c
void handler(int sig);

2.2 使用实例1

【说明】此例子我们将会捕捉SIGINT信号,捕捉完成后,会执行我们自定的函数,而不会再使进程终止。这样的话我们想要结束进程,可以重开一个终端,然后输入以下命令:

shell
ps -ef|grep <filename>  # 查看PID
kill -9 <filename_PID>  # 直接杀死进程

下面是一个实例

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <unistd.h> /* sleep  */
#include <signal.h> /* signal */

typedef void (*sighandler_t)(int);

void sigintHandle(int sig);

/* 主函数 */
int main(int argc, char *argv[])
{

	signal(SIGINT, sigintHandle);

	while(1)
	{
		printf("Please Enter: Ctrl+c \n");
		sleep(1);
	}
	return 0;
}

void sigintHandle(int sig)
{
	static int count = 0;
	printf("I catch the SIGINT![%d times] \n", ++count);
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^CI catch the SIGINT![1 times] 
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^CI catch the SIGINT![2 times]
# 后边的省略 ... ...

2.3 使用实例2

上边的例子,我们要是想再恢复Ctrl+C的按键功能,让它按下后进程执行默认的操作,我们该怎么来写呢?还记得signal函数成功情况下返回什么吗?哈哈,返回值就是指向在此之前的信号处理函数,我们在自定义处理函数中将信号的处理方式还原不就好了吗。

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <unistd.h> /* sleep  */
#include <signal.h> /* signal */

typedef void (*sighandler_t)(int);
sighandler_t oldact;
void sigintHandle(int sig);

/* 主函数 */
int main(int argc, char *argv[])
{

	oldact = signal(SIGINT, sigintHandle);

	while(1)
	{
		printf("Please Enter: Ctrl+c \n");
		sleep(1);
	}
	return 0;
}

void sigintHandle(int sig)
{
	static int count = 0;
	printf("I catch the SIGINT![%d times] \n", ++count);
	signal(SIGINT, oldact);
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^CI catch the SIGINT![1 times] 
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^C

我们按下第一次的时候,成功捕捉到信号,当我们按下第二次的时候,进程退出了。

3. sigaction() 函数

3.1 函数说明

linux下可以使用man sigaction命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <signal.h>

/* 函数声明 */
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

函数说明】该函数用于检查或修改与指定信号相关联的处理动作(可同时进行两种操作)。系统建议使用sigaction函数,因为signal在不同类UNIX系统的行为不完全一样。sigaction()也更具灵活性以及移植性,并且它允许单独获取信号的处理函数而不是设置,并且还可以设置各种属性对调用信号处理函数时的行为施以更加精准的控制。

函数参数

  • signumint类型,表示需要设置的信号,可以是除了SIGKILL信号和SIGSTOP信号之外的任何信号。

  • actstruct sigaction类型的结构体指针变量,指向一个struct sigaction数据结构,该数据结构描述了信号的处理方式。如果参数act不为NULL,则表示需要为信号设置新的处理方式;如果参数actNULL,则表示无需改变信号当前的处理方式。

  • oldactstruct sigaction类型的结构体指针变量,指向一个struct sigaction数据结构。如果参数oldact不为NULL,则会将信号之前的处理方式等信息通过参数oldact返回出来;如果我们不需要获取此类信息,那么可将该参数设置为NULL

使用man sigaction的时候,显示的帮助手册中会有struct sigaction这个结构体成员详情。

c
struct sigaction
{
	void     (*sa_handler)(int);
	void     (*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t sa_mask;
	int      sa_flags;
	void     (*sa_restorer)(void);
};

(1)sa_handler:指定信号处理函数,与signal()函数的handler参数相同。

(2)sa_sigaction:也用于指定信号处理函数,这是一个替代的信号处理函数,这个函数指针提供了更多的参数,可以通过该函数获取到更多信息,这些信号通过siginfo_t参数获取;sa_handlersa_sigaction是互斥的,不能同时设置,对于标准信号来说,使用sa_handler就可以了,可通过SA_SIGINFO标志进行选择。

(3)sa_masksigset_t类型,该参数sa_mask定义了一组信号。

当进程在执行由sa_handler所定义的信号处理函数之前,会先将这组信号添加到进程的信号掩码字段中,当进程执行完处理函数之后再恢复信号掩码,将这组信号从信号掩码字段中删除。当进程在执行信号处理函数期间,可能又收到了同样的信号或其它信号,从而打断当前信号处理函数的执行,这就好点像中断嵌套。

通常我们在执行信号处理函数期间不希望被另一个信号所打断,那么怎么做呢?这时候就可以通过信号掩码来实现,如果进程接收到了信号掩码中的这些信号,那么这个信号将会被阻塞暂时不能得到处理,直到这些信号从进程的信号掩码中移除。在信号处理函数调用时,进程会自动将当前处理的信号添加到信号掩码字段中,这样保证了在处理一个给定的信号时,如果此信号再次发生,那么它将会被阻塞。如果用户还需要在阻塞其它的信号,则可以通过设置参数sa_mask来完成,信号掩码可以避免一些信号之间的竞争状态(也称为竞态)。

(4)sa_flags:该参数指定了一组标志,这些标志用于控制信号的处理过程,可设置为如下这些标志(多个标志使用位或|组合)。sa_flags 常用可取的值如下:

SA_NOCLDSTOP如果signum为SIGCHLD,则子进程停止时(即当它们接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU中的一种时)或恢复(即它们接收到SIGCONT)时不会收到SIGCHLD信号。
SA_NOCLDWAIT如果signum是SIGCHLD,则在子进程终止时也不会转变为僵尸进程。
SA_NODEFER 不要阻塞从某个信号自身的信号处理函数中接收此信号。也就是说当进程此时正在执行某个信号的处理函数,默认情况下,进程会自动将该信号添加到进程的信号掩码字段中,从而在执行信号处理函数期间阻塞该信号,默认情况下,我们期望进程在处理一个信号时阻塞同种信号,否则引起一些竞态条件;如果设置了SA_NODEFER标志,则表示不对它进行阻塞。
SA_RESETHAND执行完信号处理函数之后,将信号的处理方式设置为系统默认操作。
SA_RESTART 被信号中断的系统调用,在信号处理完成之后将自动重新发起。
SA_SIGINFO 如果设置了该标志,则表示使用sa_sigaction作为信号处理函数、而不是sa_handler。
(5)`sa_restorer`:该成员已过时,一般是已经不再使用了。

返回值int类型,成功返回0,失败将返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <signal.h>

/* 至少应该有的语句 */
void sigactionHandle(int sig);/* 信号处理函数声明 */

struct sigaction act;/* 局部变量 */
/* 结构体变量初始化 */
act.sa_handler = sigactionHandle;  /* 指定信号处理函数 */
act.sa_flags = 0;                  /* a_flags指定了一组标志,这些标志用于控制信号的处理过程 */
sigaction(SIGINT, &act, NULL);

注意事项】关于信号处理函数的两种形式:

c
/* 使用 sig.sa_sigaction 时*/
void handler(int sig, siginfo_t *info, void *ucontext);
/* 使用 act.sa_handler 时*/
void handler(int sig);

3.2 获取信号携带的信息

sigaction()函数在act参数也就是第二个参数的成员sa_flagsSA_SIGINFO的时候,可以获取信号所携带的数据(如何携带数据?后边的使用实时信号一节会有介绍),此时的自定义信号处理函数形式可以如下所示:

c
/* act.sa_flags = SA_SIGINFO */
void handler(int sig, siginfo_t *info, void *ucontext)
{
	...
}
  • sigint类型,表示接收到的信号编号。
  • infosiginfo_t类型的指针变量,siginfo_t是一个包含信号进一步信息的结构,该信息的结构体成员中有一个成员是si_value,该成员类型为union sigval,我们获取信号携带的数据时使用的就是这个成员。

使用man sigaction的时候,显示的帮助手册中会有siginfo_t这个结构体成员详情。

c
siginfo_t
{
	int      si_signo;     /* Signal number */
	int      si_errno;     /* An errno value */
	int      si_code;      /* Signal code */
	int      si_trapno;    /* Trap number that caused hardware-generated signal (unused on most architectures) */
	pid_t    si_pid;       /* Sending process ID */
	uid_t    si_uid;       /* Real user ID of sending process */
	int      si_status;    /* Exit value or signal */
	clock_t  si_utime;     /* User time consumed */
	clock_t  si_stime;     /* System time consumed */
	union sigval si_value; /* Signal value */
	int      si_int;       /* POSIX.1b signal */
	void    *si_ptr;       /* POSIX.1b signal */
	int      si_overrun;   /* Timer overrun count; POSIX.1b timers */
	int      si_timerid;   /* Timer ID; POSIX.1b timers */
	void    *si_addr;      /* Memory location which caused fault */
	long     si_band;      /* Band event (was int in glibc 2.3.2 and earlier) */
	int      si_fd;        /* File descriptor */
	short    si_addr_lsb;  /* Least significant bit of address (since Linux 2.6.32) */
	void    *si_lower;     /* Lower bound when address violation occurred (since Linux 3.19) */
	void    *si_upper;     /* Upper bound when address violation occurred (since Linux 3.19) */
	int      si_pkey;      /* Protection key on PTE that caused fault (since Linux 4.6) */
	void    *si_call_addr; /* Address of system call instruction (since Linux 3.5) */
	int      si_syscall;   /* Number of attempted system call (since Linux 3.5) */
	unsigned int si_arch;  /* Architecture of attempted system call (since Linux 3.5) */
}
  • ucontextvoid *类型,该参数指向的结构包含内核保存在用户空间堆栈上的信号上下文信息, 通常,处理程序函数不使用第三个参数。 有关详细信息,请参阅 sigreturn(2)。 有关 ucontext_t 结构的更多信息可以在 getcontext(3)signal(7) 中找到。

注意事项】有关实例可以参考第七节的使用实时信号例子。

3.3 使用实例1

【说明】此例子我们将会捕捉SIGINT信号,捕捉完成后,会执行我们自定的函数,而不会再使进程终止。这样的话我们想要结束进程,可以重开一个终端,然后输入以下命令:

shell
ps -ef|grep <filename>  # 查看PID
kill -9 <filename_PID>  # 直接杀死进程

下面是一个实例:

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <unistd.h> /* sleep  */
#include <signal.h> /* sigaction sigemptyset*/

void sigactionHandle(int sig);

/* 主函数 */
int main(int argc, char *argv[])
{
	/* 定义一个处理信号行为的结构体变量 */
	struct sigaction act;
	/* 结构体变量初始化 */
	act.sa_handler = sigactionHandle;  /* 指定信号处理函数 */
	act.sa_flags = 0;                  /* a_flags指定了一组标志,这些标志用于控制信号的处理过程 */
	sigaction(SIGINT, &act, NULL);     /* 捕捉信号 */

	while(1)
	{
		printf("Please Enter: Ctrl+c \n");
		sleep(1);
	}
	return 0;
}

void sigactionHandle(int sig)
{
	static int count = 0;
	printf("I cath the SIGINT![%d times] \n", ++count);
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^CI cath the SIGINT![1 times] 
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^CI cath the SIGINT![2 times]
# 后边的省略 ... ...

3.4 使用实例2

上边的例子,我们要是想再恢复Ctrl+C的按键功能,让它按下后进程执行默认的操作,我们该怎么来写呢?这个函数与signal不同,我们可以直接通过参数设定执行完一次信号处理函数后恢复原来的默认操作,这个参数就是act参数的sa_flags成员。

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <unistd.h> /* sleep  */
#include <signal.h> /* sigaction sigemptyset*/

void sigactionHandle(int sig);

/* 主函数 */
int main(int argc, char *argv[])
{
	/* 定义一个处理信号行为的结构体变量 */
	struct sigaction act;
	/* 结构体变量初始化 */
	act.sa_handler = sigactionHandle;  /* 指定信号处理函数 */
	act.sa_flags = SA_RESETHAND;       /* a_flags指定了一组标志,这些标志用于控制信号的处理过程 */
	sigaction(SIGINT, &act, NULL);     /* 捕捉信号 */

	while(1)
	{
		printf("Please Enter: Ctrl+c \n");
		sleep(1);
	}
	return 0;
}

void sigactionHandle(int sig)
{
	static int count = 0;
	printf("I cath the SIGINT![%d times] \n", ++count);
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^CI cath the SIGINT![1 times] 
Please Enter: Ctrl+c 
Please Enter: Ctrl+c 
^C

我们按下第一次的时候,成功捕捉到信号,当我们按下第二次的时候,进程退出了。说明信号恢复了系统的默认操作。

4. 子进程回收

前边我们知道,子进程在结束的时候会向父进程发送一个SIGCHLD信号,并且父进程若是未对已结束的子进程进行回收的话,子进程就会变成僵尸进程,上边我们学习了两个信号函数,那是不是可以通过信号来实现进程的回收呢?当然也是可以的啦。SIGCHLD的产生会在下边三种情况下产生:

(1)子进程终止时。

(2)子进程接收到SIGSTOP信号停止时。

(3)子进程处在停止态,接受到SIGCONT后唤醒时。

4.1 signal() 实现进程回收

我们可以通过signal函数来捕获SIGCHLD信号,在信号处理函数中进行进程的回收。

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <stdlib.h> /* exit */
#include <unistd.h> /* sleep fork */
#include <signal.h> /* sigaction sigemptyset*/
#include <sys/wait.h> /* waitpid */

void sigChildHandle(int signo);

/* 主函数 */
int main(int argc, char *argv[])
{
	int i = 0;
	pid_t pid = -1;
	signal(SIGCHLD, sigChildHandle);
	pid = fork();/* 创建子进程 */
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid > 0)/* 父进程 */
	{
		while(1)
		{
			printf("The father process is runing!\n");
			sleep(1);
		}
	}
	else /* 子进程 */
	{
		for(i = 0; i < 5; i++)
		{
			printf("This is child process![i = %d]\n", i);
			sleep(1);
		}
		printf("The child process is ready to exit!\n");
		exit(0);
	}

	return 0;
}

void sigChildHandle(int signo)
{
	if(signo == SIGCHLD)
	{
		waitpid(-1, NULL,  WNOHANG);
	}
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
The father process is runing!
This is child process![i = 0]
The father process is runing!
This is child process![i = 1]
The father process is runing!
This is child process![i = 2]
The father process is runing!
This is child process![i = 3]
The father process is runing!
This is child process![i = 4]
The father process is runing!
The child process is ready to exit!
The father process is runing!
The father process is runing!

运行后,我们可以使用下边的命令查看进程以及子进程的情况:

shell
ps -ef | grep a.out

会发现,使用信号捕获子进程发出的信号后,完成了对子进程的回收,回收成功是这样的:

shell
0 S hk         24361   19822  0  80   0 -   628 hrtime 05:48 pts/0    00:00:00 ./a.out
0 S hk         24364   22941  0  80   0 -  4446 pipe_r 05:49 pts/2    00:00:00 grep --color=auto a.out

若未回收,则子进程结束后成为僵尸进程,将会是如下状态:

shell
0 S hk         24350   19822  0  80   0 -   628 hrtime 05:48 pts/0    00:00:00 ./a.out
1 Z hk         24351   24350  0  80   0 -     0 -      05:48 pts/0    00:00:00 [a.out] <defunct>
0 S hk         24355   22941  0  80   0 -  4446 pipe_r 05:48 pts/2    00:00:00 grep --color=auto a.out

4.2 sigaction() 实现进程回收

我们还可以通过sigaction函数来捕获SIGCHLD信号,但是sigaction功能很强大,我们就可以直接在参数中设置子进程结束后不成为僵尸进程。

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <stdlib.h> /* exit */
#include <unistd.h> /* sleep fork */
#include <signal.h> /* sigaction sigemptyset*/
#include <sys/wait.h> /* waitpid */

void sigChildHandle(int signo);

/* 主函数 */
int main(int argc, char *argv[])
{
	int i = 0;
	pid_t pid = -1;
	signal(SIGCHLD, sigChildHandle);
	pid = fork();/* 创建子进程 */
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid > 0)/* 父进程 */
	{
		while(1)
		{
			printf("The father process is runing!\n");
			sleep(1);
		}
	}
	else /* 子进程 */
	{
		for(i = 0; i < 5; i++)
		{
			printf("This is child process![i = %d]\n", i);
			sleep(1);
		}
		printf("The child process is ready to exit!\n");
		exit(0);
	}

	return 0;
}

void sigChildHandle(int signo)
{
	if(signo == SIGCHLD)
	{
		waitpid(-1, NULL,  WNOHANG);
	}
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
The father process is runing!
This is child process![i = 0]
The father process is runing!
This is child process![i = 1]
The father process is runing!
This is child process![i = 2]
The father process is runing!
This is child process![i = 3]
The father process is runing!
This is child process![i = 4]
The father process is runing!
The child process is ready to exit!
I catch the signal!
The father process is runing!

运行后,我们可以使用下边的命令查看进程以及子进程的情况:

shell
ps -ef|grep a.out

会发现,使用信号捕获子进程发出的信号后,完成了对子进程的回收,回收成功是这样的:

shell
0 S hk         24491   19822  0  80   0 -   628 hrtime 05:59 pts/0    00:00:00 ./a.out
0 S hk         24496   22941  0  80   0 -  4446 pipe_r 05:59 pts/2    00:00:00 grep --color=auto a.out

若未回收,则子进程结束后成为僵尸进程,将会是如下状态:

shell
0 S hk         24507   19822  0  80   0 -   628 hrtime 05:59 pts/0    00:00:00 ./a.out
1 Z hk         24508   24507  0  80   0 -     0 -      05:59 pts/0    00:00:00 [a.out] <defunct>
0 S hk         24514   22941  0  80   0 -  4446 pipe_r 06:00 pts/2    00:00:00 grep --color=auto a.out

5. SIGABRT 信号

SIGABRT信号通常是由abort()函数产生的,该信号来终止调用该函数的进程,SIGABRT信号的系统默认操作是终止进程运行、并生成核心转储文件;当调用abort()函数之后,内核会向进程发送SIGABRT信号。那这个信号能被捕获嘛?我们来试一下。

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <unistd.h> /* sleep getpid */
#include <signal.h> /* sigaction */
#include <stdlib.h> /* abort */
void sigactionHandle(int sig);

/* 主函数 */
int main(int argc, char *argv[])
{
	int i = 0;
	struct sigaction act;   /* 定义一个处理信号行为的结构体变量 */

	/* 1.捕获信号 */
	/* 1.1初始化处理信号行为的结构体变量 */
	act.sa_handler = sigactionHandle;  /* 指定信号处理函数 */
	act.sa_flags = 0         ;           /* sa_flags指定了一组标志,这些标志用于控制信号的处理过程 */
	sigemptyset(&act.sa_mask);           /* 将信号集初始化为空 */
	/* 1.2设置捕捉信号 */
	sigaction(SIGABRT, &act, NULL);      /* abort() 发送该信号 */
	while(1)
	{
		printf("Please send signal.[i=%d]\n", ++i);
		if( i == 3)
			abort();
		sleep(1);
	}
	return 0;
}

void sigactionHandle(int sig)
{
	static int count = 0;
	printf("I catch the signal [%d] %d times\n", sig, ++count);
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
Please send signal.[i=1]
Please send signal.[i=2]
Please send signal.[i=3]
I catch the signal [6] 1 times
已放弃 (核心已转储)

事实证明,即便SIGABRT信号可以被捕获,但是,它依然会使程序结束。