目录

1 信号(Signal)

1.1 本质

1.2 原理

1.3 实现方式

1.4 特点总结

1.5 Linux 常用信号总表(System V + POSIX)

1.6 信号宏

1.7 对比

2 C语言实现

2.1 System V 程序实现

2.1.1 接口讲解

2.1.2 使用示例

2.2 POSIX 程序实现

2.2.1 接口讲解

2.2.2 使用示例

         3 面试常见问题


1 信号(Signal)

1.1 本质

信号是一种异步通知机制,用于在进程之间或内核与进程之间传递事件。

一句话本质: 让操作系统向进程“发送一个事件通知”,告诉它“发生了某件事”,比如中断、异常、退出、用户操作等。


1.2 原理

信号的原理可以类比为软中断(Software Interrupt)

  • 当某个事件发生(如按下 Ctrl+C、子进程退出、定时器超时、kill 命令等),内核向目标进程发送一个信号。

  • 目标进程在运行时被打断,转而执行对应的信号处理函数(Signal Handler)

  • 若进程未注册处理函数,则执行默认动作(如终止、忽略或停止)。

信号传递依赖内核调度,由内核维护信号队列和屏蔽字。


1.3 实现方式

标准 接口 特点
System V kill 老标准,只提供发送信号功能,兼容性好
POSIX kill, signal, sigaction, sigprocmask, sigpending, sigsuspend, alarm, setitimer 新标准,接口全面,支持注册处理函数、阻塞/等待信号、定时器功能,更现代可靠

在纯 System V 标准中,只能发送信号(kill),而无法定义自定义处理函数。接收方只能执行系统默认行为(终止、忽略、暂停等)。只有在 POSIX 引入信号处理机制之后,才支持进程自定义处理函数


1.4 特点总结

异步:信号随时可能到达并中断进程执行。
📢 事件驱动:通常用于通知某个状态或事件发生。
🔒 不可携带复杂数据:信号只传递类型(及少量附加信息)。
🧠 内核直接支持:无需额外内存或管道。
🚫 不适合频繁通信:更适用于“控制通知”而非“数据交换”。


1.5 Linux 常用信号总表(System V + POSIX)

