Shell程序信号处理代码Bug排查技术求助
Got it, let's break down what might be going wrong with your signal handling code here. I've worked through similar headaches when building custom shells, so let's walk through this step by step.
核心问题分析
First, let's clarify what your code is trying to do: it looks like you're blocking SIGINT (Ctrl+C) and SIGTSTP (Ctrl+Z) before forking a child, then trying to restore the parent's signal mask afterward. But there are a few critical missteps here that are likely causing your bugs.
可能的问题点与排查方向
子进程继承了阻塞的信号掩码,exec后无法响应终端信号
When you callsigprocmask(SIG_BLOCK, ...)beforefork(), the child process inherits this exact signal mask. The problem?execdoesn't reset signal masks—so after your child runsexec, it'll still haveSIGINTandSIGTSTPblocked. That means users can't stop or pause the child process with keyboard shortcuts, which is a huge issue for a shell.- 排查方法: Test if your child process ignores Ctrl+C. If yes, this is definitely the problem. You can also write a small helper function to print the
sigset_tmask in the child beforeexecto confirm the blocked signals.
- 排查方法: Test if your child process ignores Ctrl+C. If yes, this is definitely the problem. You can also write a small helper function to print the
父进程的信号掩码恢复逻辑冗余且易引发竞态
Your parent process code does:sigprocmask(SIG_UNBLOCK, &new, &old); sigprocmask(SIG_SETMASK, &old, NULL);This is unnecessary—
SIG_SETMASKwitholdalready restores the original mask (sinceoldwas saved before you blocked the signals). Doing an unblock first creates a tiny window where signals could be delivered unexpectedly, which might cause race conditions if your parent is handling job control or other signal-sensitive logic.- 排查方法: Check if you're running these lines before or after
fork(). If it's beforefork(), it's completely useless (you're blocking then immediately restoring, wasting cycles). If it's after, just remove theSIG_UNBLOCKline—SIG_SETMASKalone is enough.
- 排查方法: Check if you're running these lines before or after
缺少子进程的信号掩码恢复步骤
Even if you block signals in the parent to avoid race conditions duringfork, the child must reset its signal mask before callingexec. Otherwise, as I mentioned earlier, the child will be deaf to keyboard signals.
修正后的示例代码
Here's how to adjust your code to fix these issues:
sigset_t new, old; sigemptyset(&new); sigaddset(&new, SIGINT); sigaddset(&new, SIGTSTP); // Block signals BEFORE forking to prevent race conditions during process creation sigprocmask(SIG_BLOCK, &new, &old); pid_t pid = fork(); if (pid == 0) { // Child process: Restore original signal mask so exec'd program can respond to signals sigprocmask(SIG_SETMASK, &old, NULL); // Do any pre-exec setup here (I/O redirection, etc.) execvp("your_command", args); // Replace with your actual exec call // If exec fails, clean up and exit perror("exec failed"); exit(EXIT_FAILURE); } else if (pid > 0) { // Parent process: Restore original signal mask immediately after forking sigprocmask(SIG_SETMASK, &old, NULL); // Handle parent logic here (wait for child, job control, etc.) int status; waitpid(pid, &status, 0); } else { // Fork failed: Don't forget to restore the signal mask! perror("fork failed"); sigprocmask(SIG_SETMASK, &old, NULL); }
额外排查 Tips
- Use
sigpending(&pending_set)in parent/child to check if any signals are pending (could help diagnose if signals are being blocked unexpectedly). - If you have custom signal handlers in the parent, note that
execwill reset them to default behavior (which is usually what you want for child processes).
内容的提问来源于stack exchange,提问作者CodeOnce

