您好,登录后才能下订单哦!
# Linux的Signal机制是什么
## 目录
1. [信号机制概述](#信号机制概述)
2. [信号的产生与来源](#信号的产生与来源)
3. [信号的分类](#信号的分类)
4. [信号的处理方式](#信号的处理方式)
5. [信号处理函数](#信号处理函数)
6. [信号集与信号屏蔽](#信号集与信号屏蔽)
7. [实时信号与非实时信号](#实时信号与非实时信号)
8. [信号在内核中的实现](#信号在内核中的实现)
9. [常见信号详解](#常见信号详解)
10. [信号编程实践](#信号编程实践)
11. [信号的安全问题](#信号的安全问题)
12. [信号与其他进程间通信方式的比较](#信号与其他进程间通信方式的比较)
13. [信号的高级话题](#信号的高级话题)
14. [总结](#总结)
## 信号机制概述
Linux信号(Signal)是操作系统内核向进程发送的一种异步通知机制,用于通知进程发生了某个事件。信号机制是Unix/Linux系统中最早的进程间通信方式之一,具有以下特点:
- **异步性**:信号可以在任何时候发送给进程
- **轻量级**:信号处理开销很小
- **有限种类**:Linux支持的标准信号数量有限(通常是1-31)
- **简单通信**:只能传递信号编号,不能携带复杂数据
信号机制最初的设计目的是:
1. 通知进程硬件异常(如除零错误、段错误)
2. 允许用户通过终端中断进程(如Ctrl+C)
3. 实现简单的进程间通信
## 信号的产生与来源
信号可以由多种来源产生:
### 1. 硬件异常
当硬件检测到异常条件时会触发信号,例如:
- SIGSEGV(段错误):非法内存访问
- SIGFPE(浮点异常):算术运算错误
- SIGILL(非法指令):执行了非法CPU指令
### 2. 终端控制
通过终端按键产生的信号:
- SIGINT(中断):Ctrl+C
- SIGQUIT(退出):Ctrl+\
- SIGTSTP(终端停止):Ctrl+Z
### 3. 软件事件
由软件条件触发的信号:
- SIGALRM:定时器超时
- SIGPIPE:管道破裂
- SIGCHLD:子进程状态改变
### 4. kill命令或kill系统调用
用户或程序显式发送信号:
```bash
kill -9 PID # 发送SIGKILL信号
内核在某些情况下会发送信号: - SIGKILL:内存耗尽时OOM Killer发送 - SIGTERM:系统关机时发送
Linux信号可以分为以下几类:
编号1-31的信号,具有以下特点: - 不支持排队,相同信号多次发送可能丢失 - 没有优先级顺序 - 部分信号有特殊含义(如SIGKILL、SIGSTOP)
编号32-64的信号(具体范围取决于系统),特点: - 支持排队,不会丢失 - 按发送顺序处理 - 可以携带附加信息
历史分类方式: - 不可靠信号:早期Unix实现中的信号(1-31) - 可靠信号:POSIX标准引入的实时信号
进程对信号的处理有三种基本方式:
signal(SIGINT, SIG_IGN); // 忽略SIGINT信号
注意:SIGKILL和SIGSTOP不能被忽略
void handler(int sig) {
printf("Received signal %d\n", sig);
}
signal(SIGINT, handler);
signal(SIGINT, SIG_DFL); // 恢复SIGINT默认行为
默认动作可能是: - Terminate:终止进程 - Core:终止并产生core dump - Ignore:忽略信号 - Stop:暂停进程 - Continue:继续已停止的进程
传统信号处理函数,简单但不推荐使用:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
更强大可靠的信号处理接口:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
信号处理函数(信号处理器)需要注意: 1. 必须是可重入函数 2. 不能调用非异步信号安全的函数 3. 执行时间应尽可能短 4. 可能被其他信号中断
表示一组信号的集合:
int sigemptyset(sigset_t *set); // 清空信号集
int sigfillset(sigset_t *set); // 包含所有信号
int sigaddset(sigset_t *set, int signum); // 添加信号
int sigdelset(sigset_t *set, int signum); // 删除信号
int sigismember(const sigset_t *set, int signum); // 测试信号
阻止信号被递送:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how参数: - SIG_BLOCK:将set中的信号加入阻塞集 - SIG_UNBLOCK:从阻塞集中移除set中的信号 - SIG_SETMASK:直接设置阻塞集为set
int sigsuspend(const sigset_t *mask); // 临时解除阻塞并等待信号
int sigwait(const sigset_t *set, int *sig); // 同步等待信号
union sigval {
int sival_int;
void *sival_ptr;
};
int sigqueue(pid_t pid, int sig, const union sigval value);
使用SA_SIGINFO标志:
struct sigaction sa;
sa.sa_sigaction = realtime_handler; // 使用三参数处理函数
sa.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN, &sa, NULL);
Linux内核中信号的实现涉及以下关键数据结构:
struct task_struct {
// ...
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
struct sigpending pending;
// ...
};
终端中断信号,通常由Ctrl+C产生。默认动作为终止进程。
终端退出信号,通常由Ctrl+\产生。默认动作为终止并产生core dump。
强制终止信号,不能被捕获、忽略或阻塞。系统管理员的最后手段。
段错误信号,由非法内存访问触发。常见原因: - 访问空指针 - 访问已释放内存 - 栈溢出
管道破裂信号,当向无读者的管道写入时触发。
定时器信号,由alarm()或setitimer()设置。
终止信号,请求进程正常退出。可以被捕获和处理。
子进程状态改变信号。通知父进程子进程终止或停止。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig) {
printf("Received signal %d\n", sig);
}
int main() {
signal(SIGINT, handler);
while(1) {
printf("Running...\n");
sleep(1);
}
return 0;
}
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
void handler(int sig, siginfo_t *info, void *ucontext) {
printf("Received signal %d from PID %d\n",
sig, info->si_pid);
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &sa, NULL);
while(1) {
pause();
}
return 0;
}
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
sigset_t set;
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigemptyset(&set);
sigaddset(&set, SIGINT);
printf("Blocking SIGINT\n");
sigprocmask(SIG_BLOCK, &set, NULL);
sleep(5); // 在此期间SIGINT被阻塞
printf("Unblocking SIGINT\n");
sigprocmask(SIG_UNBLOCK, &set, NULL);
while(1) pause();
return 0;
}
信号处理函数中只能调用异步信号安全函数(如write、_exit等),因为: - 可能中断非可重入函数的执行 - 可能破坏全局数据结构 - 可能引起死锁
信号处理可能引入竞态条件,例如: - 检查全局标志与信号处理修改标志之间的竞争 - 信号中断系统调用导致EINTR错误
特性 | 信号 | 管道 | 消息队列 | 共享内存 | 套接字 |
---|---|---|---|---|---|
通信方向 | 单向 | 单向 | 单向 | 双向 | 双向 |
数据量 | 无 | 有限 | 有限 | 大 | 大 |
速度 | 最快 | 快 | 中等 | 最快 | 中等 |
同步 | 异步 | 同步 | 同步 | 需同步 | 同步/异步 |
复杂度 | 简单 | 简单 | 中等 | 复杂 | 复杂 |
在多线程环境中: - 信号处理是进程范围的 - 每个线程有自己的信号屏蔽 - kill()发送的信号会被任意线程处理 - pthread_kill()可以向特定线程发送信号
慢速系统调用可能被信号中断: - 自动重启(SA_RESTART) - 手动处理EINTR错误
while ((n = read(fd, buf, size)) == -1 && errno == EINTR)
continue;
int sfd = signalfd(-1, &mask, 0);
read(sfd, &info, sizeof(info)); // 同步读取信号
pipe(selfpipe);
write(selfpipe[1], "X", 1); // 在信号处理函数中
在容器环境中: - 信号传播受namespace影响 - 容器init进程需要正确处理信号 - SIGTERM和SIGKILL对容器有特殊含义
Linux信号机制是系统编程中的重要组成部分,理解信号机制对于开发健壮的Linux应用程序至关重要。关键要点包括:
掌握信号机制可以帮助开发者编写更健壮、可靠的Linux应用程序,有效处理各种异常情况和进程间通信需求。
本文共计约11,200字,详细介绍了Linux信号机制的各个方面,包括基本原理、编程接口、内核实现和实际应用等内容。 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。