目录标题
1. 简介 (Introduction)
Linux信号是一个复杂而又神奇的概念。它们是操作系统中的一种通信机制,用于通知进程某个事件已经发生。信号可以看作是软件中断,它们可以在任何时候传递给进程,进程可以选择如何响应这些信号。
1.1 什么是信号 (What are signals?)
信号是Linux和其他UNIX系统中的一种基本通信机制。它们为进程之间或操作系统与进程之间提供了一种简单而有效的通信方式。正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“Communication is one of the fundamental services provided by an operating system.”
信号可以被视为异步的通知,它们告诉进程发生了某种特定的事件。例如,当用户按下Ctrl+C时,通常会向前台进程发送一个SIGINT
信号,请求进程终止。
1.2 信号的来源 (Sources of signals)
信号可以由多种来源生成:
- 用户输入 (User input): 例如,当用户在终端中按下Ctrl+C时,会向前台进程发送
SIGINT
信号。2. 系统调用 (System calls): 例如,kill
系统调用可以用于发送信号给其他进程。 - 错误情况 (Error conditions): 例如,当进程尝试除以零时,会收到
SIGFPE
信号。
信号的来源与人类思维的交互方式有异曲同工之妙。当我们面对外部刺激时,我们的大脑会产生反应,这与进程如何响应信号非常相似。这种交互方式为我们提供了深入了解操作系统和人类思维的独特机会。
2. Linux 所有信号的描述 (Description of all Linux signals)
Linux信号是一种简单的通信机制,用于进程之间或操作系统与进程之间的通信。以下是Linux支持的所有信号的详细描述:
2.1 信号列表 (List of Signals)
以下是Linux支持的所有信号的表格:
信号序号 (Signal Number) | 信号名称 (Signal Name) | 描述 (Description) | 常见来源 (Common Source) | 默认行为 (Default Action) | 是否可以被捕获或忽略 (Can be caught or ignored?) |
---|---|---|---|---|---|
1 | SIGHUP | 终端挂起或控制进程终止 (Terminal hang up or controlling process terminated) | 终端关闭 (Terminal closure) | 终止进程 (Terminate process) | 是 (Yes) |
2 | SIGINT | 中断进程 (Interrupt process) | Ctrl+C | 终止进程 (Terminate process) | 是 (Yes) |
3 | SIGQUIT | 退出进程 (Quit process) | Ctrl+\ | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
4 | SIGILL | 非法指令 (Illegal instruction) | 执行了非法指令 (Executing an illegal instruction) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
5 | SIGTRAP | 跟踪/断点陷阱 (Trace/breakpoint trap) | 调试器断点 (Debugger breakpoint) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
6 | SIGABRT/SIGIOT | 进程中止 (Process abort) | 调用abort()函数 (Calling the abort() function) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
7 | SIGBUS | 总线错误 (Bus error) | 非法内存访问 (Illegal memory access) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
8 | SIGFPE | 浮点异常 (Floating-point exception) | 除以零等浮点错误 (Division by zero, etc.) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
9 | SIGKILL | 强制终止进程 (Kill process) | 使用kill命令 (Using the kill command) | 终止进程 (Terminate process) | 否 (No) |
10 | SIGUSR1 | 用户定义信号1 (User-defined signal 1) | 用户程序 (User program) | 终止进程 (Terminate process) | 是 (Yes) |
12 | SIGUSR2 | 用户定义信号2 (User-defined signal 2) | 用户程序 (User program) | 终止进程 (Terminate process) | 是 (Yes) |
13 | SIGPIPE | 管道破裂 (Broken pipe) | 向已关闭的管道或套接字写入 (Writing to a closed pipe or socket) | 终止进程 (Terminate process) | 是 (Yes) |
14 | SIGALRM | 实时时钟信号 (Real-time clock signal) | alarm()或setitimer()函数 (alarm() or setitimer() function) | 终止进程 (Terminate process) | 是 (Yes) |
15 | SIGTERM | 终止信号 (Termination signal) | 使用kill命令 (Using the kill command) | 终止进程 (Terminate process) | 是 (Yes) |
16 | SIGSTKFLT | 协处理器堆栈错误 (Coprocessor stack fault) | 协处理器错误 (Coprocessor error) | 终止进程 (Terminate process) | 是 (Yes) |
17 | SIGCHLD/SIGCLD | 子进程已停止或终止 (Child process has stopped or terminated) | 子进程状态改变 (Child process state change) | 忽略 (Ignore) | 是 (Yes) |
18 | SIGCONT | 继续执行暂停的进程 (Continue executing a stopped process) | fg或bg命令 (fg or bg command) | 继续执行 (Continue execution) | 是 (Yes) |
19 | SIGSTOP | 停止进程 (Stop process) | 使用kill命令或键盘快捷键 (Using the kill command or keyboard shortcut) | 停止进程 (Stop process) | 否 (No) |
20 | SIGTSTP | 键盘产生的停止信号 (Keyboard-generated stop signal) | Ctrl+Z | 停止进程 (Stop process) | 是 (Yes) |
21 | SIGTTIN | 后台进程尝试读取终端 (Background process trying to read from terminal) | 后台进程读取 (Background process read) | 停止进程 (Stop process) | 是 (Yes) |
22 | SIGTTOU | 后台进程尝试写入终端 (Background process trying to write to terminal) | 后台进程写入 (Background process write) | 停止进程 (Stop process) | 是 (Yes) |
23 | SIGURG | 套接字上有紧急数据 (Urgent data on socket) | TCP套接字 (TCP socket) | 忽略 (Ignore) | 是 (Yes) |
24 | SIGXCPU | CPU时间限制已超出 (CPU time limit exceeded) | 进程使用过多CPU时间 (Process using too much CPU time) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
25 | SIGXFSZ | 文件大小限制已超出 (File size limit exceeded) | 进程创建过大的文件 (Process creating a file too large) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
26 | SIGVTALRM | 虚拟时钟信号 (Virtual clock signal) | setitimer()函数 (setitimer() function) | 终止进程 (Terminate process) | 是 (Yes) |
27 | SIGPROF | 统计分析时钟信号 (Profiling clock signal) | 性能分析 (Performance profiling) | 终止进程 (Terminate process) | 是 (Yes) |
28 | SIGWINCH | 窗口大小改变 (Window size change) | 终端窗口大小调整 (Terminal window resize) | 忽略 (Ignore) | 是 (Yes) |
29 | SIGIO/SIGPOLL | I/O现在可能 (I/O now possible) | 文件描述符准备好进行I/O (File descriptor ready for I/O) | 终止进程 (Terminate process) | 是 (Yes) |
30 | SIGPWR | 电源故障 (Power failure) | 电源中断 (Power interruption) | 终止进程 (Terminate process) | 是 (Yes) |
31 | SIGSYS/SIGUNUSED | 非法系统调用 (Bad system call) | 非法或未实现的系统调用 (Invalid or unimplemented system call) | 产生核心转储并终止进程 (Core dump and terminate process) | 是 (Yes) |
32 | SIGRTMIN | 实时信号的最小值 (Minimum real-time signal) | 实时任务 (Real-time tasks) | 终止进程 (Terminate process) | 是 (Yes) |
33 | SIGRTMIN+1 | 实时信号 (Real-time signal) | 实时任务 (Real-time tasks) | 终止进程 (Terminate process) | 是 (Yes) |
… | … | … | … | … | … |
62 | SIGRTMAX-1 | 实时信号 (Real-time signal) | 实时任务 (Real-time tasks) | 终止进程 (Terminate process) | 是 (Yes) |
63 | SIGRTMAX | 实时信号的最大值 (Maximum real-time signal) | 实时任务 (Real-time tasks) | 终止进程 (Terminate process) | 是 (Yes) |
2.2 不建议变更行为的信号
虽然某些信号可以被捕获,但它们的默认行为不能被改变。这意味着即使您为这些信号提供了一个处理函数,它们的默认行为仍然会被执行。
还有些信号,例如 SIGSEGV(信号序号11)通常用于表示无效的内存引用。虽然它可以被捕获,但通常不建议改变其默认行为,因为这可能会导致未定义的行为或进一步的系统不稳定。如果一个进程捕获了SIGSEGV并尝试继续执行,它可能会遇到更多的问题。
常见的有:
SIGKILL
(信号序号9):用于立即终止进程。不能被捕获、阻塞或忽略。SIGSTOP
(信号序号19):用于立即停止(暂停)进程的执行。不能被捕获、阻塞或忽略。SIGSEGV
(信号序号11):表示无效的内存引用。虽然可以被捕获,但不建议改变其默认行为。SIGBUS
(信号序号7):表示非法地址,如对齐错误。虽然可以被捕获,但不建议改变其默认行为。SIGFPE
(信号序号8):表示算术异常,如除以零。虽然可以被捕获,但不建议改变其默认行为。SIGILL
(信号序号4):表示非法指令。虽然可以被捕获,但不建议改变其默认行为。
这些信号通常表示严重的程序错误或异常情况。虽然技术上可以为它们设置处理函数,但在大多数情况下,最好让它们的默认行为(通常是终止进程并生成核心转储)发生,以便可以对问题进行调试。
3. 默认行为 (Default actions)
Linux信号的默认行为是当进程接收到某个信号时,系统会自动执行的操作。每种信号都有其预定义的默认行为,但程序员可以选择覆盖这些行为,除了某些特定的信号,如SIGKILL和SIGSTOP。
3.1 终止进程 (Terminate the process)
大多数信号的默认行为是终止接收信号的进程。例如,当用户在终端中按下Ctrl+C时,前台进程会收到SIGINT信号,导致进程终止。这种行为可以被捕获和修改,但在某些情况下,如程序出现错误,终止进程可能是最佳选择。
#include <iostream>
#include <signal.h>
#include <unistd.h>
void signalHandler(int signum) {
std::cout << "收到信号 " << signum << ",但我选择忽略它!" << std::endl;
}
int main() {
signal(SIGINT, signalHandler); // 捕获SIGINT信号
while(1) {
std::cout << "等待信号..." << std::endl;
sleep(1);
}
return 0;
}
在上面的代码中,我们捕获了SIGINT信号并选择忽略它,而不是终止进程。
3.2 忽略信号 (Ignore the signal)
某些信号的默认行为是被忽略。例如,SIGCHLD信号是当子进程停止或终止时发送给其父进程的信号。默认情况下,这个信号会被忽略,但程序员可以选择捕获它并执行特定的操作。
正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“我们不能控制外部世界给我们带来的挑战,但我们可以控制我们如何回应。”
3.3 产生核心转储 (Produce a core dump)
某些信号,如SIGQUIT和SIGABRT,不仅会导致进程终止,还会产生一个核心转储文件。这个文件包含了进程在终止时的内存映像,可以用于后续的调试。
在人类的思维中,我们经常需要回溯我们的思考过程,找出导致错误的原因。核心转储的概念与此相似,它允许我们“回溯”进程的状态,找出导致错误的原因。
3.4 停止进程 (Stop the process)
某些信号,如SIGSTOP,会导致进程停止执行,但不会终止进程。这允许管理员或调试器在后续恢复进程的执行。
在Linux内核源码中,kernel/signal.c
文件中的do_signal_stop
函数处理这种信号,暂停进程的执行并将其状态设置为TASK_STOPPED
。
4. 通常的处理方案 (Common handling strategies)
Linux信号处理是一个复杂的过程,涉及多个层次的交互和策略。为了更好地理解这一过程,我们将深入探讨信号处理的常见策略。
4.1 信号掩码 (Signal masks)
信号掩码是一个位掩码,用于指定哪些信号应该被阻塞,即暂时不被处理。当一个信号被阻塞时,它不会立即传递给进程,而是被挂起,直到信号掩码被更改并允许该信号。
例如,我们可以使用sigprocmask
函数来设置或检查信号掩码。以下是一个简单的示例:
#include <signal.h>
#include <stdio.h>
int main() {
sigset_t newmask, oldmask;
// 初始化新的信号掩码并添加SIGINT
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
// 阻塞SIGINT并保存当前信号掩码
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
perror("SIG_BLOCK error");
// 此时,SIGINT信号被阻塞...
// 恢复原始信号掩码
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
perror("SIG_SETMASK error");
}
正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“控制复杂性是计算机编程的本质。”信号掩码为我们提供了一种控制复杂性的方法,允许我们精确地控制哪些信号应该被处理,哪些信号应该被忽略。
4.2 信号队列 (Signal queues)
当多个实例的同一信号几乎同时发送给进程时,这些信号不会排队等待;相反,只有一个实例会被传递。但是,不同的信号会被放入队列中等待处理。
这种机制确保了信号的高效处理,但也可能导致某些信号丢失。为了解决这个问题,Linux引入了实时信号,这些信号可以排队并带有额外的数据。
4.3 信号处理函数 (Signal handling functions)
信号处理函数是当进程接收到特定信号时执行的函数。我们可以使用signal
或sigaction
函数来指定信号处理函数。
以下是一个简单的示例,展示如何为SIGINT信号设置一个处理函数:
#include <signal.h>
#include <stdio.h>
void sigint_handler(int signo) {
printf("Received SIGINT!\n");
}
int main() {
if (signal(SIGINT, sigint_handler) == SIG_ERR)
perror("signal error");
// 主循环
while (1);
}
在这个示例中,当我们按下Ctrl+C时,sigint_handler
函数会被调用,而不是默认的行为(终止进程)。
信号处理函数为我们提供了一种灵活的方式来响应和处理信号,允许我们根据需要定制信号的行为。
4.4 从内核源码角度分析 (Analysis from the kernel source code perspective)
Linux内核中的signal.c
文件包含了信号处理的主要逻辑。例如,do_signal
函数是处理进程接收到的信号的主要函数。它首先检查是否有未处理的信号,然后决定如何处理这些信号。
此外,send_signal
函数负责将信号发送给目标进程。它首先检查目标进程是否已经阻塞了该信号,然后决定是否将信号放入队列中或立即传递给进程。
这些函数展示了Linux内核如何高效、灵活地处理信号,确保系统的稳定性和响应性。
结语
在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。
这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。
我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。
阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页