进程信号之sigaction系统调用
目录
信号捕捉的大致逻辑
函数原型
函数作用
参数说明
返回值
struct sigaction 结构体
信号集函数
示例代码
被挂起的信号
信号捕捉的大致逻辑
建议大家先看一下这篇文章进程信号之signal系统调用-CSDN博客,通过简单学习一下signal来了解信号捕捉,然后自定义处理函数的大致逻辑。
函数原型
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
函数作用
sigaction
是一个设置信号处理方式的系统调用,它比传统的 signal
函数提供了更强大和灵活的信号处理机制。
参数说明
signum
:指定要设置处理方式的信号编号。常见的信号如SIGINT
(中断信号,通常由Ctrl + C
产生)、SIGTERM
(终止信号)、SIGKILL
(杀死信号,不可被捕获或忽略)等。act
:一个指向struct sigaction
结构体的指针,用于指定新的信号处理方式。如果设置为NULL
,则恢复该信号的默认处理方式。oldact
:也是一个指向struct sigaction
结构体的指针,用于保存旧的信号处理方式。如果不需要保存旧的处理方式,可以将其设置为NULL
。
返回值
sigaction
函数成功时返回 0
,失败时返回 -1
,并设置 errno
以指示错误原因。常见的错误包括无效的信号编号、权限不足等。
struct sigaction
结构体
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};
sa_handler
:这是一个函数指针,指向信号处理函数。该函数只有一个整数参数,即接收到的信号编号。sa_sigaction
:另一个函数指针,指向更复杂的信号处理函数。它可以获取关于信号的更多信息,比如信号发送者的进程 ID 等。该函数有三个参数:接收到的信号编号、指向siginfo_t
结构体的指针(包含信号的详细信息)和一个指向void
的指针(通常用于传递上下文信息)。当sa_flags
中设置了SA_SIGINFO
标志时,会使用这个函数来处理信号。sa_mask
:一个信号集,用于指定在执行信号处理函数期间要阻塞的信号。例如,如果在处理SIGINT
信号时,希望阻塞SIGTERM
信号,可以将SIGTERM
添加到sa_mask
中。在信号处理函数开始执行前,sa_mask
中的信号会被自动阻塞,处理函数执行完毕后,这些信号的阻塞状态会被恢复到之前的状态。sa_flags
:一些标志位,用于指定信号处理的行为。常见的标志有:SA_RESTART
:如果信号中断了系统调用(如read
、write
、wait
等),设置此标志后,系统调用会自动重启,而不是返回-1
并将errno
设置为EINTR
。例如,一个正在读取文件的read
系统调用被信号中断,如果设置了SA_RESTART
,read
会自动重新开始读取,而不会让应用程序认为读取操作失败。SA_SIGINFO
:表示使用sa_sigaction
函数而不是sa_handler
函数来处理信号,以便获取更详细的信号信息。SA_NODEFER
:默认情况下,在执行信号处理函数期间,该信号会被自动阻塞,防止递归调用信号处理函数。设置SA_NODEFER
标志后,在处理该信号时,不会阻塞该信号本身,这可能会导致信号处理函数被递归调用,需谨慎使用。
sa_restorer
:此成员已过时,在现代系统中不再使用,通常设为NULL
。
信号集函数
前面提到Linux用数据结构sigset_t表示一组信号集,处在信号集中的信号,在执行sigaction捕捉的信号的对应自定义捕捉函数时,会阻塞。下列是对信号集的各种操作方法:
- int sigemptyset(sigset_t *set);清空一个信号集,即把信号集中所有信号都设置为未包含状态。成功时返回
0
,失败时返回-1
。 - int sigfillset(sigset_t *set);将信号集中所有信号都设置为包含状态,即填充信号集。成功时返回
0
,失败时返回-1
。 - int sigaddset(sigset_t *set, int signum);将指定的信号
signum
添加到信号集set
中。成功时返回0
,失败时返回-1
。 - int sigdelset(sigset_t *set, int signum);从信号集
set
中移除指定的信号signum
。成功时返回0
,失败时返回-1
。 - int sigismember(const sigset_t *set, int signum);检查指定的信号
signum
是否在信号集set
中。如果信号signum
在信号集set
中,返回1
;如果不在,返回0
;如果出错(如无效的信号集指针),返回-1
。
示例代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void my_handler(int signum) {printf("Received signal %d\n", signum);
}int main() {struct sigaction act;act.sa_handler = my_handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;if (sigaction(SIGINT, &act, NULL) == -1) {perror("sigaction");return 1;}printf("Waiting for signal...\n");while (1) {sleep(1);}return 0;
}
被挂起的信号
在执行sigaction设置的特定信号的处理函数期间,如果sigaction在为这个特定信号设置处理函数时设置阻塞了一些信号,那么这个被阻塞的信号如果在自定义函数执行期间产生了,则被挂起,当这个自定义函数执行完毕,这个被挂起的信号会被重新发给进程。
举个例子
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void my_handler(int signum) {printf("Received signal %d\n", signum);
}int main() {struct sigaction act;act.sa_handler = my_handler;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGTERM);//阻塞SIGTERM信号act.sa_flags = 0;if (sigaction(SIGINT, &act, NULL) == -1) {perror("sigaction");return 1;}printf("Waiting for signal...\n");while (1) {sleep(1);}return 0;
}
- 当用户按下
Ctrl + C
触发SIGINT
信号,进入sigint_handler
处理函数,此时如果尝试发送SIGTERM
信号(例如通过另一个终端执行kill -SIGTERM <pid>
,<pid>
为该进程的 ID),SIGTERM
信号会被挂起。 - 当
sigint_handler
处理函数执行完毕,由于SIGTERM
信号仍然处于挂起状态且系统默认SIGTERM
的处理方式是终止进程,所以进程会终止,即SIGTERM
信号在sigaction
处理函数结束后被执行。
在本文所讲解的知识大家都已经理解的基础上,请大家务必阅读进程的信号掩码,信号集,sigprocmask函数-CSDN博客,从而更加立体的理解信号捕捉。