编号 宏名称 触发来源 含义(用途说明) 默认行为
1 SIGHUP 终端挂断、会话关闭、或由程序发送 “挂起”信号,表示控制终端断开或配置需重载(常用于守护进程 reload) 终止进程
2 SIGINT 用户按下 Ctrl+Ckill -2 中断进程(Interrupt) 终止进程
3 SIGQUIT 用户按下 Ctrl+\kill -3 退出并产生 core dump 终止 + core dump
4 SIGILL CPU 执行非法指令 非法指令错误(可能程序损坏) 终止 + core dump
5 SIGTRAP 调试器断点、trap 指令 调试用途(gdb 使用) 终止 + core dump
6 SIGABRT 调用 abort() 函数 异常终止 终止 + core dump
7 SIGBUS 非法内存访问(对齐错误或无效总线访问) 硬件访问错误 终止 + core dump
8 SIGFPE 浮点异常(除零等) 算术错误 终止 + core dump
9 SIGKILL kill -9 或系统强制杀死 无法被捕获/忽略的终止信号 立即终止(不可捕获)
10 SIGUSR1 用户自定义信号(1) 应用自定义事件 终止进程(可捕获)
11 SIGSEGV 无效内存访问(Segmentation Fault) 访问非法内存区域 终止 + core dump
12 SIGUSR2 用户自定义信号(2) 应用自定义事件 终止进程(可捕获)
13 SIGPIPE 向已关闭的管道/Socket 写入 写入失败时产生 终止进程(默认),常被忽略
14 SIGALRM 调用 alarm() / setitimer() 定时器到期信号 终止进程
15 SIGTERM 默认的 kill 信号 (kill -15) 请求进程正常退出 终止进程
16 SIGSTKFLT 协处理器栈错误(历史) 已废弃(罕见) 终止进程
17 SIGCHLD 子进程停止或结束时发送给父进程 子进程退出通知 忽略(默认不终止)
18 SIGCONT 恢复被暂停的进程(与 SIGSTOP 对应) kill -CONT 或系统恢复 继续运行
19 SIGSTOP 强制暂停进程(无法捕获) kill -STOPCtrl+Z 暂停(不可捕获/忽略)
20 SIGTSTP 用户按下 Ctrl+Z 暂停进程(可捕获) 暂停(可捕获)
21 SIGTTIN 后台进程尝试读终端 Shell 作业控制 暂停
22 SIGTTOU 后台进程尝试写终端 Shell 作业控制 暂停
23 SIGURG Socket 有“带外数据”(Out-of-band data) 网络异步事件 忽略
24 SIGXCPU 超出 CPU 时间限制(setrlimit 系统资源保护 终止 + core dump
25 SIGXFSZ 文件超过最大限制 文件系统限制 终止 + core dump
26 SIGVTALRM setitimer(ITIMER_VIRTUAL) 超时 虚拟时间定时器 终止进程
27 SIGPROF setitimer(ITIMER_PROF) 超时 统计/性能分析用 终止进程
28 SIGWINCH 终端窗口大小变化 shell/tui 程序响应窗口变化 忽略
29 SIGIO / SIGPOLL 文件描述符可读/可写 异步 IO 通知 终止进程
30 SIGPWR 电源故障、系统关机事件 UPS / 电源通知 终止进程
31 SIGSYS 非法系统调用 系统调用参数错误 终止 + core dump

⚙️ 实时信号(POSIX.1b Real-time Signals)

宏名称 含义 触发来源 默认行为
SIGRTMIN ~ SIGRTMAX 实时信号区间(数量由系统决定,通常为 32~64) 用户自定义、POSIX 定时器、异步 I/O、线程通信 终止进程(可捕获)

特点:

  • 支持信号队列(不会合并)

  • 可携带参数(sigqueue()

  • 用于线程、实时通信、异步事件(如 timer_create)


🚦 信号的触发来源分类总结

来源 说明 示例
内核自动触发 程序运行中出现硬件/系统异常 SIGSEGV, SIGFPE, SIGILL, SIGBUS
用户主动发送 调用 kill(pid, sig)raise(sig)pthread_kill() SIGUSR1, SIGUSR2, SIGTERM
终端/控制台产生 用户键盘组合(Ctrl+C/Z/\)或 shell 控制 SIGINT, SIGTSTP, SIGQUIT, SIGHUP
进程关系触发 子进程结束、作业控制等 SIGCHLD, SIGCONT, SIGSTOP
定时器触发 alarm(), setitimer(), timer_create() SIGALRM, SIGVTALRM, SIGPROF
系统资源限制 超出 ulimit 或资源控制(rlimit) SIGXCPU, SIGXFSZ
文件/网络事件 异步 I/O、Socket 带外数据 SIGIO, SIGURG
系统事件 电源、窗口、系统调用错误 SIGPWR, SIGWINCH, SIGSYS

💡常见编程习惯

场景 常用信号 处理方式
进程优雅退出 SIGTERM, SIGINT 注册 handler 释放资源再退出
守护进程配置重载 SIGHUP handler 中重新加载配置文件
子进程退出回收 SIGCHLD handler 中 waitpid() 回收
防止管道崩溃 SIGPIPE signal(SIGPIPE, SIG_IGN);
定时任务 SIGALRM 在 handler 中执行周期任务
用户自定义控制 SIGUSR1, SIGUSR2 应用层自由定义行为

1.6 信号宏

含义 典型用法(示例) 备注
SIG_DFL 恢复 默认处理(Default action) signal(SIGTERM, SIG_DFL); POSIX 标准常用值,表示使用内核默认动作(如终止、忽略、core dump 等)
SIG_IGN 忽略 信号(Ignore) signal(SIGPIPE, SIG_IGN); POSIX 标准常用,接收到信号时被丢弃
SIG_ERR 错误返回标志(signal/sigaction 调用失败时返回) if (signal(SIGINT, h) == SIG_ERR) perror("signal"); POSIX 定义。不是 handler,不会被调用
SIG_HOLD 将信号放入“保持/阻塞”状态(历史/实现) (部分实现用于旧的 sigset API) 非 POSIX 标准,历史遗留(旧 System V 接口中出现)
SIG_GET (历史/实现) 表示“获取当前 handler” 仅在少数旧实现中见到 非标准;历史用途,几乎不用
SIG_SGE (历史/实现) 内部/扩展标志 非标准,厂商/历史遗留
SIG_ACK (历史/实现) 确认/应答相关 非标准,厂商/历史遗留
SIG_BLOCK sigprocmaskhow 参数:阻塞集合中的信号 sigprocmask(SIG_BLOCK, &set, NULL); POSIX 标准,用于临界区阻塞信号
SIG_UNBLOCK sigprocmaskhow 参数:解除阻塞 sigprocmask(SIG_UNBLOCK, &set, NULL); POSIX 标准
SIG_SETMASK sigprocmaskhow 参数:设置屏蔽字为集合 sigprocmask(SIG_SETMASK, &set, &old); POSIX 标准
SA_RESTART sigactionsa_flags:被信号打断的系统调用自动重启 act.sa_flags = SA_RESTART; POSIX 推荐选项,避免 EINTR 在部分系统调用中出现
SA_RESETHAND(或 SA_ONESHOT 处理一次后恢复默认动作(一次性 handler) `act.sa_flags = SA_RESETHAND;`
SA_SIGINFO 使 handler 使用 sa_sigaction(带更多信息) act.sa_flags = SA_SIGINFO; handler 原型为 void (*sa_sigaction)(int, siginfo_t*, void*)
SA_NODEFER(或 SA_NOMASK 在信号处理期间不自动屏蔽该信号 `act.sa_flags = SA_NODEFER;`
SA_NOCLDSTOP 对子进程停止/继续不发送 SIGCHLD act.sa_flags = SA_NOCLDSTOP; SIGCHLD 行为相关
SA_NOCLDWAIT 子进程退出时,不在父进程产生僵尸(内核自动回收) act.sa_flags = SA_NOCLDWAIT; 可避免 wait(),但在不同内核行为上需注意
SA_ONSTACK 在备用信号栈上执行 handler(配合 sigaltstack `act.sa_flags = SA_ONSTACK;`
ITIMER_REAL setitimer 的定时器类型:以真实时间(会产生 SIGALRM setitimer(ITIMER_REAL, &tv, NULL); POSIX/Unix 常用
ITIMER_VIRTUAL 以进程用户态 CPU 时间为基准(会产生 SIGVTALRM setitimer(ITIMER_VIRTUAL, ...) POSIX/Unix
ITIMER_PROF 包含系统 + 用户时间(会产生 SIGPROF setitimer(ITIMER_PROF, ...) POSIX/Unix
NSIG / SIGRTMIN / SIGRTMAX 信号数量或实时信号范围 for (i=1;i<NSIG;i++) ... 或 使用 SIGRTMIN SIGRT* 为 POSIX 实时信号(可携带数据)
SIGEV_SIGNAL / SIGEV_THREAD sigevent 的通知类型(定时器/异步IO) sev.sigev_notify = SIGEV_SIGNAL; 与 POSIX 定时器/异步事件相关(sigevent

额外说明(要点归纳)

  1. 必须区分“handler 值”与“控制/标志宏”

    • SIG_DFL/SIG_IGN/(历史)SIG_GET 等是作为 signal() 的第二个参数使用的“特殊值”,或作为 signal() 返回值(SIG_ERR)。

    • SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK 是传给 sigprocmask 的操作类型。

    • SA_* 系列是 sigaction 的标志位(sa_flags),用于控制处理行为。

  2. 现代编程实践

    • 优先使用 sigaction() + sigprocmask()(POSIX),而非老旧 signal()

    • 常用的宏你只需记住:SIG_DFLSIG_IGNSIG_ERR(与 signal() 相关),SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK(与 sigprocmask() 相关),以及常见 SA_ 标志(如 SA_RESTARTSA_SIGINFOSA_RESETHANDSA_NOCLDWAIT)。

  3. 历史兼容性

    • SIG_GETSIG_SGESIG_ACKSIG_HOLD 等多为 历史/厂商扩展,现代 POSIX 程序通常不使用,若需要移植性请用条件编译或查文档。

  4. 实时信号

    • POSIX 实时信号 (SIGRTMIN..SIGRTMAX) 支持队列化和附带数据,适用于需要较丰富异步通信的场景。

⚠️ 重要的例外

需要特别注意,有两个信号是​​无法被捕获、阻塞或忽略​​的终极武器,其余信号均可以重载,但是异常重载后如未终止程序,会在未定义的不稳定状态下运行:

  • SIGKILL(信号 9)

  • SIGSTOP(信号 19)


1.7 对比

特性 信号(Signal) 管道(Pipe) 消息队列 共享内存
通信内容 控制/事件 数据流 结构化消息 任意数据
是否异步 ✅ 是 ❌ 否 ❌ 否 ❌ 否
数据量 极少(仅类型) 中等
速度 快(仅事件) 快(零拷贝)
典型用途 通知 / 中断 / 进程控制 父子进程通信 异步消息交换 大数据共享

2 C语言实现

2.1 System V 程序实现

2.1.1 接口讲解

System V 信号接口非常简洁:

接口 作用 参数说明
kill(pid_t pid, int sig) 向进程或进程组发送信号 pid:目标进程ID;sig:信号编号

特点:System V 只提供发送信号的功能,不提供注册信号处理函数或阻塞信号的机制。


2.1.2 使用示例

System V 本身没有注册处理函数的标准接口,所以通常只是发送信号给其他进程:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int main() {
    pid_t target_pid;
    printf("请输入要发送信号的进程 PID: ");
    scanf("%d", &target_pid);

    // 向目标进程发送 SIGTERM 信号
    if (kill(target_pid, SIGTERM) == -1) {
        perror("kill");
        return 1;
    }

    printf("已向进程 %d 发送 SIGTERM 信号\n", target_pid);
    return 0;
}

System V 仅负责发送信号,不关心接收进程如何处理。


2.2 POSIX 程序实现

2.2.1 接口讲解

POSIX 信号接口更全面,支持注册处理函数、阻塞/等待信号以及定时器功能:

接口 作用 参数说明
kill(pid, sig) 向进程或进程组发送信号 同 System V
signal(signum, handler) 注册信号处理函数(旧接口) signum:信号编号;handler:函数指针,
sigaction(signum, &act, NULL) 注册信号处理函数(推荐接口) act:新处理行为;oldact:保存旧行为(可选)
sigprocmask(how, set, oldset) 阻塞/解除阻塞信号 howSIG_BLOCK/SIG_UNBLOCK/SIG_SETMASKset:信号集合
sigpending(set) 查询当前被阻塞的信号集合 set:信号集合
sigsuspend(mask) 原子挂起进程,等待未屏蔽信号 mask:临时屏蔽字
alarm(seconds) 设置定时器,到期发送 SIGALRM seconds:秒数
setitimer(which, &new_value, &old_value) 精细定时器接口,可微秒级 which:定时器类型;new_value:新定时值

2.2.2 使用示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号处理函数
void handle_sigusr1(int sig) {
    printf("收到 SIGUSR1 信号,sig=%d\n", sig);
}

int main() {
    struct sigaction sa;

    sa.sa_handler = handle_sigusr1;  // 注册处理函数
    sigemptyset(&sa.sa_mask);        // 处理信号时屏蔽的信号集合
    sa.sa_flags = 0;                 // 默认标志

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    printf("进程 PID=%d,等待 SIGUSR1 信号...\n", getpid());

    while (1) {
        pause();  // 阻塞等待信号
    }

    return 0;
}

POSIX 信号接口可以可靠注册处理函数,支持信号屏蔽和定时器,比 System V 更现代和稳健。


3 面试常见问题

1. 什么是信号?在C/C++中,信号的主要作用是什么?

信号是进程间通信的一种异步通知机制,用于告知进程某个特定事件已发生。主要作用包括:处理硬件异常(如段错误SIGSEGV)、响应终端指令(如Ctrl+C的SIGINT)、进程间简单通知、定时器超时处理等。

2. 信号作为一种IPC机制,最适合哪些场景?

信号适合异步通知和简单事件处理场景,不适用于需要传递大量数据或复杂同步的场景。典型应用包括:优雅终止进程、处理程序异常、定时任务提醒、子进程状态变化通知等。

3. 描述信号的生命周期

  1. ​产生​​:由硬件异常、终端指令、kill命令或软件条件触发

  2. ​注册​​:信号被添加到目标进程的待处理信号集中

  3. ​注销​​:信号被处理前从待处理集中移除

  4. ​处理​​:执行默认动作、忽略或调用用户自定义处理函数

4. 信号有哪几种基本的处理方式?

  • ​默认处理​​:执行系统预定义行为(终止、忽略、核心转储等)

  • ​忽略信号​​:明确指示系统忽略该信号(SIGKILL和SIGSTOP除外)

  • ​自定义处理​​:注册用户定义的信号处理函数

5. 如何捕获一个信号并执行自定义函数?

#include <signal.h>

void signal_handler(int sig) {
    // 处理逻辑
}

int main() {
    // 使用signal函数(简单但不推荐用于复杂程序)
    signal(SIGINT, signal_handler);
    
    // 或使用更健壮的sigaction
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
}

6. 哪些信号不能被捕获或忽略?

SIGKILL(信号9)和SIGSTOP(信号19)是终极控制信号,任何进程都无法捕获、阻塞或忽略,确保系统总能终止或暂停失控进程。

7. 信号处理函数中有哪些安全注意事项?

信号处理函数应:

  • 只调用​​异步信号安全​​函数(如write、_exit)

  • 避免使用非安全函数(如printf、malloc)

  • 执行时间尽量短,避免阻塞

  • 使用volatile sig_atomic_t类型保护共享变量

8. 什么是可靠信号与不可靠信号?

早期Unix信号(编号1-31)可能丢失,为不可靠信号;现代Linux通过实时信号(SIGRTMIN-SIGRTMAX)和sigqueue()提供可靠信号支持,保证信号不丢失、可排队、可携带额外数据。

9. 信号和信号量(Semaphore)有什么区别?

​信号​​是异步事件通知机制,用于进程间简单通知。​​信号量​​是同步原语,用于控制多进程/线程对共享资源的访问,本质是一个计数器配合等待队列。

10. 在多线程程序中使用信号需要注意什么?

  • 信号是发送给整个进程的,但可由特定线程处理

  • 使用pthread_sigmask设置线程信号掩码

  • 建议创建专用信号处理线程,使用sigwait同步处理信号

  • 避免在主工作线程中处理异步信号

11. 为什么说信号不适合作为线程间通信的主要方式?

线程间通信应使用更高效、可控的机制(互斥锁、条件变量、消息队列等)。信号的异步性会引入竞态条件,且无法精确控制哪个线程接收信号,增加了调试复杂度。

12. 如何模拟"信号量"的功能?

虽然C++标准库没有直接提供信号量,但可以通过mutex和condition_variable实现:

#include <mutex>
#include <condition_variable>

class CountingSemaphore {
    int count_;
    std::mutex mutex_;
    std::condition_variable cv_;
public:
    CountingSemaphore(int initial = 0) : count_(initial) {}
    
    void signal() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++count_;
        cv_.notify_one();
    }
    
    void wait() {
        std::unique_lock<std::mutex> lock(mutex_);
        cv_.wait(lock, [this]{ return count_ > 0; });
        --count_;
    }
};

13. 在信号处理函数中如何安全地修改一个全局标志?

#include <signal.h>
#include <stdatomic.h>

// C11方式(推荐)
_Atomic int graceful_shutdown = 0;

// 或传统方式
volatile sig_atomic_t shutdown_requested = 0;

void handler(int sig) {
    // 简单赋值是原子的
    graceful_shutdown = 1;
    // 或:shutdown_requested = 1;
}

14. 什么是信号掩码(信号集)?

信号掩码定义当前被阻塞(暂时不交付)的信号集合。使用sigset_t类型和以下函数操作:

sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);  // 添加SIGINT到集合
sigprocmask(SIG_BLOCK, &set, NULL);  // 阻塞这些信号

15. sigaction相比signal函数有哪些优势?

  • ​行为一致性​​:在各Unix系统上行为一致

  • ​更多控制​​:可指定信号处理时的行为标志

  • ​信号屏蔽​​:可设置处理函数执行期间自动阻塞的信号

  • ​额外信息​​:处理函数可获得更多信号相关信息

16. 如何让一个进程在后台运行时不受终端关闭的影响?

创建守护进程的关键步骤:

pid_t pid = fork();
if (pid > 0) exit(0);  // 父进程退出

setsid();  // 创建新会话,脱离终端
chdir("/");  // 改变工作目录
umask(0);   // 重设文件权限掩码

// 关闭标准文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

17. 如果信号处理函数执行时间过长,可能会导致什么问题?

  • 延迟对其他信号的响应

  • 可能错过重要的时序要求

  • 同种信号会被自动阻塞,导致信号丢失

  • 可能影响程序整体响应性能

18. 除了kill命令,还有哪些方式可以给进程发送信号?

  • ​终端按键​​:Ctrl+C(SIGINT)、Ctrl+Z(SIGTSTP)、Ctrl+(SIGQUIT)

  • ​硬件异常​​:段错误(SIGSEGV)、除零(SIGFPE)、非法指令(SIGILL)

  • ​软件条件​​:alarm(SIGALRM)、子进程退出(SIGCHLD)

  • ​系统调用​​:kill()、sigqueue()、pthread_kill()

19. 在C++的RAII风格代码中,信号可能带来什么挑战?

信号可能中断正在执行的代码,如果在资源持有期间(如锁未释放、文件未关闭)发生信号,并在处理函数中尝试操作相同资源,可能导致:

  • 死锁(如重复加锁)

  • 资源泄漏

  • 状态不一致

20. 信号处理函数执行期间,如果再次收到同一信号会怎样?

默认情况下,信号处理函数执行期间,同种信号会被自动加入进程信号掩码(阻塞)。这意味着:

  • 不会发生嵌套调用

  • 后续信号会被标记为未决,待当前处理完成后才交付

  • 可使用sa_flags的SA_NODEFER标志改变此行为


Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