信号量
- 目录
- 操作系统信号
- 信号的默认处理动作
- 示例
- 解释
- 信号的捕获与处理
- 使用 `signal` 函数
- 使用 `sigaction` 函数
- 信号的阻塞
- 信号的生命周期
- 1. 信号产生
- 2. 信号在进程中注册
- 3. 信号在进程中注销
- 4. 信号处理
- main 7 signal
- 命令含义
- 使用场景
- 手册页包含的关键信息
- 1. 信号概述
- 2. 信号列表
- 3. 信号处理
- 4. 信号的产生和发送
- 5. 信号的阻塞和未决信号
- 6. 信号的可靠性和排队
- 示例
- 信号的产生
- 信号的响应方式
- kill命令
- `kill` 命令基本使用
- 常用信号及对应 `kill` 命令示例
- `kill` 命令的使用场景
- 发送信号给多个进程或进程组
- 信号的捕捉
- 信号的默认处理
- term 和croe
- 含义说明
- 二者区别
- 忽略信号
- 标准信号处理与扩展信号处理概述
- 标准信号处理
- 扩展信号处理(实时信号处理)
- 函数接口
- `sigqueue` 函数接口
- `sigaction` 函数接口
目录
操作系统信号
- 定义:在操作系统中,信号是一种软件中断机制,用于通知进程发生了某种特定事件。进程可以对信号进行捕获、处理或忽略,从而实现进程间的通信和异步事件处理。
- 举例:在 Unix/Linux 系统中,常见的信号有 SIGINT(中断信号,通常由用户按下 Ctrl+C 产生)、SIGTERM(终止信号,用于正常终止进程)、SIGKILL(强制终止信号,不能被进程捕获或忽略)等。当用户在终端中按下
Ctrl+C 时,操作系统会向当前正在运行的进程发送 SIGINT 信号,进程可以选择捕获该信号并执行相应的清理操作后退出。
信号的默认处理动作
每个标准信号都有其默认的处理动作,常见的默认处理动作包括:
- 终止进程:如
SIGTERM
(终止信号,可被捕获和忽略)、SIGKILL
(强制终止信号,不可被捕获和忽略)。当进程接收到这些信号时,会直接终止运行。- 终止进程并生成核心转储文件:例如
SIGSEGV
(段错误信号),当进程发生段错误时,会收到该信号,进程终止并生成核心转储文件,方便后续调试。- 暂停进程:
SIGSTOP
(暂停信号,不可被捕获和忽略)、SIGTSTP
(交互式暂停信号,可被捕获和忽略),进程收到这些信号后会暂停执行。- 继续执行暂停的进程:
SIGCONT
信号用于让暂停的进程继续执行。
示例
# 查看当前系统所支持的所有信号
sgaseng@ubantu:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
sgaseng@ubantu:~$
解释
- 用户信息:在 Linux/Unix 系统的命令行中,
sgaseng@ubantu
代表当前登录的用户是sgaseng
,所在的主机名为ubantu
。~
表示用户的主目录,$
是普通用户的命令提示符。 kill -l
命令:该命令用于列出当前系统支持的所有信号。输出中左边的数字是信号编号,右边是信号名称。前 31 个信号是标准信号,后面从SIGRTMIN
开始的是实时信号。
信号的捕获与处理
在 C 语言中,可以使用 signal
或 sigaction
函数来捕获和处理信号。
使用 signal
函数
signal
函数是一个较简单的信号处理函数,其原型如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum
:要捕获的信号编号。handler
:信号处理函数的指针,也可以是SIG_IGN
(忽略该信号)或SIG_DFL
(使用默认处理动作)。
以下是一个简单的示例代码,用于捕获 SIGINT
信号:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void sigint_handler(int signum) {
printf("Received SIGINT signal. Exiting...\n");
// 终止进程
_exit(0);
}
int main() {
// 注册信号处理函数
signal(SIGINT, sigint_handler);
printf("Waiting for SIGINT signal...\n");
while (1) {
sleep(1);
}
return 0;
}
在上述代码中,当用户按下 Ctrl+C
时,会发送 SIGINT
信号,进程会调用 sigint_handler
函数进行处理。
使用 sigaction
函数
sigaction
函数比 signal
函数更强大和灵活,其原型如下:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum
:要捕获的信号编号。act
:指向struct sigaction
结构体的指针,用于指定新的信号处理动作。oldact
:指向struct sigaction
结构体的指针,用于保存旧的信号处理动作。
以下是使用 sigaction
函数处理 SIGINT
信号的示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void sigint_handler(int signum) {
printf("Received SIGINT signal. Exiting...\n");
// 终止进程
_exit(0);
}
int main() {
struct sigaction sa;
sa.sa_handler = sigint_handler;
// 清空信号集
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// 注册信号处理函数
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Waiting for SIGINT signal...\n");
while (1) {
sleep(1);
}
return 0;
}
信号的阻塞
有时候,我们希望在某些代码段中暂时阻塞某些信号,避免信号的干扰。或者可以说 阻塞是暂时性的不处理或者较后的处理信号,可以使用 sigprocmask
函数来阻塞和解除阻塞信号。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
:指定如何修改信号掩码,可选值有SIG_BLOCK
(将set
中的信号添加到信号掩码中)、SIG_UNBLOCK
(从信号掩码中移除set
中的信号)、SIG_SETMASK
(将信号掩码设置为set
)。set
:指向信号集的指针,用于指定要操作的信号。oldset
:指向信号集的指针,用于保存旧的信号掩码。
以下是一个简单的示例代码,用于阻塞 SIGINT
信号:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
sigset_t set;
// 清空信号集
sigemptyset(&set);
// 将 SIGINT 信号添加到信号集中
sigaddset(&set, SIGINT);
// 阻塞 SIGINT 信号
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
perror("sigprocmask");
return 1;
}
printf("SIGINT signal is blocked. Press any key to continue...\n");
getchar();
// 解除阻塞 SIGINT 信号
if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1) {
perror("sigprocmask");
return 1;
}
printf("SIGINT signal is unblocked.\n");
return 0;
}
在上述代码中,首先使用
sigprocmask
函数阻塞SIGINT
信号,然后等待用户输入,最后解除阻塞。通过以上方式,可以对标准信号进行有效的处理,实现进程间的通信和异步事件处理。
信号的生命周期
1. 信号产生
信号的产生是信号生命周期的起始点,它可以由多种事件触发,以下是一些常见的信号产生场景:
- 用户操作:用户在终端通过特定的按键组合触发信号。例如按下
Ctrl + C
会产生SIGINT
信号,用于请求中断当前正在运行的进程;按下Ctrl + \
会产生SIGQUIT
信号,不仅会终止进程,还可能生成核心转储文件。- 硬件异常:当系统硬件出现错误时,会产生相应的信号。比如,进程访问了非法内存地址,会触发
SIGSEGV
(段错误)信号;进行除零操作时,会产生SIGFPE
(浮点异常)信号。- 软件条件:某些软件事件也会引发信号。例如,当一个进程尝试向一个没有读端的管道写入数据时,会收到
SIGPIPE
信号;使用alarm()
函数设置的定时器到期时,会产生SIGALRM
信号。- 系统调用:通过系统调用可以显式地向进程发送信号。例如,
kill()
系统调用可以向指定进程或进程组发送任意信号,raise()
系统调用可以向当前进程发送信号。
2. 信号在进程中注册
信号产生后,内核会将信号添加到目标进程的信号队列中,这个过程就是信号的注册不过,标准信号和实时信号的注册机制有所不同:
- 标准信号:标准信号(编号 1 - 31)不支持排队。如果一个标准信号已经在进程的信号队列中,后续相同的信号会被丢弃。也就是说,无论有多少个相同的标准信号发送给进程,进程最多只会处理一次该信号。
- 实时信号:实时信号(编号 34 - 64)支持排队。多个相同的实时信号可以依次添加到进程的信号队列中,进程会按照信号接收的顺序依次处理这些信号。
3. 信号在进程中注销
当进程准备处理信号时,会将信号从信号队列中移除,这个过程称为信号的注销。同样,标准信号和实时信号的注销方式也存在差异:
- 标准信号:由于 标准信号不排队,当进程开始处理某个标准信号时,会直接将该信号从信号队列中移除,后续相同的信号若之前已被丢弃则不会再处理。
- 实时信号:对于实时信号,每次处理一个信号后,会将该信号从信号队列中移除,直到信号队列中该信号的所有实例都被处理完毕。
4. 信号处理
进程在合适的时机(通常是从内核态返回到用户态时)会检查自己的信号队列,并处理其中的信号。信号的处理方式主要有以下三种:
- 默认处理动作:每个信号都有其默认的处理动作,这些动作由内核定义。常见的默认处理动作包括终止进程、暂停进程、继续执行暂停的进程、终止进程并生成核心转储文件等。例如,
SIGKILL
信号的默认处理动作是强制终止进程,且该信号不能被捕获或忽略。 - 忽略信号:进程可以选择忽略某些信号,即不做任何处理。使用
signal()
或sigaction()
系统调用将信号处理函数设置为SIG_IGN
即可忽略该信号。不过,SIGKILL
和SIGSTOP
信号不能被忽略,因为它们是系统为了确保能够控制进程而保留的。 - 自定义处理函数:进程可以为信号设置自定义的处理函数,当接收到该信号时,会执行自定义的处理逻辑。通过
signal()
或sigaction()
系统调用可以为信号注册自定义的处理函数。例如:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义信号处理函数
void sigint_handler(int signum) {
printf("Received SIGINT signal. Continuing...\n");
}
int main() {
// 注册信号处理函数
signal(SIGINT, sigint_handler);
printf("Waiting for SIGINT signal...\n");
while (1) {
sleep(1);
}
return 0;
}
在上述代码中,为 SIGINT
信号注册了自定义的处理函数 sigint_handler
,当接收到 SIGINT
信号时,会执行该处理函数。
在 Linux/Unix 系统中,信号是进程间通信的一种机制,下面为你详细介绍信号的产生、响应方式、捕捉、默认处理以及忽略信号等方面的内容。
main 7 signal
在 Linux 或类 Unix 系统中,man 7 signal
是一条用于查看信号相关手册页的命令。下面为你详细介绍该命令的含义、使用场景以及手册页中包含的关键信息。
命令含义
man
是 Linux 系统中用于查看手册页(man pages)的命令,它提供了关于系统命令、系统调用、库函数等各种系统组件的详细文档。7
是手册页的章节编号。在 Linux 系统中,手册页被分为多个章节,每个章节涵盖不同类型的内容,章节 7 通常包含杂项信息,如文件格式、约定、宏包、惯例等,而信号相关的通用信息就包含在这个章节中。signal
则指定了要查看的具体手册页主题,即信号相关的信息。
使用场景
- 学习信号知识:如果你是初学者,想要了解 Linux 系统中信号的基本概念、信号编号、默认行为等信息,
man 7 signal
是一个很好的学习资源。 - 开发和调试:在进行系统编程、多进程或多线程编程时,经常需要使用信号来实现进程间通信、异步事件处理等功能。此时,通过
man 7 signal
可以快速查阅信号的详细信息,确保正确使用信号。 - 问题排查:当程序出现与信号相关的问题,如进程异常终止、信号处理不当等,查看
man 7 signal
手册页可以帮助你理解信号的产生原因和默认处理方式,从而找到问题的解决方案。
手册页包含的关键信息
1. 信号概述
手册页会介绍信号的基本概念,包括信号是一种软件中断机制,用于通知进程发生了某种特定事件。
2. 信号列表
详细列出了系统支持的所有信号,包括信号的编号、名称和默认行为。例如:
信号编号 | 信号名称 | 默认行为 | 描述 |
---|---|---|---|
1 | SIGHUP | 终止进程 | 终端线路挂断 |
2 | SIGINT | 终止进程 | 用户按下 Ctrl+C |
3 | SIGQUIT | 终止进程并生成核心转储文件 | 用户按下 Ctrl+\ |
9 | SIGKILL | 强制终止进程 | 不能被捕获或忽略 |
15 | SIGTERM | 终止进程 | 程序终止信号,可被捕获和忽略 |
3. 信号处理
介绍了进程如何处理信号,包括默认处理、忽略信号和自定义处理函数。同时,会说明哪些信号可以被捕获、忽略或阻塞,以及如何使用 signal()
、sigaction()
等系统调用来设置信号处理方式。
4. 信号的产生和发送
解释了信号是如何产生的,如用户操作、硬件异常、软件条件等,以及如何使用 kill()
、raise()
等系统调用来发送信号。
5. 信号的阻塞和未决信号
说明如何使用 sigprocmask()
函数来阻塞和解除阻塞信号,以及未决信号(pending signals)的概念,即已经产生但尚未被进程处理的信号。
6. 信号的可靠性和排队
区分标准信号(不可靠信号)和实时信号(可靠信号)的特点,如标准信号不排队,相同信号可能会被丢弃;实时信号支持排队,相同信号会依次处理。
示例
当你在终端输入 man 7 signal
并回车后,会看到类似以下的手册页内容(实际内容会更详细):
SIGNAL(7) Linux Programmer's Manual SIGNAL(7)
NAME
signal - overview of signals
SYNOPSIS
#include <signal.h>
/* Argument to signal() */
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
DESCRIPTION
Signals are a limited form of inter-process communication (IPC),
typically used in Unix, Unix-like, and other POSIX-compliant
operating systems. A signal is an asynchronous notification sent
to a process or to a specific thread within the same process to
notify it of an event.
...
Standard signals (SIGHUP to SIGSYS) are not queued; if the same
standard signal is sent multiple times while it is blocked, only
one instance of the signal will be marked as pending. In
contrast, real-time signals (SIGRTMIN to SIGRTMAX) are queued;
multiple instances of the same real-time signal can be marked as
pending.
...
SEE ALSO
signal(2), sigaction(2), sigprocmask(2), kill(2), raise(2), alarm(2),
pause(2), sigpending(2), sigsuspend(2), sigqueue(3), psignal(3),
strsignal(3), siginfo(7)
COLOPHON
This page is part of release 5.10 of the Linux man-pages project.
A description of the project, information about reporting bugs,
and the latest version of this page, can be found at
https://www.kernel.org/doc/man-pages/.
Linux 2023-02-05 SIGNAL(7)
通过阅读这些内容,你可以全面了解 Linux 系统中信号的相关知识。
信号的产生
信号的产生可以由多种不同的事件触发,以下是一些常见的情况:
- 用户操作:用户在终端使用特定的按键组合来产生信号。例如,按下
Ctrl + C
会产生SIGINT
信号,通常用于请求中断当前正在运行的进程;按下Ctrl + \
会产生SIGQUIT
信号,它不仅会终止进程,还可能生成核心转储文件。- 硬件异常:当系统硬件出现错误时,会自动产生相应的信号。比如,进程访问了非法的内存地址,就会触发
SIGSEGV
(段错误)信号;进行除零操作时,会产生SIGFPE
(浮点异常)信号。- 软件条件:某些软件事件也会引发信号。例如,当一个进程尝试向一个没有读端的管道写入数据时,会收到
SIGPIPE
信号;使用alarm()
函数设置的定时器到期时,会产生SIGALRM
信号。- 系统调用:可以通过系统调用显式地向进程发送信号。
kill()
系统调用能向指定进程或进程组发送任意信号,raise()
系统调用则可以向当前进程发送信号。
例如:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
// 向当前进程发送 SIGUSR1 信号
raise(SIGUSR1);
return 0;
}
信号的响应方式
信号的响应方式主要分为以下三种:
- 默认处理:每个信号都有其预定义的默认处理动作,这些动作由内核负责执行。
- 忽略信号:进程可以选择对某些信号不做任何处理,直接忽略它们。不过,
SIGKILL
和SIGSTOP
这两个信号是不能被忽略的,因为系统需要确保能够强制控制进程。 - 捕捉信号:进程可以为信号设置自定义的处理函数,当接收到该信号时,会执行这个自定义的处理逻辑。
kill命令
你想说的可能是在终端使用 kill
命令来发送信号给进程。下面为你详细介绍 kill
命令的相关内容,包括基本使用、常用信号及使用场景。
kill
命令基本使用
kill
命令用于向指定的进程发送信号,其基本语法如下:
kill [信号选项] 进程ID
- 信号选项:是可选参数,用于指定要发送的信号。如果不指定,默认发送
SIGTERM
(编号为 15)信号,该信号用于请求进程正常终止。 - 进程 ID:是要接收信号的进程的唯一标识符,可以通过
ps
命令(如ps -ef
查看所有进程信息)或top
命令来获取。
常用信号及对应 kill
命令示例
- SIGTERM(15):这是
kill
命令默认发送的信号,用于请求进程正常终止。进程可以捕获这个信号并执行一些清理操作后再退出。例如,要终止进程 ID 为 1234 的进程,可以使用以下命令:
kill 1234
这等价于:
kill -15 1234
kill -SIGTERM 1234
- SIGKILL(9):强制终止信号,该信号不能被进程捕获或忽略,会直接终止进程。当进程无法响应
SIGTERM
信号时,可以使用SIGKILL
信号。示例命令如下:
kill -9 1234
kill -SIGKILL 1234
- SIGSTOP(19):暂停进程的执行,进程会进入停止状态。这个信号同样不能被捕获或忽略。示例:
kill -19 1234
kill -SIGSTOP 1234
- SIGCONT(18):让暂停的进程继续执行。示例:
kill -18 1234
kill -SIGCONT 1234
kill
命令的使用场景
- 正常关闭进程:当你想要停止一个正在运行的进程,并且希望它有机会进行资源清理时,可以使用默认的
SIGTERM
信号。例如,关闭一个 Web 服务器进程,让它有时间保存未完成的请求和释放资源。 - 强制终止进程:如果进程陷入了无限循环或出现了严重的错误,无法正常响应
SIGTERM
信号,就需要使用SIGKILL
信号来强制终止它。但要注意,使用SIGKILL
信号可能会导致数据丢失或资源未正确释放。 - 暂停和恢复进程:在调试或需要临时暂停进程执行时,可以使用
SIGSTOP
信号暂停进程,之后使用SIGCONT
信号让进程继续执行。
发送信号给多个进程或进程组
- 多个进程:可以在
kill
命令后面跟上多个进程 ID,用空格分隔。例如:
kill 1234 5678 9012
- 进程组:使用
-g
选项可以向进程组发送信号。进程组 ID 通常和该组内父进程的 ID 相同。例如,向进程组 ID 为 2000 的所有进程发送SIGTERM
信号:
kill -g -2000
这里的负号 -2000
表示是进程组 ID。
通过合理使用 kill
命令和不同的信号,可以灵活地控制进程的行为。
信号的捕捉
信号捕捉是指进程为信号设置自定义处理函数的过程,通常使用 signal()
或 sigaction()
系统调用。
signal()
函数:这是一个较简单的信号处理函数,其原型如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义信号处理函数
void sigint_handler(int signum) {
printf("Received SIGINT signal. Continuing...\n");
}
int main() {
// 注册信号处理函数
signal(SIGINT, sigint_handler);
printf("Waiting for SIGINT signal...\n");
while (1) {
sleep(1);
}
return 0;
}
sigaction()
函数:它比signal()
函数更强大和灵活,其原型如下:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义信号处理函数
void sigint_handler(int signum) {
printf("Received SIGINT signal. Continuing...\n");
}
int main() {
struct sigaction sa;
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// 注册信号处理函数
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Waiting for SIGINT signal...\n");
while (1) {
sleep(1);
}
return 0;
}
信号的默认处理
每个信号都有其默认的处理动作,常见的默认处理动作包括:
- 终止进程:如
SIGTERM
(终止信号,可被捕获和忽略)、SIGKILL
(强制终止信号,不可被捕获和忽略)。 - 终止进程并生成核心转储文件:例如
SIGSEGV
(段错误信号)。 - 暂停进程:
SIGSTOP
(暂停信号,不可被捕获和忽略)、SIGTSTP
(交互式暂停信号,可被捕获和忽略)。 - 继续执行暂停的进程:
SIGCONT
信号用于让暂停的进程继续执行。
term 和croe
在 Linux 或类 Unix 系统的信号机制里,Term
和 Core
并非直接指“中断程序”,而是信号默认处理动作的描述,下面为你详细解释。
含义说明
- Term(Terminate):表示终止进程。当进程接收到带有
Term
默认处理动作的信号时,系统会让进程停止运行。例如SIGTERM
(信号编号 15),这是一个比较常用的用于请求进程正常终止的信号。它允许进程捕获该信号,在信号处理函数里执行一些必要的清理操作,像关闭文件、释放资源等,之后再自行终止。代码示例如下:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义 SIGTERM 信号处理函数
void sigterm_handler(int signum) {
printf("Received SIGTERM signal. Cleaning up and exiting...\n");
// 这里可以添加清理资源的代码
_exit(0);
}
int main() {
// 注册信号处理函数
signal(SIGTERM, sigterm_handler);
printf("Running... Press Ctrl+C to send SIGINT, or use 'kill -15 <pid>' to send SIGTERM.\n");
while (1) {
sleep(1);
}
return 0;
}
在上述代码中,程序为 SIGTERM
信号注册了自定义处理函数 sigterm_handler
,当接收到 SIGTERM
信号时,会执行清理操作并终止进程。
- Core(Core Dump):意思是终止进程并生成核心转储文件。核心转储文件是进程在终止时内存状态的一个快照,它记录了进程在崩溃瞬间的内存内容,包含程序计数器、寄存器值、栈信息等。这对于调试程序非常有帮助,开发者可以使用调试工具(如
gdb
)来分析核心转储文件,找出程序崩溃的原因。例如SIGSEGV
(信号编号 11),当进程访问了非法内存地址时会收到这个信号,默认情况下,进程会终止并生成核心转储文件。示例代码如下:
#include <stdio.h>
int main() {
int *ptr = NULL;
// 访问空指针,会触发 SIGSEGV 信号
*ptr = 10;
return 0;
}
运行上述代码时,程序会因访问空指针而触发 SIGSEGV
信号,默认处理动作会终止进程并生成核心转储文件(前提是系统允许生成核心转储文件)。
二者区别
Term
侧重于正常地终止进程,给进程提供了清理资源的机会,是一种相对温和的终止方式。Core
除了终止进程外,还会生成核心转储文件,主要用于调试程序崩溃问题,通常是在进程遇到严重错误(如内存访问错误)时触发。
忽略信号
进程可以选择忽略某些信号,使用 signal()
或 sigaction()
系统调用将信号处理函数设置为 SIG_IGN
即可。示例代码如下:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
// 忽略 SIGINT 信号
signal(SIGINT, SIG_IGN);
printf("SIGINT signal is ignored. Press Ctrl+C to test...\n");
while (1) {
sleep(1);
}
return 0;
}
在上述代码中,通过将 SIGINT
信号的处理函数设置为 SIG_IGN
,实现了对该信号的忽略。
在 Linux/Unix 系统中,信号处理分为标准信号处理和扩展信号处理,它们各有特点且涉及不同的函数接口,
标准信号处理与扩展信号处理概述
标准信号处理
前文已经叙述。。。
扩展信号处理(实时信号处理)
实时信号(编号 34 - 64)是 Linux 系统新增的信号机制,也被称为“可靠信号”。实时信号的响应次序按接收顺序排队,不嵌套。即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。实时信号没有特殊的系统事件与之对应。
// 信号发送者程序sender.c
int main()
{
kill(PID, SIGINT);
}
// 信号接收者程序receiver.c
void func(int sig)
{
// sig 是触发本函数的信号值
}
int main()
{
signal(SIGINT, func);
pause();
}
// 信号发送者程序sender2.c
int main()
{
union sigval val;
val.sival_int = 100;
// 发送一个携带额外信息的信号
sigqueue(PID, SIGINT, val);
}
// 信号接收者程序receiver2.c
void func(int sig, siginfo_t *info, void *arg)
{
// sig 是触发本函数的信号值
}
int main()
{
struct sigaction act;
bzero(&act);
// 指定函数响应函数
act.sa_sigaction = func;
act.sa_flags |= SA_SIGINFO;
sigaction(SIGINT, &act, NULL);
pause();
}
函数接口
sigqueue
函数接口
sigqueue
函数主要用于发送实时信号,并且可以附带数据,这是它与 kill
函数的重要区别,kill
只能单纯地发送信号。
- 函数原型
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
- 参数说明
pid
:目标进程的进程 ID,指定信号要发送到哪个进程。sig
:要发送的信号编号,可以是实时信号或标准信号,但sigqueue
通常用于发送实时信号。value
:是一个union sigval
类型的联合体,用于传递额外的数据。union sigval
的定义如下:
union sigval {
int sival_int;
void *sival_ptr;
};
-
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置相应的
errno
。
-
示例代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
pid_t target_pid = 1234; // 替换为实际的目标进程 ID
union sigval sv;
sv.sival_int = 42;
if (sigqueue(target_pid, SIGRTMIN, sv) == -1) {
perror("sigqueue");
return 1;
}
printf("Signal sent successfully.\n");
return 0;
}
sigaction
函数接口
sigaction
函数用于检查或修改与指定信号相关联的处理动作,相较于 signal
函数,它功能更强大、更灵活,可用于标准信号和实时信号的处理。
- 函数原型
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
-
参数说明
signum
:要操作的信号编号。act
:指向struct sigaction
结构体的指针,用于指定新的信号处理动作。如果为NULL
,则不改变信号的处理动作。oldact
:指向struct sigaction
结构体的指针,用于保存旧的信号处理动作。如果为NULL
,则不保存旧的处理动作。
-
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`:另一种信号处理函数指针,用于接收更多信息,包括信号编号、信号信息结构体指针和上下文指针。当 `sa_flags` 中设置了 `SA_SIGINFO` 标志时,会使用这个函数。
- `sa_mask`:在信号处理函数执行期间需要阻塞的信号集。
- `sa_flags`:一些标志位,用于控制信号处理的行为,例如 `SA_SIGINFO` 表示使用 `sa_sigaction` 函数,`SA_RESTART` 表示系统调用被信号中断后自动重启等。
- `sa_restorer`:该成员已被弃用,一般不使用。
-
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置相应的
errno
。
-
示例代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 传统信号处理函数
void handler(int signum) {
printf("Received signal %d\n", signum);
}
// 带额外信息的信号处理函数
void siginfo_handler(int signum, siginfo_t *info, void *context) {
printf("Received real-time signal %d with value %d\n", signum, info->si_value.sival_int);
}
int main() {
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = siginfo_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Waiting for real-time signal...\n");
while (1) {
sleep(1);
}
return 0;
}
通过 sigqueue
和 sigaction
函数,我们可以更灵活地处理标准信号和实时信号,实现更复杂的进程间通信和信号处理逻辑。