资讯动态

Linux信号机制深度解析:从基础概念到实战避坑指南

发布时间:2026/8/7 3:44:23 来源:尧图企业网站定制
1. 项目概述深入理解Linux信号机制在Linux系统编程的世界里信号Signal是一个既基础又强大的概念。它就像是操作系统内核与应用进程之间的一种即时通讯机制允许内核在特定事件发生时打断进程的正常执行流通知它“有情况发生”。无论是你按下CtrlC试图终止一个前台程序还是程序自身发生了除零错误亦或是子进程结束需要通知父进程背后都是信号在默默工作。对于任何希望深入Linux系统底层、编写健壮后台服务或高性能应用的开发者而言透彻理解信号机制是绕不开的一课。这不仅仅是知道有哪些信号更要理解信号的产生、传递、阻塞、捕获和处理的全过程以及其中潜藏的“坑”。很多看似诡异的进程行为比如服务突然退出、日志文件损坏、或者资源清理不彻底追根溯源往往都与信号处理不当有关。接下来我将结合十多年的系统开发经验为你拆解Linux信号的方方面面从基础概念到高级应用再到那些手册上不会写的实战避坑指南。2. 信号基础核心概念与工作机制2.1 信号是什么从生活场景到技术抽象你可以把信号想象成办公室里的紧急广播。你正在专心写代码进程正常执行突然火警铃响了信号产生。这个铃声对所有在办公室里的人都一样标准信号但你进程可以选择不同的应对方式立刻放下手头工作跑出去执行默认动作或者先保存一下文件再跑捕获信号并执行自定义处理函数甚至戴上降噪耳机假装没听见忽略信号。Linux信号机制的核心思想与此类似它是一种异步通知机制用于通知进程发生了某种事件。每个信号都有一个唯一的整数编号从1开始和一个宏定义名称如SIGINT、SIGTERM。使用kill -l命令可以查看系统支持的所有信号列表。信号大致可以分为几类终止类信号如SIGTERM15礼貌的终止请求、SIGKILL9强制立即终止不可捕获或忽略、SIGINT2终端中断字符通常是CtrlC。错误类信号如SIGSEGV11非法内存访问、SIGFPE8算术运算错误如除零。作业控制信号如SIGSTOP19暂停进程不可捕获或忽略、SIGCONT18继续执行已暂停的进程。用户自定义信号SIGUSR110和SIGUSR212进程可以自由定义其用途常用于进程间通信或控制。注意SIGKILL和SIGSTOP是两个特权信号进程无法为其设置信号处理函数来捕获或忽略。这是操作系统为了保证管理员始终有能力控制进程而设计的“后门”。2.2 信号的产生、传递与处理流程理解信号的完整生命周期至关重要这能帮你诊断很多复杂问题。产生Generation信号由某个事件产生。产生者可以是内核如硬件异常、定时器到期、其他进程通过kill()系统调用或进程自身通过raise()或abort()。传递Delivery内核将产生的信号传递给目标进程。在传递前信号处于“未决”Pending状态。内核会更新目标进程的进程控制块PCB中的一个数据结构——信号位图pending signal set将对应信号的位设为1。处理Handling当目标进程被调度执行并从内核态返回用户态之前内核会检查其未决信号集。如果发现有待处理的信号且该信号未被阻塞Block内核就会安排进程处理该信号。处理方式有三种执行默认动作Default每个信号都有一个系统预设的动作可能是终止进程Term、终止并生成核心转储Core、忽略Ign或暂停进程Stop。捕获Catch进程提前通过sigaction()等系统调用注册了一个信号处理函数handler。当信号到来时内核会临时中断进程当前的执行流跳转到这个处理函数去执行。执行完毕后再返回到被中断的代码处继续执行除非处理函数中调用了exit或longjmp。忽略Ignore进程明确告诉内核当这个信号到来时直接丢弃它不做任何处理。但SIGKILL和SIGSTOP不能被忽略。这里有一个关键细节信号的传递是异步的。它可能发生在进程执行的任何时刻。这就带来了可重入reentrancy和异步信号安全async-signal-safe的问题我们后面会详细讨论。2.3 信号的阻塞Block与未决Pending这是信号机制中一个容易混淆但非常重要的概念。阻塞Block有时也叫屏蔽是进程主动设置的一个过滤器。当一个信号被阻塞后即使它产生了内核也不会立即将其传递给进程进行处理而是让其保持在“未决”状态。直到进程解除了对该信号的阻塞这个未决的信号才会被递送。你可以通过sigprocmask()系统调用来设置或修改进程的信号掩码signal mask从而阻塞或解除阻塞一组信号。这个功能非常有用例如保护临界区在更新一个复杂的数据结构如链表、哈希表时你不希望被信号处理函数打断否则可能导致数据结构处于不一致状态。这时你可以先阻塞相关信号完成更新后再解除阻塞。防止信号丢失标准的信号编号1-31是不排队的。如果在信号被阻塞期间同一个信号产生了多次在解除阻塞后进程通常只会收到一次。但实时信号编号34-64支持排队这是后话。未决信号集就是记录那些已经产生但尚未递送可能是因为被阻塞也可能是还没来得及处理的信号。你可以通过sigpending()系统调用读取当前的未决信号集。3. 核心API详解从signal到sigaction3.1 古老的signal()函数及其缺陷很多教材入门时用的是signal()函数它的原型很简单#include signal.h typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);用法看起来也很直观void my_handler(int sig) { write(STDOUT_FILENO, Got SIGINT!\n, 12); } int main() { signal(SIGINT, my_handler); while(1) pause(); // 等待信号 return 0; }然而在实际生产代码中几乎不应该使用signal()函数。这是因为它存在历史遗留的、不可移植的行为问题信号处理重置在某些古老的Unix系统实现中当信号处理函数被调用一次后该信号的处理方式会被自动重置为默认动作SIG_DFL。这意味着如果你连续快速按两次CtrlC第二次就会直接终止程序而不是再次调用你的处理函数。虽然现代Linux默认情况下glibc的signal()包装使用了SA_RESTART等标志来避免此问题即模拟sigaction的某些行为但为了可移植性和明确性不应依赖于此。控制力弱signal()无法在注册处理函数的同时指定其他关键行为比如是否自动重启被信号中断的系统调用也无法方便地设置信号掩码来阻塞其他信号。3.2 现代标准sigaction()函数sigaction()是设置信号处理行为的现代、可靠且功能全面的接口。它让你能精确控制信号处理的所有方面。#include signal.h int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);核心在于struct sigaction这个结构体struct sigaction { void (*sa_handler)(int); // 简单的处理函数指针类似signal() void (*sa_sigaction)(int, siginfo_t *, void *); // 更强大的处理函数指针 sigset_t sa_mask; // 在执行处理函数期间需要额外阻塞的信号集 int sa_flags; // 控制函数行为的标志位 void (*sa_restorer)(void); // 已废弃不要使用 };关键参数解析sa_handler和sa_sigaction这两个字段共用一块内存通过sa_flags中的SA_SIGINFO标志位来决定使用哪一个。如果设置了SA_SIGINFO则使用sa_sigaction它能够接收更多关于信号来源的信息通过siginfo_t结构体例如发送信号的进程PID、引发信号的地址等。否则使用sa_handler。sa_mask这是一个信号集。当你的信号处理函数被调用时除了当前正在处理的这个信号本身会被自动加入进程的信号掩码即被阻塞防止处理函数被同一信号重入你还可以通过sa_mask指定需要额外阻塞的信号。这非常重要可以防止在处理一个信号时被另一个敏感信号打断。sa_flags这是一系列标志位的组合深刻影响信号处理行为。最常用的几个是SA_RESTART如果系统调用如read,write,accept被这个信号中断那么内核会自动重启该系统调用而不是让它返回错误EINTR。这对于服务器程序保持健壮性非常有用。但请注意并非所有系统调用都是可重启的例如poll,epoll_wait,sleep系列通常不受此标志影响。SA_SIGINFO使用sa_sigaction而非sa_handler作为处理函数。SA_NODEFER或SA_NOMASK通常情况下当处理函数执行时内核会自动将当前信号加入阻塞集防止处理函数被同一个信号嵌套调用。设置此标志将禁用这一行为。除非你非常清楚在做什么否则不要设置这个标志它很容易导致处理函数递归调用直至栈溢出。SA_RESETHAND模拟老式signal()行为在处理函数执行一次后将信号动作重置为默认。同样不推荐使用。一个健壮的sigaction使用示例#include stdio.h #include stdlib.h #include signal.h #include unistd.h #include string.h void sigint_handler(int sig, siginfo_t *info, void *ucontext) { const char *msg Caught SIGINT (sent by PID: %d)\n; // 使用write是安全的printf不安全 char buf[64]; snprintf(buf, sizeof(buf), msg, info-si_pid); write(STDOUT_FILENO, buf, strlen(buf)); } int main() { struct sigaction sa; memset(sa, 0, sizeof(sa)); sigemptyset(sa.sa_mask); // 清空阻塞信号集 // 在处理SIGINT时我们希望也阻塞SIGTERM防止两个信号处理逻辑冲突 sigaddset(sa.sa_mask, SIGTERM); sa.sa_sigaction sigint_handler; // 使用能获取更多信息的处理函数 sa.sa_flags SA_SIGINFO | SA_RESTART; // 获取额外信息并自动重启被中断的系统调用 if (sigaction(SIGINT, sa, NULL) -1) { perror(sigaction); exit(EXIT_FAILURE); } printf(PID: %d. Try pressing CtrlC or send SIGINT from another terminal.\n, getpid()); // 一个可能被中断的循环读取 char buffer[256]; ssize_t n; while(1) { n read(STDIN_FILENO, buffer, sizeof(buffer)-1); // 如果被SIGINT中断SA_RESTART会使其重启 if (n 0) { buffer[n] \0; printf(Read: %s, buffer); } else if (n 0) { printf(EOF reached.\n); break; } else { // 如果read返回-1且errno不是EINTR说明是其他错误 if (errno ! EINTR) { perror(read); break; } // 如果是EINTR且没有SA_RESTART循环会继续read会再次被调用 // 如果有SA_RESTART则根本不会走到这里 } } return 0; }3.3 信号集操作函数族为了设置sa_mask或使用sigprocmask()你需要操作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);- 测试一个信号是否在集合中。一个关键的心得总是先用sigemptyset()或sigfillset()初始化信号集然后再进行添加或删除操作。未初始化的sigset_t变量内容是不确定的直接对其进行操作会导致不可预知的行为。4. 高级话题与实战陷阱4.1 异步信号安全Async-Signal-Safe函数这是信号处理编程中最容易踩坑的地方。信号处理函数是在主程序执行的任意时刻被异步调用的此时程序可能正处在某个库函数如malloc的内部持有着该库函数内部的全局锁或处于不一致状态。如果你在信号处理函数中调用了另一个同样需要这个锁的函数就可能导致死锁或数据损坏。因此在信号处理函数中你只能调用那些被明确列为“异步信号安全”async-signal-safe的函数。POSIX标准定义了一个这样的函数列表可以通过man 7 signal-safety查看。常见的安全函数包括write用于向文件描述符写入_exit注意不是exitexit会执行清理工作不安全sigactionkill部分简单的字符串函数如strlen但strcpy/sprintf等可能不安全如果它们内部使用了动态内存绝对禁止在信号处理函数中调用printf,sprintf它们内部会操作stdio的缓冲区这些缓冲区是全局的非可重入。malloc,free堆管理器的全局状态可能被破坏。任何可能修改全局数据结构或使用静态缓冲区的库函数。实战技巧信号处理函数的设计原则是“快进快出”。它应该只做最小化的工作通常是设置一个全局的volatile sig_atomic_t类型的标志位。主循环定期检查这个标志位并在安全的环境下执行实际的逻辑。volatile关键字防止编译器优化掉对该变量的读写sig_atomic_t保证对该变量的读写是原子的在信号处理上下文中安全。#include signal.h #include unistd.h #include stdbool.h volatile sig_atomic_t g_got_signal 0; void handler(int sig) { g_got_signal 1; // 只做这一件事 } int main() { // ... 设置信号处理 ... while (!done) { // 主循环工作 some_work(); // 安全地检查信号标志 if (g_got_signal) { g_got_signal 0; handle_signal_safely(); // 在非异步上下文中安全处理 } // 或者使用 pause, sigwait 等同步方式等待信号 } }4.2 系统调用中断与重启当一个进程正在执行一个“慢”系统调用如read等待终端输入、accept等待网络连接、wait等待子进程时如果收到一个信号并且该信号的处理函数被调用那么这个系统调用通常会失败返回并设置errno为EINTRInterrupted system call。处理EINTR是编写健壮网络服务或任何可能阻塞的程序的必备技能。有两种主流方式手动重启循环这是最明确、兼容性最好的方式。int ret; do { ret read(fd, buf, sizeof(buf)); } while (ret -1 errno EINTR); if (ret -1) { // 处理其他错误 }使用SA_RESTART标志如前所述通过sigaction设置SA_RESTART可以让内核自动重启被中断的系统调用。这非常方便但你必须清楚知道哪些系统调用支持重启。一个常见的经验法则是与文件描述符包括socket操作相关的系统调用如read,write,send,recv,accept,connect通常支持SA_RESTART而与等待事件相关的如sleep,poll,select,epoll_wait,msgrcv,sem_wait通常不支持或者行为因信号而异。重要提示对于多线程程序SA_RESTART标志的行为是作用于整个进程的但信号是传递给特定线程的如果是硬件异常产生的信号如SIGSEGV则发送给引发异常的线程如果是通过kill()或tgkill()发送的则可以指定线程。在多线程环境中处理信号需要格外小心通常建议将所有信号的处理集中到一个专用线程该线程使用sigwait()或sigwaitinfo()同步地等待并处理信号从而完全避免异步信号处理函数的复杂性。4.3 实时信号Real-time Signals与信号排队标准信号SIGUSR1之前的信号有一个重大缺陷它们不排队。如果在信号被阻塞或处理期间同一个标准信号产生了多次解除阻塞后进程通常只会收到一次。这会导致信号丢失。实时信号POSIX.1b定义通常从SIGRTMIN34到SIGRTMAX64解决了这个问题排队多个相同的实时信号可以排队等待递送。携带额外数据通过sigqueue()发送实时信号时可以附带一个整型值或一个指针。优先级编号小的实时信号比编号大的优先递送。使用实时信号需要使用sigaction()注册处理函数并设置SA_SIGINFO标志以接收附加数据。使用sigqueue(pid, sig, value)发送信号和值。在处理函数sa_sigaction中通过siginfo_t结构体的si_value字段或si_ptr字段获取发送来的值。实时信号适用于需要可靠、有序传递简单消息的进程间通信场景但其复杂性和性能开销使其并不如管道、消息队列或socket常用。5. 典型应用场景与代码实战5.1 优雅终止Graceful Shutdown这是信号最经典的应用。一个后台服务如Web服务器、数据库在收到SIGTERM或SIGINT时不应该立刻_exit而应该停止接受新的连接或请求。完成正在处理的请求。关闭文件描述符释放资源。刷新日志。然后退出。实现的关键在于信号处理函数只设置退出标志主循环检测到标志后开始有序关闭流程。#include signal.h #include stdbool.h #include stdio.h #include unistd.h #include errno.h static volatile sig_atomic_t g_shutdown_requested 0; static void handle_shutdown_signal(int sig) { g_shutdown_requested 1; } int setup_signal_handlers() { struct sigaction sa; sa.sa_handler handle_shutdown_signal; sigemptyset(sa.sa_mask); sa.sa_flags 0; // 不设置SA_RESTART我们希望某些阻塞调用能被中断 if (sigaction(SIGTERM, sa, NULL) -1) return -1; if (sigaction(SIGINT, sa, NULL) -1) return -1; // 通常忽略SIGPIPE由write的返回值来处理 Broken pipe sa.sa_handler SIG_IGN; if (sigaction(SIGPIPE, sa, NULL) -1) return -1; return 0; } int main() { if (setup_signal_handlers() ! 0) { perror(Failed to setup signal handlers); return 1; } // 模拟服务器主循环 while (!g_shutdown_requested) { printf(Working...\n); // 这里可能是 accept(), epoll_wait() 等 // 为了演示我们用 sleep。注意 sleep 会被信号中断返回剩余秒数。 unsigned int remaining sleep(5); if (remaining ! 0) { // sleep 被信号中断了 printf(sleep was interrupted, remaining %u seconds\n, remaining); // 继续循环检查 g_shutdown_requested } } printf(Shutdown signal received. Starting graceful shutdown...\n); // 执行清理工作关闭监听socket通知工作线程等待任务完成写关闭日志等 sleep(2); // 模拟清理时间 printf(Cleanup done. Exiting.\n); return 0; }5.2 定时器与SIGALRM虽然现代高精度定时器如timerfd更受欢迎但使用alarm()函数和SIGALRM信号实现简单的单次定时器仍然是一种经典模式。alarm(seconds)函数会在指定秒数后向进程发送一个SIGALRM信号。一个常见的用途是为可能阻塞的操作设置超时例如从终端读取密码。#include stdio.h #include signal.h #include unistd.h #include string.h #include errno.h volatile sig_atomic_t g_timeout 0; void alarm_handler(int sig) { g_timeout 1; } int main() { char buf[100]; struct sigaction sa; sa.sa_handler alarm_handler; sigemptyset(sa.sa_mask); sa.sa_flags 0; sigaction(SIGALRM, sa, NULL); printf(You have 5 seconds to enter your name: ); fflush(stdout); alarm(5); // 设置5秒定时器 if (fgets(buf, sizeof(buf), stdin) ! NULL) { alarm(0); // 取消定时器 buf[strcspn(buf, \n)] 0; // 去掉换行符 printf(Hello, %s!\n, buf); } else { // fgets 返回 NULL if (g_timeout) { printf(\nTimeout! Too slow.\n); } else if (feof(stdin)) { printf(\nEOF reached.\n); } else { perror(fgets); } } return 0; }注意alarm()定时器是进程全局的任何一次新的alarm()调用都会取消之前的定时器。在多线程或复杂逻辑中使用时需要仔细协调。对于更复杂的周期性定时或多定时器需求建议使用setitimer也使用信号但精度更高或timer_createtimer_settime可以指定将信号发送到哪个线程或者完全使用非信号的timerfd机制后者可以像文件描述符一样被epoll监控集成到事件循环中更加方便。5.3 多线程环境下的信号处理多线程中信号的处理变得更加棘手。信号可以发送给整个进程kill(pid, sig)或特定线程pthread_kill(tid, sig)。但信号的处理动作由sigaction设置是进程级别共享的。此外每个线程有自己独立的信号掩码。最佳实践将信号处理线程化最清晰、最不容易出错的方式是在主线程或某个专用线程中阻塞所有需要处理的信号然后创建一个单独的“信号处理线程”该线程使用sigwait()或sigwaitinfo()同步地等待这些信号。这样信号处理就变成了一个普通的同步函数调用完全避免了异步处理函数带来的所有复杂性和限制。#include pthread.h #include signal.h #include stdio.h #include unistd.h static void *signal_thread_func(void *arg) { sigset_t *set (sigset_t *)arg; int sig; int ret; for (;;) { ret sigwait(set, sig); if (ret ! 0) { // 处理 sigwait 错误 continue; } printf(Signal handling thread caught signal: %d\n, sig); switch (sig) { case SIGINT: case SIGTERM: printf(Shutdown requested.\n); // 设置全局标志通知其他线程退出 // 或者直接调用 exit注意线程安全 // 这里简单退出线程 pthread_exit(NULL); break; case SIGUSR1: printf(Do something for SIGUSR1.\n); break; default: printf(Unexpected signal.\n); } } return NULL; } int main() { pthread_t sig_thr; sigset_t sigset; // 在主线程中阻塞我们关心的信号 sigemptyset(sigset); sigaddset(sigset, SIGINT); sigaddset(sigset, SIGTERM); sigaddset(sigset, SIGUSR1); pthread_sigmask(SIG_BLOCK, sigset, NULL); // 创建信号处理线程该线程会继承主线程的信号掩码 if (pthread_create(sig_thr, NULL, signal_thread_func, (void *)sigset) ! 0) { perror(pthread_create); return 1; } printf(Main thread PID: %d, TID: %lu\n, getpid(), pthread_self()); printf(Send SIGUSR1 or SIGINT to this process.\n); // 主线程做其他工作 while (1) { printf(Main thread is working...\n); sleep(3); } pthread_join(sig_thr, NULL); return 0; }这种方法将异步事件同步化使得信号处理逻辑可以像普通代码一样使用任何函数无需担心异步信号安全大大降低了编程复杂度。6. 常见问题排查与调试技巧6.1 程序不响应CtrlCSIGINT这是新手常遇到的问题。可能的原因有进程处于停止Stopped状态如果进程收到了SIGSTOP或SIGTSTPCtrlZ它会进入停止状态此时不执行任何代码包括信号处理函数。需要用SIGCONT信号唤醒它例如在shell中用fg或bg命令或kill -CONT pid。信号被阻塞或忽略检查代码中是否调用了sigprocmask或pthread_sigmask阻塞了SIGINT或者是否将SIGINT的处理方式设置成了SIG_IGN。前台/后台进程组问题在终端中CtrlC产生的SIGINT会发送给前台进程组的所有进程。如果你的程序通过fork()创建了子进程并且没有正确地设置进程组例如在setsid()后可能导致信号发送给了错误的进程组。使用ps -ejf查看进程的PID和PGID。自定义处理函数陷入死循环或阻塞如果你的SIGINT处理函数逻辑有bug比如死循环或调用了一个阻塞的系统调用那么进程会一直卡在处理函数里无法返回主程序。调试方法使用strace跟踪系统调用。运行strace -f -e tracesignal -p pid可以查看进程接收和处理信号的情况。6.2 核心转储Core Dump文件不生成当程序收到SIGSEGV、SIGABRT等信号时默认动作是终止并生成核心转储文件core dump用于事后调试。但有时会发现没有生成core文件。资源限制使用ulimit -c命令查看当前shell的核心文件大小限制。如果显示为0则不会生成。使用ulimit -c unlimited设置为无限制或在代码中调用setrlimit。文件系统权限或路径进程可能没有在当前目录写入的权限。核心文件的生成路径和命名模式由/proc/sys/kernel/core_pattern控制。可以使用sysctl kernel.core_pattern查看例如/var/crash/core.%e.%p。确保目标目录存在且进程有写权限。文件系统已满。进程的dumpable属性被禁用通过prctl(PR_SET_DUMPABLE, 0)或更改了用户ID/组ID而没有正确处理可能导致进程被标记为不可转储。检查/proc/pid/status中的Dumpable字段。6.3 信号导致系统调用返回EINTR的处理遗漏这是导致网络服务、数据库等长时间运行程序出现偶发性错误或性能问题的常见原因。如果你的程序在循环中调用read,write,accept,connect等必须检查errno是否为EINTR如果是则需要重试。// 不安全的写法 int n read(fd, buf, size); if (n -1) { perror(read failed); // 如果是因为信号中断这里会错误地打印失败 // ... } // 安全的写法手动重启 ssize_t n; do { n read(fd, buf, size); } while (n -1 errno EINTR); if (n -1) { // 此时才是真正的错误 perror(read); }或者如前所述为相关信号设置SA_RESTART标志让内核替你重启这些系统调用。但务必清楚哪些调用支持重启。6.4 使用信号进行进程间同步的竞态条件一个经典的错误模式是父进程fork()子进程父进程希望等待子进程准备就绪例如子进程创建了socket并开始监听后再继续。有人可能会尝试用信号如SIGUSR1来通知。// 父进程 signal(SIGUSR1, handler_ready); pid fork(); if (pid 0) { /* child */ setup_and_signal_parent(); } pause(); // 等待子进程信号 // 子进程 setup_server(); kill(getppid(), SIGUSR1);这里存在一个竞态条件如果子进程在父进程调用pause()之前就发送了信号那么信号可能会丢失因为标准信号不排队父进程将永远挂起在pause()上。解决方案使用更可靠的进程间同步机制如管道pipe、socketpair、FIFO或System V/POSIX信号量。在fork()之前创建管道子进程准备好后向管道写入一个字节父进程从管道读取read会自然阻塞直到有数据。这完全避免了信号异步性带来的时序问题。信号是Linux/Unix系统编程中一个深邃而精妙的领域。它强大但危险理解其原理和陷阱是写出稳定、可靠系统软件的基石。我的经验是在非必要的情况下尽量使用更高级的、同步的IPC机制当必须使用信号时遵循“处理函数尽可能简单、只设置标志位”的原则并在主循环中同步处理对于多线程程序强烈推荐使用专用线程配合sigwait的同步处理模型。把这些点把握好你就能驯服这头“异步猛兽”让它为你的程序服务而不是带来深夜调试的噩梦。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价