ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

信号继承陷阱:子进程与线程中 fork 后信号处理的 5 个坑(完整指南)

信号继承陷阱:子进程与线程中 fork 后信号处理的 5 个坑(完整指南) 信号继承陷阱子进程与线程中 fork 后信号处理的 5 个坑完整指南【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebookCoursebook是伊利诺伊大学开源的入门级系统编程教材其中的《Signals》一章系统讲解了 Linux 信号在 fork 子进程和线程场景下的继承规则。本文将用通俗的方式拆解这些规则帮你避开新手最常踩的 5 个信号继承陷阱。先搞懂一个信号的一生信号Signal可以理解为操作系统发来的软件中断——它能在程序执行到一半时打断程序让它跳转去执行处理函数。理解陷阱之前先记住信号的四阶段生命周期见上图Generated生成信号被产生比如你按下了 Ctrl-C或内核检测到非法内存访问Pending挂起信号已发出、等待内核处理Blocked阻塞被信号掩码拦截停在挂起状态直到解阻塞Delivered投递内核打断某个线程跳转执行处理函数再区分两个容易混淆的概念来源signals.tex概念作用常用接口信号处理策略Disposition决定信号投递后做什么终止/忽略/执行自定义函数sigaction信号掩码Mask决定信号能否被投递sigprocmask/pthread_sigmask⚠️ 例外SIGKILL和SIGSTOP永远无法被捕获、阻塞或忽略。陷阱一子进程继承的信号其实有一半是空的fork()之后子进程会复制父进程的两样东西信号处理策略和信号掩码来源processes.tex✅继承父进程注册的 SIGINT 处理函数子进程也有——如果父进程 fork 前装了处理器子进程收到 SIGINT 同样会跳转执行✅继承父进程中处于阻塞状态的信号在子进程里也保持阻塞❌不继承父进程挂起中pending的信号子进程统统拿不到 新手最大的误判以为父进程没来得及处理的信号会传给子进程。实际上挂起信号不会跨 fork 传递子进程必须等新信号。另外子进程与父进程的关键差异还包括getpid()不同、子进程结束时父进程会收到SIGCHLD反过来不会、子进程不继承定时器闹钟。陷阱二exec 之后处理器全部被重置子进程调用exec换上新程序后继承关系会发生剧变信号掩码保留挂起的信号也保留自定义信号处理器全部重置为默认动作原因很直白旧的处理器函数属于旧进程映像exec之后那段代码可能已经被覆盖或消失继续跳过去就是灾难。所以如果你想在forkexec的管道里保留某个处理逻辑必须在新程序的代码里重新注册推荐用sigaction它在不同系统上语义一致老式的signal在不同操作系统行为不同属于已废弃的写法。陷阱三多线程里掩码是每人一份的这是进程和线程最大的不对称点来源signals.tex掩码Mask每个线程各有一份。新线程继承创建它的那个线程的掩码副本处理策略Disposition整个进程只有一份。任何线程调用sigaction设置的都是全进程共用的处理器由此产生两个连锁陷阱哪个线程会被打断是随机的。只要有一个线程没阻塞该信号内核就可能打断它——哪怕你以为是主线程在处理逻辑只想在特定线程处理信号正确做法是主线程在pthread_create之前用pthread_sigmask把信号阻塞掉让新线程继承这份掩码再开一个专门的信号线程用sigwait同步地逐个捕获。这样还能安全地使用printf等普通库函数而不受信号处理函数内不能调用非异步信号安全函数的限制// 主线程先阻塞 SIGINT/SIGTERM新线程会继承这份掩码 pthread_sigmask(SIG_BLOCK, set, NULL); pthread_create(tid, NULL, signal_thread, NULL); // signal_thread 内部再调用 sigwait(set, sig) 同步等待pthread_kill发给某线程的 SIGKILL会杀死整个进程——因为 SIGKILL 无法被捕获内核直接终结的是进程映像而不是单个线程。陷阱四信号处理器里写复杂逻辑fork/线程继承的只是入口处理器里的代码执行环境依然受严格约束多数库函数是**异步信号不安全async-signal-unsafe**的——比如程序正被malloc内部打断时处理器里再调printf它内部也用 malloc就是未定义行为。 新手黄金模式处理器里只置一个标志位主循环负责善后volatile sig_atomic_t pleaseStop; // 类型必须用 sig_atomic_t void handle_sigint(int sig) { pleaseStop 1; }注意必须用volatile sig_atomic_t而不是普通int前者防止编译器把循环条件优化掉、也保证每次从内存重新读取最新值。新手避坑清单 #陷阱正确姿势1以为挂起信号会传给子进程pending 信号不跨 fork 继承2exec 后期待旧处理器还活着处理器会被重置需重新注册3建线程后才阻塞信号主线程先pthread_sigmask再pthread_create4处理器里调 printf / malloc 等只置volatile sig_atomic_t标志位5对某线程pthread_kill(tid, SIGKILL)会杀掉整个进程慎用延伸阅读 信号全章生命周期、sigaction、sigwait、线程场景signals/signals.tex fork 的 POSIX 细节与 SIGCHLD 异步等待processes/processes.tex 线程与竞态的图示讲解threads/threads.tex 教材构建与部署脚本_scripts/掌握掩码每人一份、策略全进程共享、pending 不跨 fork、exec 重置处理器这四句话就能避开绝大多数信号继承问题。【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表