Big Tech C++ Interview: Deep Dive into Linux Signal Mechanism
This article explains Linux signal fundamentals—including generation, registration, dispatch, masking, and handling—covers standard and real‑time signals, common pitfalls like non‑reentrancy and EINTR, and provides concrete C/C++ code examples for each scenario.
1. Linux Signal Mechanism Overview
Signals are asynchronous notifications sent by the kernel or other processes to a target process, used for process control, exception handling, timeout monitoring, and graceful shutdown. Most developers only call registration functions without fully understanding the kernel‑side flow, leading to gaps in interview answers.
2. Signal Generation Methods
2.1 User‑initiated signals
Key terminal shortcuts generate signals:
Ctrl+C → SIGINT (interrupts the running program).
Ctrl+\ → SIGQUIT (terminates and may produce a core dump).
Ctrl+Z → SIGTSTP (suspends the foreground process, resumable with fg).
Simple test program:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("程序运行中,可测试:
");
printf("1. Ctrl+C 触发 SIGINT
");
printf("2. Ctrl+Z 触发 SIGTSTP
");
printf("3. Ctrl+\\ 触发 SIGQUIT
");
while (1) {
sleep(1);
}
return 0;
}2.2 Programmatic generation
Four primary functions can raise signals from code: kill(pid_t pid, int signo) – sends signo to the specified process or process group. raise(int signo) – a thin wrapper around kill(getpid(), signo), sending a signal to the calling process. alarm(unsigned int seconds) – sets a one‑shot timer that delivers SIGALRM after the given seconds. abort(void) – sends SIGABRT to terminate the process and generate a core dump.
Combined demonstration:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void usr1_handler(int sig) { printf("收到 SIGUSR1 自定义信号
"); }
void alarm_handler(int sig) { printf("闹钟超时,收到 SIGALRM 信号
"); }
int main() {
signal(SIGUSR1, usr1_handler);
signal(SIGALRM, alarm_handler);
printf("1. raise 发送自定义信号
");
raise(SIGUSR1);
printf("2. 设置 3 秒闹钟定时器
");
alarm(3);
sleep(4);
printf("3. abort 触发异常终止
");
abort();
return 0;
}2.3 System‑event signals
When certain kernel events occur, signals are generated automatically: SIGCHLD – sent to a parent when a child exits or stops; the parent can reap the child with wait / waitpid.
Timer signals: SIGALRM (real‑time alarm), SIGVTALRM (process virtual time), SIGPROF (profiling). SIGPIPE – raised when writing to a pipe whose read end is closed; default action terminates the process.
Example for SIGCHLD and SIGPIPE:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void chld_handler(int sig) {
printf("收到 SIGCHLD:子进程已退出,开始回收资源
");
wait(NULL);
}
int main() {
signal(SIGCHLD, chld_handler);
pid_t pid = fork();
if (pid == 0) {
printf("子进程运行完毕,即将退出
");
return 0;
} else {
while (1) sleep(1);
}
return 0;
}2.4 Hardware‑exception signals
Faults detected by the CPU or MMU generate signals such as: SIGSEGV – invalid memory access (e.g., dereferencing a NULL pointer). SIGFPE – arithmetic errors like division by zero. SIGILL – illegal instruction. SIGBUS – bus errors, often caused by misaligned accesses.
Demonstration:
#include <stdio.h>
#include <signal.h>
void segv_handler(int sig) { printf("捕获 SIGSEGV:非法内存访问(段错误)
"); exit(1); }
void fpe_handler(int sig) { printf("捕获 SIGFPE:算术运算异常(除零)
"); exit(1); }
int main() {
signal(SIGSEGV, segv_handler);
signal(SIGFPE, fpe_handler);
int *p = NULL;
*p = 10; // triggers SIGSEGV
return 0;
}3. Signal Classification
Linux defines 62 signals (1‑64, with 0, 32, 33 unused). They are split into:
Standard signals (1‑31) – historic Unix signals, no queuing; repeated occurrences may be lost.
Real‑time signals (34‑64) – defined by POSIX.1b, support queuing and can carry an integer payload via sigqueue.
Key standard signals: SIGTERM (15) – graceful termination. SIGKILL (9) – forced kill, cannot be caught or ignored. SIGSTOP (19) / SIGCONT (18) – pause and resume. SIGSEGV (11), SIGFPE (8), SIGILL (4), SIGBUS (7) – error handling. SIGINT (2), SIGQUIT (3), SIGTSTP (20), SIGHUP (1) – user‑initiated. SIGCHLD (17), SIGALRM (14), SIGPIPE (13), SIGUSR1 (10), SIGUSR2 (12) – system notifications and custom use.
Real‑time signals guarantee delivery order and can carry data:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void rt_signal_handler(int sig, siginfo_t *info, void *ctx) {
printf("收到实时信号 %d,携带数据:%d
", sig, info->si_value.sival_int);
}
int main() {
struct sigaction sa = {0};
sa.sa_sigaction = rt_signal_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGRTMIN, &sa, NULL);
pid_t pid = getpid();
union sigval val;
for (int i = 1; i <= 3; i++) {
val.sival_int = i;
sigqueue(pid, SIGRTMIN, val);
}
sleep(2);
return 0;
}4. Signal Handling Strategies
4.1 Default handling
If a process does not install a handler, the kernel performs the predefined action for that signal (terminate, core dump, ignore, stop, continue, etc.). For example, an unhandled SIGTERM terminates the process, while an unhandled SIGSEGV may generate a core file before termination.
4.2 Ignoring a signal
Use signal(signo, SIG_IGN) or sigaction with SA_IGN to discard a signal. Commonly, SIGPIPE is ignored in network programs to prevent abrupt termination.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main() {
signal(SIGPIPE, SIG_IGN);
while (1) sleep(1);
return 0;
}4.3 Custom handlers
Register a function with signal (simple) or sigaction (flexible). Example using signal for SIGINT:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void sigint_handler(int signum) {
printf("Received SIGINT signal. Cleaning up...
");
sleep(1);
printf("Cleaning up done. Exiting...
");
exit(0);
}
int main() {
signal(SIGINT, sigint_handler);
printf("Press Ctrl + C to terminate the program.
");
while (1) sleep(1);
return 0;
}Using sigaction for SIGTERM with additional flags:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void sigterm_handler(int signum) {
printf("Received SIGTERM signal. Performing custom actions...
");
sleep(1);
printf("Custom actions done. Exiting...
");
exit(0);
}
int main() {
struct sigaction sa = {0};
sa.sa_handler = sigterm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // could set SA_RESTART, SA_SIGINFO, etc.
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Process is running. Send SIGTERM to terminate.
");
while (1) sleep(1);
return 0;
}The sigaction structure fields: sa_handler – simple handler (compatible with signal). sa_sigaction – three‑argument handler used when SA_SIGINFO is set, allowing access to siginfo_t and context. sa_mask – set of signals blocked during execution of the handler. sa_flags – options such as SA_RESTART, SA_NOCLDSTOP, SA_NOCLDWAIT, SA_NODEFER, SA_RESETHAND, SA_SIGINFO.
5. Core Signal‑Related Functions
signal(int signum, sighandler_t handler)– legacy API, may reset to default after handling on some systems.
sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)– POSIX‑standard, more control. kill(pid_t pid, int sig) – send sig to a process or process group. pid semantics:
>0 – specific PID.
0 – all processes in the caller's process group.
-1 – all processes the caller has permission to signal.
< -1 – all processes in the absolute value of pid as a process‑group ID. raise(int sig) – equivalent to kill(getpid(), sig), used for self‑signalling. alarm(unsigned int seconds) – sets a one‑shot timer that delivers SIGALRM. Only one alarm can be active per process; calling with 0 cancels it.
Typical error codes for kill: EPERM – insufficient permission. ESRCH – no such process or process group. EINVAL – invalid signal number.
6. Signal Masking and Pending Signals
Signals can be blocked with sigprocmask to prevent delivery while a critical section runs. Blocked signals are recorded in the pending set and delivered once unblocked.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint_handler(int sig) {
printf("执行 SIGINT 信号处理逻辑
");
sleep(3);
printf("SIGINT 信号处理完成
");
}
int main() {
signal(SIGINT, sigint_handler);
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
printf("开始屏蔽 SIGINT 信号,5 秒内 Ctrl+C 无效
");
sigprocmask(SIG_BLOCK, &mask, NULL);
sleep(5);
printf("解除 SIGINT 信号屏蔽
");
sigprocmask(SIG_UNBLOCK, &mask, NULL);
while (1) sleep(1);
return 0;
}Query pending signals with sigpending:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main() {
sigset_t mask, pending;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, NULL);
printf("请按下 Ctrl+C,触发未决信号
");
sleep(5);
sigpending(&pending);
if (sigismember(&pending, SIGINT)) {
printf("检测到 SIGINT 处于未决状态
");
}
sigprocmask(SIG_UNBLOCK, &mask, NULL);
printf("信号处理完毕
");
return 0;
}Understanding generation, classification, masking, and handling of Linux signals is essential for backend developers, system programmers, and anyone preparing for technical interviews that probe deep OS knowledge.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
