信号是Linux系统中最古老的进程间通信机制之一,本质上是一种软件中断。当某个事件发生时,内核会向目标进程发送一个信号,进程收到信号后会中断当前执行流程,转去执行预设的信号处理函数。
信号的核心特点:
产生(Delivery) → 排队(Pending) → 抛递(Delivery) → 处理(Disposition)
| 类别 | 信号范围 | 特点 |
|---|---|---|
| 非实时信号 | 1-31 | 不支持排队,多次发送只保留一次 |
| 实时信号 | 34-64 | 支持排队,可携带额外数据 |
#include <signal.h>
typedef void (*sighandler_t)(int signum);
sighandler_t signal(int signum, sighandler_t handler);
| 参数 | 说明 |
|---|---|
| signum | 信号编号 |
| handler | SIG_IGN(忽略)、SIG_DFL(默认)、自定义处理函数指针 |
| 返回值 | 说明 |
|---|---|
| 成功 | 之前的信号处理函数指针 |
| 失败 | SIG_ERR |
注意:signal()在不同Unix系统中行为不一致,Linux下会自动重启系统调用,但handler可能被重置为默认(不可靠)。
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction 结构体:
struct sigaction {
union {
void (*sa_handler)(int signum); // 简单处理函数
void (*sa_sigaction)(int signum, siginfo_t *info, void *context); // 高级处理函数
};
sigset_t sa_mask; // 信号屏蔽字,处理期间阻塞的信号
int sa_flags; // 选项标志
void (*sa_restorer)(void); // 已废弃,不要使用
};
| sa_flags | 说明 |
|---|---|
| SA_SIGINFO | 使用sa_sigaction作为处理函数(可获取信号详细信息) |
| SA_RESTART | 自动重启被中断的系统调用 |
| SA_NOCLDSTOP | 子进程停止时不产生SIGCHLD |
| SA_NOCLDWAIT | 子进程退出时不产生僵尸进程 |
| SA_NODEFER | 处理期间不自动屏蔽当前信号 |
| SA_RESETHAND | 处理一次后恢复为默认动作 |
siginfo_t 结构体(需SA_SIGINFO标志):
typedef struct {
int si_signo; // 信号编号
int si_code; // 信号来源
int si_errno; // 错误码
pid_t si_pid; // 发送进程PID
uid_t si_uid; // 发送进程UID
void *si_addr; // 内存地址(SIGSEGV)
int si_status; // 退出状态(SIGCHLD)
// ... 其他字段
} siginfo_t;
#include <signal.h>
int kill(pid_t pid, int sig); // 发送信号给进程
int raise(int sig); // 发送信号给自己
int sigqueue(pid_t pid, int sig, const union sigval value); // 实时信号+数据
kill() 的 pid 参数:
| pid值 | 含义 |
|---|---|
| pid > 0 | 发送给指定PID的进程 |
| pid == 0 | 发送给同进程组的所有进程 |
| pid == -1 | 发送给所有有权限发送的进程 |
| pid < -1 | 发送给进程组ID为-pid的进程组 |
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
sigprocmask 的 how 参数:
| how | 说明 |
|---|---|
| SIG_BLOCK | 将set中的信号添加到屏蔽字 |
| SIG_UNBLOCK | 从屏蔽字中移除set中的信号 |
| SIG_SETMASK | 将屏蔽字设置为set |
信号集操作函数:
sigemptyset(sigset_t *set); // 清空信号集
sigfillset(sigset_t *set); // 填满信号集
sigaddset(sigset_t *set, int signum); // 添加信号
sigdelset(sigset_t *set, int signum); // 删除信号
sigismember(const sigset_t *set, int signum); // 检查信号是否在集合中
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigint_handler(int signum) {
printf("收到SIGINT信号(%d),程序即将退出...\n", signum);
_exit(0);
}
int main() {
// 注册SIGINT信号处理函数
signal(SIGINT, sigint_handler);
printf("程序运行中,按Ctrl+C测试...\n");
while(1) {
sleep(1);
}
return 0;
}
编译命令:
gcc -o signal_demo signal_demo.c
./signal_demo
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
// 高级信号处理函数
void sig_handler(int signum, siginfo_t *info, void *context) {
printf("信号编号: %d\n", signum);
printf("发送进程PID: %d\n", info->si_pid);
printf("发送进程UID: %d\n", info->si_uid);
printf("信号来源代码: %d\n", info->si_code);
if (signum == SIGSEGV) {
printf("发生段错误,地址: %p\n", info->si_addr);
}
}
int main() {
struct sigaction sa;
// 清空并设置sigaction结构体
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sig_handler;
sa.sa_flags = SA_SIGINFO; // 使用高级处理函数
// 注册SIGSEGV信号处理
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
printf("程序运行中,触发段错误测试...\n");
// 触发段错误
int *p = NULL;
*p = 100;
return 0;
}
编译命令:
gcc -o sigaction_demo sigaction_demo.c
./sigaction_demo
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
volatile int received_sigusr1 = 0;
void sigusr1_handler(int signum) {
received_sigusr1 = 1;
printf("收到SIGUSR1信号\n");
}
int main() {
struct sigaction sa;
sigset_t block_set, old_set, wait_set;
// 设置信号处理函数
sigemptyset(&sa.sa_mask);
sa.sa_handler = sigusr1_handler;
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, NULL);
// 创建要屏蔽的信号集
sigemptyset(&block_set);
sigaddset(&block_set, SIGUSR1);
// 阻塞SIGUSR1
sigprocmask(SIG_BLOCK, &block_set, &old_set);
printf("PID: %d,等待SIGUSR1信号...\n", getpid());
// 等待信号(原子操作:解除阻塞并等待)
sigemptyset(&wait_set);
sigsuspend(&wait_set);
printf("收到信号,继续执行...\n");
// 恢复原来的信号屏蔽字
sigprocmask(SIG_SETMASK, &old_set, NULL);
return 0;
}
编译命令:
gcc -o signal_block signal_block.c
./signal_block &
kill -USR1 <PID>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
volatile int alarm_count = 0;
void alarm_handler(int signum) {
alarm_count++;
printf("ALRM: %d\n", alarm_count);
// 每次触发后重新设置5秒定时器
if (alarm_count < 5) {
alarm(5);
} else {
printf("定时器结束\n");
}
}
int main() {
struct sigaction sa;
char buf[100];
// 设置SIGALRM处理
sigemptyset(&sa.sa_mask);
sa.sa_handler = alarm_handler;
sa.sa_flags = SA_RESTART; // 自动重启被中断的read
sigaction(SIGALRM, &sa, NULL);
// 设置5秒定时器
alarm(5);
printf("程序运行中,等待定时器...\n");
printf("也可以在终端输入内容测试EINTR:\n");
// 测试SA_RESTART效果
// 如果没有SA_RESTART,read会被信号中断返回-1
while (1) {
printf("> ");
fflush(stdout);
if (read(STDIN_FILENO, buf, sizeof(buf)) == -1) {
if (errno == EINTR) {
printf("read被信号中断 (EINTR)\n");
continue;
}
perror("read");
break;
}
if (strncmp(buf, "quit", 4) == 0) {
break;
}
printf("输入: %s", buf);
}
return 0;
}
编译命令:
gcc -o alarm_demo alarm_demo.c
./alarm_demo
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
volatile int child_exited = 0;
void sigchld_handler(int signum) {
int status;
pid_t pid;
// 使用WNOHANG循环回收,避免遗漏
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
printf("子进程%d正常退出,状态码: %d\n", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程%d被信号%d终止\n", pid, WTERMSIG(status));
}
child_exited = 1;
}
}
int main() {
struct sigaction sa;
pid_t pid;
// 设置SIGCHLD处理
sigemptyset(&sa.sa_mask);
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // 不处理停止的子进程
sigaction(SIGCHLD, &sa, NULL);
// 创建子进程
pid = fork();
if (pid == 0) {
// 子进程
printf("子进程PID: %d,3秒后退出\n", getpid());
sleep(3);
exit(42); // 退出状态码42
}
// 父进程
printf("父进程PID: %d,子进程PID: %d\n", getpid(), pid);
printf("等待子进程退出...\n");
while (!child_exited) {
pause(); // 挂起等待信号
}
printf("所有子进程已回收\n");
return 0;
}
编译命令:
gcc -o sigchld_demo sigchld_demo.c
./sigchld_demo
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
void sigrt_handler(int signum, siginfo_t *info, void *context) {
printf("收到实时信号 %d, 数据值: %d\n", signum, info->si_value.sival_int);
}
int main() {
struct sigaction sa;
union sigval val;
pid_t pid;
// 设置实时信号处理
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sigrt_handler;
sa.sa_flags = SA_SIGINFO; // 必须使用SA_SIGINFO
sigaction(SIGRTMIN, &sa, NULL);
printf("PID: %d\n", getpid());
printf("等待实时信号...\n");
// 等待一下,然后发送测试信号
sleep(2);
// 使用sigqueue发送实时信号+数据
val.sival_int = 12345;
if (sigqueue(getpid(), SIGRTMIN, val) == -1) {
perror("sigqueue");
}
// 多次发送,实时信号会排队
for (int i = 0; i < 3; i++) {
val.sival_int = 100 + i;
sigqueue(getpid(), SIGRTMIN, val);
}
sleep(1);
return 0;
}
编译命令:
gcc -o realtime_sig realtime_sig.c
./realtime_sig
| 问题 | 说明 | 解决方案 |
|---|---|---|
| signal()不可靠 | handler可能被重置为SIG_DFL | 使用sigaction()替代 |
| 信号处理函数中避免非异步安全函数 | printf、malloc、free等不可重入 | 使用write()、_exit(),或设置标志位 |
| 信号屏蔽遗漏 | 信号处理期间收到相同信号导致递归 | 使用sa_mask自动屏蔽当前信号 |
| 僵尸进程 | 子进程退出后父进程未回收 | 处理SIGCHLD或设置SA_NOCLDWAIT |
| EINTR错误 | 系统调用被信号中断 | 使用SA_RESTART或检查errno |
| 实时信号丢失 | 非实时信号只保留一次 | 使用SIGRTMIN及以上的实时信号 |
| kill()权限不足 | 非root进程只能发给同用户进程 | 保证发送者和接收者属于同一用户 |
| 信号处理函数阻塞 | 处理函数执行时间过长影响系统响应 | 尽量简短,设置标志位延迟处理 |
| 多线程信号处理 | 信号默认投递给主线程 | 使用sigaction配合线程掩码 |
异步信号安全函数(可在信号处理函数中调用):
_exit()、_Exit()read()、write()sigaction()、sigprocmask()kill()、raise()alarm()、pause()、sleep()getpid()、getuid()pthread_sigmask()(多线程)不安全函数(避免在信号处理函数中调用):
printf()、fprintf()、malloc()、free()exit()、abort()答:
答:
答:
答:
signal(SIGCHLD, SIG_IGN) 或设置SA_NOCLDWAIT(不产生僵尸进程)while(waitpid(-1, &status, WNOHANG) > 0)循环回收答:
| 特性 | 非实时信号(1-31) | 实时信号(34-64) |
|---|---|---|
| 排队 | 不支持,多次发送只保留一次 | 支持,按发送顺序排队 |
| 优先级 | 无 | 优先级从34递增 |
| 携带数据 | 不能 | 可通过sigqueue传递联合体数据 |
| 典型用途 | 传统Unix信号 | 用户自定义、实时系统 |
答: