在 Linux 系统编程中,信号(Signal) 是一种异步通知机制,用于告知进程发生了某种事件。理解常见的信号及其默认行为对于编写健壮的应用程序至关重要。
目录
[1. SIGHUP(信号编号:1)](#1. SIGHUP(信号编号:1))
[2. SIGINT(信号编号:2)](#2. SIGINT(信号编号:2))
[3. SIGQUIT(信号编号:3)](#3. SIGQUIT(信号编号:3))
[4. SIGILL(信号编号:4)](#4. SIGILL(信号编号:4))
[5. SIGABRT(信号编号:6)](#5. SIGABRT(信号编号:6))
[6. SIGFPE(信号编号:8)](#6. SIGFPE(信号编号:8))
[7. SIGKILL(信号编号:9)](#7. SIGKILL(信号编号:9))
[8. SIGUSR1 和 SIGUSR2(信号编号:10 和 12)](#8. SIGUSR1 和 SIGUSR2(信号编号:10 和 12))
[9. SIGSEGV(信号编号:11)](#9. SIGSEGV(信号编号:11))
[10. SIGPIPE(信号编号:13)](#10. SIGPIPE(信号编号:13))
[11. SIGALRM(信号编号:14)](#11. SIGALRM(信号编号:14))
[12. SIGTERM(信号编号:15)](#12. SIGTERM(信号编号:15))
[13. SIGCHLD(信号编号:17)](#13. SIGCHLD(信号编号:17))
[14. SIGCONT(信号编号:18)](#14. SIGCONT(信号编号:18))
[15. SIGSTOP(信号编号:19)](#15. SIGSTOP(信号编号:19))
[16. SIGTSTP(信号编号:20)](#16. SIGTSTP(信号编号:20))
[使用 sigaction() 更精确地设置信号处理函数](#使用 sigaction() 更精确地设置信号处理函数)
一、信号的分类与作用
Linux 定义了多种信号,每个信号都有一个编号和默认的行为。下面列出了一些常用的信号及其用途:
1. SIGHUP
(信号编号:1)
- 默认行为:终止
- 用途:当控制终端关闭或网络连接断开时发送给进程。通常用于通知守护进程重新加载配置文件。
2. SIGINT
(信号编号:2)
- 默认行为:终止
- 用途:由用户通过键盘输入中断字符(通常是 Ctrl+C)产生,要求进程停止执行。
3. SIGQUIT
(信号编号:3)
- 默认行为:核心转储后终止
- 用途 :类似于
SIGINT
,但要求进程生成核心转储文件以便调试。
4. SIGILL
(信号编号:4)
- 默认行为:核心转储后终止
- 用途:非法指令错误,例如尝试执行无效的操作码。
5. SIGABRT
(信号编号:6)
- 默认行为:核心转储后终止
- 用途 :由调用
abort()
函数触发,用于异常终止程序并生成核心转储文件。
6. SIGFPE
(信号编号:8)
- 默认行为:核心转储后终止
- 用途:浮点运算错误,如除以零或溢出。
7. SIGKILL
(信号编号:9)
- 默认行为:强制终止
- 用途:无法被捕获或忽略,用于立即终止进程,通常作为最后手段。
8. SIGUSR1
和 SIGUSR2
(信号编号:10 和 12)
- 默认行为:终止
- 用途:用户定义的信号,可用于应用程序内部通信或特殊处理逻辑。
9. SIGSEGV
(信号编号:11)
- 默认行为:核心转储后终止
- 用途:无效内存引用(段错误),常用于调试内存访问问题。
10. SIGPIPE
(信号编号:13)
- 默认行为:终止
- 用途:写入管道或 socket 连接已关闭时触发。
11. SIGALRM
(信号编号:14)
- 默认行为:终止
- 用途 :由
alarm()
函数设置的定时器超时时触发,常用于实现定时任务。
12. SIGTERM
(信号编号:15)
- 默认行为:终止
- 用途 :请求进程正常终止,可以被捕获和处理,比
SIGKILL
更友好。
13. SIGCHLD
(信号编号:17)
- 默认行为:忽略
- 用途:子进程状态改变时发送给父进程,用于回收僵尸进程。
14. SIGCONT
(信号编号:18)
- 默认行为:继续执行
- 用途:如果进程被暂停,则恢复其执行。
15. SIGSTOP
(信号编号:19)
- 默认行为:暂停
- 用途:不可捕获或忽略,用于暂停进程执行。
16. SIGTSTP
(信号编号:20)
- 默认行为:暂停
- 用途 :类似
SIGSTOP
,但可被捕获或忽略,通常由终端输入 Ctrl+Z 触发。
二、信号的处理方式
对于每一个信号,可以采取以下三种处理方式之一:
- 默认动作:根据信号类型采取相应操作(如终止进程、忽略信号等)。
- 忽略信号 :使用
signal()
或sigaction()
注册忽略处理函数。 - 自定义处理函数:为信号指定一个处理函数,当信号到达时会调用该函数。
示例代码:自定义信号处理函数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle_signal(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
// 设置 SIGINT 的处理函数
if (signal(SIGINT, handle_signal) == SIG_ERR) {
perror("signal");
exit(EXIT_FAILURE);
}
printf("Press Ctrl+C to test...\n");
while (1) {
pause(); // 挂起等待信号
}
return 0;
}
使用 sigaction()
更精确地设置信号处理函数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle_signal(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
struct sigaction sa;
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("Press Ctrl+C to test...\n");
while (1) {
pause();
}
return 0;
}
三、信号的阻塞与未决
有时候我们需要暂时阻止某些信号的传递,即使信号已经发送也不会立即处理,直到解除阻塞为止。此时这些信号处于"未决"状态。
示例代码:演示信号阻塞
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle_signal(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
sigset_t newmask, oldmask;
// 设置 SIGINT 的处理函数
struct sigaction sa;
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
// 阻塞 SIGINT
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
printf("Blocked SIGINT, press Ctrl+C...\n");
sleep(10); // 在这期间按 Ctrl+C 不会触发 handler
// 解除阻塞
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
printf("Unblocked SIGINT\n");
pause(); // 现在按 Ctrl+C 会触发 handler
return 0;
}
四、总结知识点图解(知识树状图)
常见信号及其用途
│
├── SIGHUP
│ ├── 编号:1
│ └── 用途:通知守护进程重新加载配置
│
├── SIGINT
│ ├── 编号:2
│ └── 用途:用户请求中断(Ctrl+C)
│
├── SIGQUIT
│ ├── 编号:3
│ └── 用途:用户请求退出并生成 core 文件(Ctrl+\)
│
├── SIGILL
│ ├── 编号:4
│ └── 用途:非法指令错误
│
├── SIGABRT
│ ├── 编号:6
│ └── 用途:调用 abort() 时生成 core 文件
│
├── SIGFPE
│ ├── 编号:8
│ └── 用途:浮点运算错误
│
├── SIGKILL
│ ├── 编号:9
│ └── 用途:强制终止进程
│
├── SIGUSR1 / SIGUSR2
│ ├── 编号:10 / 12
│ └── 用途:用户定义信号
│
├── SIGSEGV
│ ├── 编号:11
│ └── 用途:无效内存引用
│
├── SIGALRM
│ ├── 编号:14
│ └── 用途:定时器超时
│
├── SIGTERM
│ ├── 编号:15
│ └── 用途:请求进程正常终止
│
├── SIGCHLD
│ ├── 编号:17
│ └── 用途:子进程状态变化
│
├── SIGCONT
│ ├── 编号:18
│ └── 用途:继续暂停的进程
│
├── SIGSTOP
│ ├── 编号:19
│ └── 用途:暂停进程
│
└── SIGTSTP
├── 编号:20
└── 用途:终端停止命令(Ctrl+Z)
五、课后练习建议
- 编写程序注册不同信号的处理函数,并通过
kill
命令发送信号测试。 - 实现一个简单的计时器,使用
alarm()
和SIGALRM
处理函数实现定时任务。 - 修改上面的例子,让程序在接收到
SIGINT
后不退出,而是打印一条消息并继续运行。 - 使用
sigaction()
替代signal()
,体验更精细的信号处理控制。 - 探索如何使用
sigprocmask()
和sigsuspend()
实现更复杂的信号同步逻辑。
bash
gcc your_signal_program.c -o your_signal_program
./your_signal_program &
kill -<signal_number> <PID>