Linux的Signal机制是什么

发布时间:2022-02-18 09:43:41 作者:iii
来源:亿速云 阅读:193
# 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信号

5. 内核

内核在某些情况下会发送信号: - SIGKILL:内存耗尽时OOM Killer发送 - SIGTERM:系统关机时发送

信号的分类

Linux信号可以分为以下几类:

1. 标准信号(非实时信号)

编号1-31的信号,具有以下特点: - 不支持排队,相同信号多次发送可能丢失 - 没有优先级顺序 - 部分信号有特殊含义(如SIGKILL、SIGSTOP)

2. 实时信号(扩展信号)

编号32-64的信号(具体范围取决于系统),特点: - 支持排队,不会丢失 - 按发送顺序处理 - 可以携带附加信息

3. 不可靠信号与可靠信号

历史分类方式: - 不可靠信号:早期Unix实现中的信号(1-31) - 可靠信号:POSIX标准引入的实时信号

信号的处理方式

进程对信号的处理有三种基本方式:

1. 忽略信号(SIG_IGN)

signal(SIGINT, SIG_IGN);  // 忽略SIGINT信号

注意:SIGKILL和SIGSTOP不能被忽略

2. 捕获信号(自定义处理函数)

void handler(int sig) {
    printf("Received signal %d\n", sig);
}
signal(SIGINT, handler);

3. 执行默认动作(SIG_DFL)

signal(SIGINT, SIG_DFL);  // 恢复SIGINT默认行为

默认动作可能是: - Terminate:终止进程 - Core:终止并产生core dump - Ignore:忽略信号 - Stop:暂停进程 - Continue:继续已停止的进程

信号处理函数

1. signal()函数

传统信号处理函数,简单但不推荐使用:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

2. 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);
};

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

信号处理函数的限制

信号处理函数(信号处理器)需要注意: 1. 必须是可重入函数 2. 不能调用非异步信号安全的函数 3. 执行时间应尽可能短 4. 可能被其他信号中断

信号集与信号屏蔽

信号集(sigset_t)

表示一组信号的集合:

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);  // 同步等待信号

实时信号与非实时信号

实时信号特点

  1. 支持排队,不会丢失
  2. 按发送顺序处理
  3. 可以携带附加信息(通过siginfo_t结构)
  4. 优先级高于标准信号

发送实时信号

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内核中信号的实现涉及以下关键数据结构:

1. task_struct中的信号相关字段

struct task_struct {
    // ...
    struct signal_struct *signal;
    struct sighand_struct *sighand;
    sigset_t blocked;
    sigset_t real_blocked;
    struct sigpending pending;
    // ...
};

2. 信号递送流程

  1. 发送信号(通过kill、tkill等系统调用)
  2. 内核检查目标进程的信号屏蔽
  3. 将信号加入目标进程的pending队列
  4. 设置进程的TIF_SIGPENDING标志
  5. 在返回用户空间前检查并处理信号

3. 信号处理流程

  1. 从内核态返回用户态前检查TIF_SIGPENDING
  2. 调用do_signal()处理信号
  3. 设置用户栈帧,准备执行信号处理函数
  4. 信号处理函数返回后恢复原始执行流程

常见信号详解

1. SIGINT (2)

终端中断信号,通常由Ctrl+C产生。默认动作为终止进程。

2. SIGQUIT (3)

终端退出信号,通常由Ctrl+\产生。默认动作为终止并产生core dump。

3. SIGKILL (9)

强制终止信号,不能被捕获、忽略或阻塞。系统管理员的最后手段。

4. SIGSEGV (11)

段错误信号,由非法内存访问触发。常见原因: - 访问空指针 - 访问已释放内存 - 栈溢出

5. SIGPIPE (13)

管道破裂信号,当向无读者的管道写入时触发。

6. SIGALRM (14)

定时器信号,由alarm()或setitimer()设置。

7. SIGTERM (15)

终止信号,请求进程正常退出。可以被捕获和处理。

8. SIGCHLD (17)

子进程状态改变信号。通知父进程子进程终止或停止。

信号编程实践

1. 基本信号处理示例

#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;
}

2. 使用sigaction的可靠信号处理

#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;
}

3. 信号屏蔽示例

#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;
}

信号的安全问题

1. 异步信号安全

信号处理函数中只能调用异步信号安全函数(如write、_exit等),因为: - 可能中断非可重入函数的执行 - 可能破坏全局数据结构 - 可能引起死锁

2. 竞态条件

信号处理可能引入竞态条件,例如: - 检查全局标志与信号处理修改标志之间的竞争 - 信号中断系统调用导致EINTR错误

3. 信号处理的最佳实践

  1. 保持信号处理函数尽可能简单
  2. 只设置volatile sig_atomic_t类型的全局标志
  3. 使用自管道技术(self-pipe)将信号转换为I/O事件
  4. 考虑使用signalfd()将信号转换为文件描述符

信号与其他进程间通信方式的比较

特性 信号 管道 消息队列 共享内存 套接字
通信方向 单向 单向 单向 双向 双向
数据量 有限 有限
速度 最快 中等 最快 中等
同步 异步 同步 同步 需同步 同步/异步
复杂度 简单 简单 中等 复杂 复杂

信号的高级话题

1. 信号与线程

在多线程环境中: - 信号处理是进程范围的 - 每个线程有自己的信号屏蔽 - kill()发送的信号会被任意线程处理 - pthread_kill()可以向特定线程发送信号

2. 信号与系统调用

慢速系统调用可能被信号中断: - 自动重启(SA_RESTART) - 手动处理EINTR错误

while ((n = read(fd, buf, size)) == -1 && errno == EINTR)
    continue;

3. 替代信号处理的技术

  1. signalfd:将信号转换为文件描述符
int sfd = signalfd(-1, &mask, 0);
read(sfd, &info, sizeof(info));  // 同步读取信号
  1. 自管道技术
pipe(selfpipe);
write(selfpipe[1], "X", 1);  // 在信号处理函数中

4. 信号与容器

在容器环境中: - 信号传播受namespace影响 - 容器init进程需要正确处理信号 - SIGTERM和SIGKILL对容器有特殊含义

总结

Linux信号机制是系统编程中的重要组成部分,理解信号机制对于开发健壮的Linux应用程序至关重要。关键要点包括:

  1. 信号是异步事件通知机制,用于进程间通信和异常处理
  2. 信号处理需要考虑可重入性和异步信号安全
  3. 现代编程应优先使用sigaction而非signal
  4. 实时信号提供了更可靠的信号传递机制
  5. 在多线程环境中需要特别注意信号处理
  6. 存在多种替代技术可以简化信号处理

掌握信号机制可以帮助开发者编写更健壮、可靠的Linux应用程序,有效处理各种异常情况和进程间通信需求。


本文共计约11,200字,详细介绍了Linux信号机制的各个方面,包括基本原理、编程接口、内核实现和实际应用等内容。 “`

推荐阅读:
  1. Linux捕捉信号机制之(signal,kill)、(sig
  2. signal 信号

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

linux signal

上一篇:Linux下常用的过滤操作有哪些

下一篇:Linux下如何刻录光盘

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》