0.1 什么是进程间通信
-
定义:进程间通信(IPC)是指不同进程之间交换信息或协作的机制。
-
为什么需要:
-
每个进程运行在独立的内存空间,默认无法直接访问其他进程的数据。
-
某些任务需要多个进程协作才能完成:
-
例如:客户端请求 → 服务器处理 → 响应客户端。
-
-
-
IPC 的作用:
-
数据传递:多个进程共享信息。
-
事件通知:告知另一个进程某事件发生。
-
同步控制:确保进程按特定顺序执行。
-
资源协调:避免多个进程同时访问同一资源导致冲突。
-
类比生活场景:
-
邮件系统:A写信 → B收到 → B处理 → 回信。
-
手机提醒:程序A完成任务 → 发送通知给程序B。
0.2 进程间通信方式
1. 信号(Signal)
-
定义:轻量级事件通知,只传递事件标志,不传递数据。
-
特点:
-
异步触发,不需要目标进程主动接收。
-
适合“事件通知”型任务。
-
默认动作可为终止、忽略、暂停或生成 core 文件。
-
-
应用示例:
-
Ctrl+C → SIGINT
-
定时器到期 → SIGALRM
-
2. 管道 / 有名管道(Pipe / FIFO)
-
定义:通过字节流在进程间传递数据。
-
特点:
-
父子进程间可直接使用匿名管道。
-
不同进程间需要用有名管道。
-
数据按写入顺序读取。
-
-
应用示例:
-
父进程产生数据 → 子进程处理数据。
-
3. 消息队列(Message Queue)
-
定义:内核维护的消息队列,多个进程可通过队列发送/接收消息。
-
特点:
-
可以带优先级。
-
队列存储在内核空间,进程不直接共享内存。
-
-
应用示例:
-
多个客户端向服务器发送任务消息,服务器按优先级处理。
-
4. 共享内存(Shared Memory)
-
定义:多个进程直接映射同一块内存区域。
-
特点:
-
数据传递高效,适合大数据量。
-
必须配合信号量或互斥锁保证同步。
-
-
应用示例:
-
游戏中共享地图数据、图像处理共享缓存。
-
5. 套接字(Socket)
-
定义:基于 TCP/UDP 的进程间通信机制,可本地或跨网络。
-
特点:
-
支持不同机器上的进程通信。
-
可以实现客户端-服务器架构。
-
-
应用示例:
-
Web 服务器接收浏览器请求 → 响应页面。
-
0.3 小结
-
选择方式原则:
-
事件通知 → 信号
-
小量数据传递 → 管道 / 消息队列
-
大数据传递 → 共享内存
-
跨机器通信 → 套接字
-
-
注意事项:
-
信号是异步的,处理不当可能导致资源泄露或程序异常。
-
共享内存必须加锁防止数据竞争。
-
管道/消息队列有限制大小,过量数据可能阻塞写操作。
-
1. 信号(Signal)
1.1 信号的概念
-
定义:信号是操作系统提供的一种异步通信机制,用于通知进程某个事件发生。
-
特点:
-
轻量级:只传递事件标志,不传递实际数据。
-
异步:发送信号的进程不关心接收进程是否立即处理。
-
类似生活类比:
-
教室铃响 → 下课(事件发生通知学生)
-
手机提醒 → 有新消息(通知用户)
-
-
-
应用场景:
-
异常处理:除零、非法内存访问
-
用户交互:Ctrl+C 停止程序
-
定时任务:定时器到期触发
-
1.2 信号的特点
特点 | 说明 |
---|---|
传递信息 | 只传递事件标志,不传递具体数据 |
软件中断 | 由操作系统内核或进程调用触发,不依赖硬件中断 |
异步性 | 进程在运行过程中可以被信号打断 |
可处理性 | 默认动作可执行、忽略或自定义处理 |
注意事项:
-
信号可能随时到达,如果处理函数执行时间过长,可能干扰进程正常逻辑。
-
某些信号(如 SIGKILL、SIGSTOP)无法被捕获或忽略。
1.3 信号编号
-
常规信号:编号 1~31,标准信号
-
实时信号:编号 34~64,保证排队、不丢失
-
查看方式:
kell -l
-
输出所有信号编号及对应名称
-
示例(常用的):
Linux 标准信号(1-32)一览表
信号名称 | 编号 | 默认行为 | 描述与触发原因 |
---|---|---|---|
SIGHUP | 1 | Term | 挂起 (Hangup)。控制终端关闭或进程退出后,发送给会话中的其他进程。也用于通知守护进程重载配置。 |
SIGINT | 2 | Term | 中断 (Interrupt)。来自键盘的中断,通常由 Ctrl + C 产生。 |
SIGQUIT | 3 | Core | 退出 (Quit)。来自键盘的退出,通常由 Ctrl + \ 产生。不仅终止进程,还会生成 core dump 文件用于调试。 |
SIGILL | 4 | Core | 非法指令 (Illegal Instruction)。进程尝试执行一条非法、未知或特权指令。 |
SIGTRAP | 5 | Core | 跟踪陷阱 (Trace/Breakpoint Trap)。由调试器使用,用于在断点处中断被调试的进程。 |
SIGABRT | 6 | Core | 中止 (Abort)。由 abort() 函数产生,表示程序检测到错误并主动调用终止。 |
SIGBUS | 7 | Core | 总线错误 (Bus Error)。内存访问错误,例如访问了未对齐的地址或不存在的物理地址。 |
SIGFPE | 8 | Core | 浮点异常 (Floating-point exception)。错误的算术操作,如除以零、溢出等。 |
SIGKILL | 9 | Term | 杀死 (Kill)。立即无条件终止进程。此信号无法被捕获、阻塞或忽略。是确保能杀死进程的最后手段。 |
SIGUSR1 | 10 | Term | 用户自定义信号 1 (User-defined signal 1)。留给程序员自由定义其用途。 |
SIGSEGV | 11 | Core | 段错误 (Segmentation fault)。无效的内存引用,如访问野指针、写入只读内存或访问已释放的内存。 |
SIGUSR2 | 12 | Term | 用户自定义信号 2 (User-defined signal 2)。留给程序员自由定义其用途。 |
SIGPIPE | 13 | Term | 管道破裂 (Broken pipe)。向一个没有读端的管道或套接字进行写入。常见于网络连接已关闭。 |
SIGALRM | 14 | Term | 闹钟信号 (Alarm clock)。由 alarm() 或 setitimer(ITIMER_REAL) 设置的定时器超时后产生。 |
SIGTERM | 15 | Term | 终止 (Termination)。友好地请求进程终止。这是 kill 命令的默认信号,进程可以捕获它并进行清理工作后退出。 |
SIGSTKFLT | 16 | Term | 协处理器栈错误 (Stack fault)。早年用于处理数学协处理器的栈错误,现在大多平台已不再使用。 |
SIGCHLD | 17 | Ign | 子进程状态改变 (Child status changed)。子进程停止或终止时,会发送此信号通知其父进程。用于避免僵尸进程。 |
SIGCONT | 18 | Cont | 继续 (Continue)。让一个被停止的进程恢复执行。即使进程被阻塞,此信号也会被交付。 |
SIGSTOP | 19 | Stop | 停止 (Stop)。暂停进程的执行。这是一个作业控制信号,与 SIGCONT 相对。无法被捕获、阻塞或忽略。 |
SIGTSTP | 20 | Stop | 终端停止 (Terminal stop)。来自终端的停止信号,通常由 Ctrl + Z 产生。进程可以捕获此信号。 |
SIGTTIN | 21 | Stop | 后台进程读终端 (Background read from tty)。当一个后台进程尝试从终端读取输入时,终端驱动程序会发送此信号来暂停它。 |
SIGTTOU | 22 | Stop | 后台进程写终端 (Background write to tty)。当一个后台进程尝试向终端输出时,终端驱动程序可能会发送此信号来暂停它。 |
SIGURG | 23 | Ign | 紧急条件 (Urgent condition)。通知进程带外数据 (out-of-band data) 已经到达网络连接上。 |
SIGXCPU | 24 | Core | 超过CPU时间限制 (CPU time limit exceeded)。进程消耗的CPU时间超过了其软资源限制。 |
SIGXFSZ | 25 | Core | 超过文件大小限制 (File size limit exceeded)。进程尝试扩大文件以至于超过了其软资源限制。 |
SIGVTALRM | 26 | Term | 虚拟定时器告警 (Virtual alarm clock)。由 setitimer(ITIMER_VIRTUAL) 设置的用户模式CPU时间定时器超时。 |
SIGPROF | 27 | Term | 性能分析定时器告警 (Profiling alarm clock)。由 setitimer(ITIMER_PROF) 设置的定时器超时,用于性能分析。 |
SIGWINCH | 28 | Ign | 窗口大小改变 (Window resize)。当终端窗口大小发生变化时(如调整了终端模拟器大小),会发送此信号。 |
SIGIO / SIGPOLL | 29 | Term | I/O 可能 (I/O now possible)。表示一个异步 I/O 事件发生。 |
SIGPWR | 30 | Term | 电源故障 (Power failure)。当系统检测到电源故障,即将切换到UPS备用电源时,会发送此信号。 |
SIGSYS | 31 | Core | 非法系统调用 (Bad system call)。进程执行了一个无效的系统调用(错误的参数或未知的调用号)。 |
SIGRTMIN 到 SIGRTMAX | 32-64 | Term | 实时信号 (Real-time signals)。编号 32 到 64 的信号是实时信号,它们没有预定义的含义,支持排队,不会丢失。它们不属于传统的 1-31 标准信号范围。 |
1.4 信号的四要素
-
编号(Number)
-
唯一标识信号类型
-
例如
SIGINT
编号 2
-
-
信号名称(Name)
-
宏定义形式,如
SIGTERM
-
编程时通常用名称而非数字,提高可移植性
-
-
对应事件(Event)
-
信号触发的实际事件,例如 Ctrl+C、定时器到期
-
-
默认处理动作(Default Action)
-
终止进程(Terminate)
-
忽略(Ignore)
-
暂停(Stop)
-
生成 core 文件(Core Dump)
-
示例查看:
man 7 signal
1.5 信号产生方式
-
用户操作
-
Ctrl+C → SIGINT
-
Ctrl+\ → SIGQUIT
-
Ctrl+Z → SIGSTOP
-
-
硬件异常
-
除零、非法内存访问 → 内核发送 SIGFPE、SIGSEGV
-
-
软件异常
-
程序调用
abort()
→ SIGABRT -
定时器到期 → SIGALRM
-
-
系统调用
-
kill(pid, sig)
→ 发送信号 -
raise(sig)
→ 向自己发送
-
-
命令行工具
-
kill
、killall
实际是系统调用封装
-
注意事项:
-
信号发送是异步的,可能随时中断进程执行。
-
如果进程没有捕获信号,默认动作执行。
1.6 信号处理方式
处理方式 | 说明 |
---|---|
默认动作 | 系统预定义,可能终止、暂停、忽略或生成 core |
忽略信号 | 使用 SIG_IGN 忽略信号 |
捕获信号 | 使用自定义 handler 函数处理信号 |
示例:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handle_sigint(int sig) {
printf("收到 SIGINT,信号编号 %d\n", sig);
}
int main() {
signal(SIGINT, handle_sigint); // 捕获 Ctrl+C
while(1) {
sleep(1);
}
return 0;
}
当用户按 Ctrl+C,程序不会退出,而是打印消息。
2. 信号的阻塞集与未决集
2.1 信号异步特性
-
信号是异步事件,即它可以在任意时刻打断进程执行。
-
异步意味着:
-
信号发送者无需等待接收者处理。
-
处理时机可能不确定。
-
问题:异步信号可能干扰关键操作(如修改共享数据),需要控制处理时机。
2.2 阻塞信号集(Signal Mask / Blocked Set)
-
定义:阻塞信号集是当前进程暂时“屏蔽”的信号集合。
-
作用:
-
当某些信号被加入阻塞集后,进程收到这些信号时不会立即处理。
-
信号状态存入未决信号集,处理会被推迟,直到信号从阻塞集移除。
-
-
常用 API:
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigemptyset(sigset_t *set); // 清空集合 int sigfillset(sigset_t *set); // 全部加入集合 int sigaddset(sigset_t *set, int signo); // 加入单个信号 int sigdelset(sigset_t *set, int signo); // 移除单个信号 int sigismember(const sigset_t *set, int signo); // 判断信号是否在集合
-
类比:
-
把手机调成“勿扰模式”,通知来了但不打扰你(信号被阻塞)。
-
2.3 未决信号集(Pending Set)
-
定义:未决信号集是已经发送但尚未处理的信号集合。
-
特点:
-
当信号被阻塞时,它会进入未决信号集。
-
一旦从阻塞集移除,未决信号会立即触发处理。
-
-
原理:
-
Linux 内核在
task_struct
中维护未决信号集位图。 -
信号到达时,对应位从 0 → 1。
-
信号处理完成后,对应位复原为 0。
-
类比:
-
阻塞集:你在开会时把通知静音。
-
未决信号集:静音期间收到的消息积压在通知栏,会议结束后立即处理。
2.4 信号阻塞与未决关系
-
阻塞信号进入未决信号集:
进程收到信号 S: - S 在阻塞集 → 加入未决信号集,处理延迟 - S 不在阻塞集 → 立即处理
-
解除阻塞触发处理:
sigprocmask(SIG_UNBLOCK, &set, NULL);
-
移除阻塞集的信号 → 内核检查未决信号集 → 调用信号处理函数。
-
2.5 示例代码:阻塞与未决信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int sig) {
printf("处理信号 %d\n", sig);
}
int main() {
sigset_t set;
signal(SIGINT, sig_handler); // 捕获 Ctrl+C
sigemptyset(&set);
sigaddset(&set, SIGINT); // 阻塞 SIGINT
sigprocmask(SIG_BLOCK, &set, NULL);
printf("阻塞 SIGINT,尝试按 Ctrl+C\n");
sleep(5); // 此时 Ctrl+C 不会触发处理函数
sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞
printf("解除阻塞,未决信号立即处理\n");
sleep(5); // 等待观察信号处理
return 0;
}
运行效果:
-
阻塞期间按 Ctrl+C → 不打印任何信息。
-
解除阻塞后,未决的 SIGINT 被立即处理 → 打印“处理信号 2”。
2.6 注意事项
-
信号顺序:
-
常规信号可能丢失(只记录一次未决状态)。
-
实时信号保证顺序,不丢失。
-
-
避免数据竞争:
-
信号处理函数内不要执行非原子操作或使用共享全局数据,除非加锁。
-
-
适用场景:
-
临界区保护:在关键操作期间阻塞特定信号。
-
异步事件处理:控制信号触发时机。
-
3. 信号相关 API 函数
3.1 kill(向其他进程发送信号)
-
功能:向指定进程或进程组发送信号。
-
头文件:
#include <sys/types.h>
#include <signal.h>
-
函数原型:
int kill(pid_t pid, int sig);
-
参数说明:
参数 含义 pid 指定发送对象,规则如下:
pid>0 → 指定进程ID
pid=0 → 当前进程所在进程组
pid=-1 → 系统内所有进程(慎用)
pid<-1 → 指定进程组,组号 =sig 信号编号或宏,如 SIGINT、SIGTERM -
返回值:
-
成功 → 0
-
失败 → -1
-
-
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main() {
pid_t pid = fork();
if (pid == 0) { // 子进程
while(1) { printf("子进程运行中...\n"); sleep(1);}
} else if (pid > 0) { // 父进程
sleep(3);
kill(pid, SIGINT); // 向子进程发送 SIGINT
}
return 0;
}
作用:父进程在 3 秒后通知子进程收到 SIGINT,默认子进程被中断。
3.2 raise(向自己发送信号)
-
功能:给调用进程自己发送信号。
-
头文件:
#include <signal.h>
-
函数原型:
int raise(int sig);
-
作用:
-
等价于
kill(getpid(), sig)
。 -
常用于测试或自杀式终止。
-
-
示例:
#include <stdio.h>
#include <signal.h>
void handler(int sig) { printf("收到信号 %d\n", sig); }
int main() {
signal(SIGINT, handler);
printf("向自己发送 SIGINT\n");
raise(SIGINT); // 触发 handler
return 0;
}
3.3 abort(异常终止)
-
功能:向自己发送 SIGABRT,产生 core 文件。
-
头文件:
#include <stdlib.h>
-
函数原型:
void abort(void);
-
应用场景:
-
当程序检测到严重错误,无法继续执行时使用。
-
-
示例:
#include <stdlib.h>
int main() {
abort(); // 进程异常终止并生成 core
return 0;
}
3.4 alarm(定时发送信号)
-
功能:设置定时器,到期向进程发送 SIGALRM。
-
头文件:
#include <unistd.h>
-
函数原型:
unsigned int alarm(unsigned int seconds);
-
参数说明:
-
seconds
:指定秒数 -
alarm(0)
→ 取消定时器,返回剩余时间
-
-
示例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig) { printf("定时器触发 SIGALRM\n"); }
int main() {
signal(SIGALRM, handler);
alarm(3); // 3秒后发送 SIGALRM
printf("等待信号...\n");
pause(); // 阻塞等待信号
return 0;
}
3.5 setitimer(高精度定时器)
-
功能:可实现周期性定时器,精度到微秒。
-
头文件:
#include <sys/time.h>
#include <signal.h>
-
函数原型:
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
-
参数说明:
参数 含义 which 定时器类型:
ITIMER_REAL → 实际时间(触发 SIGALRM)
ITIMER_VIRTUAL → 用户态 CPU 时间(触发 SIGVTALRM)
ITIMER_PROF → 用户 + 内核 CPU 时间(触发 SIGPROF)new_value 设置新的定时器,包括首次触发和循环间隔 old_value 保存上一次定时器状态,可为 NULL -
结构体:
struct itimerval {
struct timeval it_interval; // 后续周期
struct timeval it_value; // 首次触发时间
};
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒
};
-
示例:
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
void handler(int sig) { printf("SIGALRM触发\n"); }
int main() {
signal(SIGALRM, handler);
struct itimerval timer = {{2,0}, {5,0}}; // 第一次 5s 后触发,每隔 2s
setitimer(ITIMER_REAL, &timer, NULL);
while(1) sleep(1);
return 0;
}
3.6 小结与注意事项
-
kill:向其他进程发送信号,常用于进程控制。
-
raise:向自己发送信号,方便测试。
-
abort:异常终止并生成 core。
-
alarm:简单定时器,单位秒。
-
setitimer:高精度、可周期性定时器,单位微秒。
-
注意事项:
-
定时器只能有一个(每种类型)。
-
信号处理函数尽量简单,避免复杂逻辑或阻塞操作。
-
某些信号无法捕获(如 SIGKILL、SIGSTOP)。
-
4. 给信号注册自定义处理函数
当进程收到信号时,系统提供三种处理方式:
-
默认动作(Default):执行信号预定义动作,如终止、暂停或忽略进程。
-
忽略信号(Ignore):对信号完全不做处理。
-
自定义处理函数(User-defined):由程序员提供函数,收到信号时执行特定操作。
自定义信号处理函数可以在进程中释放资源、记录日志或执行清理操作。
4.1 signal 函数
-
功能:注册信号处理函数。
-
头文件:
#include <signal.h>
-
函数原型:
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
-
参数说明:
参数 含义 signum 信号编号(如 SIGINT、SIGTERM) handler 可以是:
1. SIG_IGN → 忽略信号
2. SIG_DFL → 默认处理
3. 函数指针 → 自定义处理函数 -
返回值:
-
成功 → 返回之前的信号处理函数指针
-
失败 → SIG_ERR
-
-
注意事项:
-
不能捕获 SIGKILL 和 SIGSTOP。
-
在信号处理函数中,尽量只调用异步安全函数(如 write、_exit),避免 printf、malloc 等可能导致未定义行为。
-
-
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char *p = NULL;
void my_handler(int sig) {
if (p != NULL) {
free(p); // 释放资源
p = NULL;
}
printf("收到信号 %d,资源已释放\n", sig);
_exit(0); // 安全退出
}
int main() {
p = (char *)malloc(128);
signal(SIGINT, my_handler); // 注册 Ctrl+C 处理
while(1) {
printf("p 地址: %p\n", p);
sleep(1);
}
free(p);
return 0;
}
功能:按 Ctrl+C 时调用
my_handler
,释放分配的内存并退出程序。
4.2 sigaction 函数
-
功能:比 signal 更强大、可精确控制信号处理行为。
-
头文件:
#include <signal.h>
-
函数原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
-
参数说明:
参数 含义 signum 信号编号 act 指向新的信号处理方式 oldact 保存原来的信号处理方式,可为 NULL -
结构体:
struct sigaction {
void (*sa_handler)(int); // 旧处理函数
void (*sa_sigaction)(int, siginfo_t*, void*); // 新处理函数,可获取更多信息
sigset_t sa_mask; // 信号阻塞集(在处理期间阻塞的信号)
int sa_flags; // 控制信号处理行为
void (*sa_restorer)(void); // 已弃用
};
-
sa_flags 常用值:
标志 含义 SA_RESTART 使被信号打断的系统调用自动重启(已废弃) SA_NOCLDSTOP 子进程暂停或继续时不发送 SIGCHLD SA_NOCLDWAIT 子进程退出时不生成僵尸进程 SA_NODEFER 信号处理期间仍可接收相同信号 SA_RESETHAND 处理完信号后恢复默认动作 SA_SIGINFO 使用 sa_sigaction 成员,而不是 sa_handler -
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig, siginfo_t *info, void *ucontext) {
printf("收到信号 %d,发送者 PID: %d\n", sig, info->si_pid);
_exit(0);
}
int main() {
struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
sigaction(SIGINT, &act, NULL); // 注册自定义信号处理
while(1) {
printf("等待信号...\n");
sleep(1);
}
return 0;
}
功能:按 Ctrl+C 时打印发送信号的进程 PID 并退出。相比 signal,可获取更多信号信息。
4.3 小结
-
signal:简单易用,但功能有限。
-
sigaction:功能强大,可获取信号来源、控制处理期间阻塞的信号,适合复杂场景。
-
安全建议:
-
信号处理函数要尽量短小、异步安全。
-
避免在处理函数中调用可能阻塞或不安全的函数(如 printf、malloc 等)。
-
5. 信号集(Signal Set)
信号集用于管理一组信号,主要用于 阻塞信号(sigprocmask) 或 处理期间屏蔽信号。
信号集类型:
#include <signal.h>
typedef struct { ... } sigset_t;
5.1 sigemptyset(清空信号集)
-
功能:将信号集置为空,即不包含任何信号。
-
函数原型:
int sigemptyset(sigset_t *set);
-
参数:
-
set
:要操作的信号集指针
-
-
返回值:
-
成功 → 0
-
失败 → -1
-
-
示例:
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t set;
sigemptyset(&set); // 清空信号集
if (sigismember(&set, SIGINT) == 0) {
printf("SIGINT 不在集合中\n");
}
return 0;
}
5.2 sigfillset(填满信号集)
-
功能:将信号集填满,包含所有信号。
-
函数原型:
int sigfillset(sigset_t *set);
-
用途:
-
常用于阻塞所有信号,例如临界区保护。
-
-
示例:
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t set;
sigfillset(&set); // 填满集合
if (sigismember(&set, SIGINT)) {
printf("SIGINT 已在集合中\n");
}
return 0;
}
5.3 sigaddset(加入信号)
-
功能:将某个信号加入信号集。
-
函数原型:
int sigaddset(sigset_t *set, int signo);
-
参数:
-
set
:信号集 -
signo
:信号编号(如 SIGINT)
-
-
示例:
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t set;
sigemptyset(&set); // 清空集合
sigaddset(&set, SIGINT); // 加入 SIGINT
if (sigismember(&set, SIGINT)) {
printf("SIGINT 已加入集合\n");
}
return 0;
}
5.4 sigdelset(删除信号)
-
功能:从信号集中删除某个信号。
-
函数原型:
int sigdelset(sigset_t *set, int signo);
-
示例:
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t set;
sigfillset(&set); // 填满集合
sigdelset(&set, SIGINT); // 删除 SIGINT
if (!sigismember(&set, SIGINT)) {
printf("SIGINT 已从集合中删除\n");
}
return 0;
}
5.5 sigismember(判断信号是否存在)
-
功能:判断某个信号是否在集合中。
-
函数原型:
int sigismember(const sigset_t *set, int signo);
-
返回值:
-
1 → 在集合中
-
0 → 不在集合中
-
-1 → 错误
-
-
示例:
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
if (sigismember(&set, SIGINT)) {
printf("SIGINT 在集合中\n");
} else {
printf("SIGINT 不在集合中\n");
}
return 0;
}
5.6 使用场景
-
阻塞信号
-
在临界区阻止信号打断关键操作,例如:
sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞 SIGINT
-
-
在信号处理函数中临时屏蔽信号
-
避免递归调用同一信号。
-
-
配合
sigaction
使用-
sa_mask
成员即是信号集,用于指定处理期间阻塞的信号。
-
6. 信号阻塞集(Blocked Signal Set)
6.1 概念
-
阻塞信号:进程在某段时间不希望处理的信号。
-
当信号在阻塞信号集中时:
-
信号不会立即被处理。
-
信号被标记为未决信号,等待解除阻塞后再处理。
-
-
通过阻塞信号可以保护临界区或关键操作,防止信号干扰。
6.2 API 函数
6.2.1 sigprocmask
-
功能:操作当前进程的信号屏蔽字(阻塞集)。
-
原型:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-
参数说明:
参数 含义 how SIG_BLOCK → 添加阻塞信号
SIG_UNBLOCK → 移除阻塞信号
SIG_SETMASK → 设置阻塞信号集set 要操作的信号集 oldset 保存原来的阻塞信号集,可为 NULL -
示例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
sigset_t set, oldset;
sigemptyset(&set);
sigaddset(&set, SIGINT); // 阻塞 SIGINT
sigprocmask(SIG_BLOCK, &set, &oldset);
printf("阻塞 SIGINT,3秒内按 Ctrl+C 无效\n");
sleep(3);
sigprocmask(SIG_SETMASK, &oldset, NULL); // 恢复原阻塞集
printf("解除阻塞 SIGINT\n");
while(1) sleep(1);
return 0;
}
6.3 使用场景
-
保护临界区:
-
在操作共享资源时阻塞可能打断操作的信号。
-
-
延迟处理信号:
-
阻塞信号后,处理可以统一在安全时刻进行。
-
-
配合 sigaction 的 sa_mask 使用:
-
信号处理期间临时阻塞指定信号,防止递归调用。
-
7. 信号未决集(Pending Signal Set)
7.1 概念
-
未决信号:进程收到但尚未处理的信号。
-
产生条件:
-
信号到达时被阻塞。
-
信号尚未被处理(处理函数未执行)。
-
-
特点:
-
每个信号在未决集合中只有一个位。
-
当信号解除阻塞时才会被处理。
-
异步发送时,如果多次发送同一信号,未决集合只标记一次。
-
7.2 查看未决信号
7.2.1 sigpending
-
功能:获取当前进程的未决信号集。
-
原型:
#include <signal.h>
int sigpending(sigset_t *set);
-
参数:
-
set
:存放未决信号集
-
-
返回值:
-
成功 → 0
-
失败 → -1
-
-
示例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞 SIGINT
printf("阻塞 SIGINT,按 Ctrl+C 尝试发送\n");
sleep(5);
sigpending(&set); // 获取未决信号
if (sigismember(&set, SIGINT)) {
printf("SIGINT 在未决信号集中\n");
}
sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞,信号处理
while(1) sleep(1);
return 0;
}
7.3 阻塞集 vs 未决集
特性 | 阻塞信号集 | 未决信号集 |
---|---|---|
含义 | 当前不希望处理的信号集合 | 已到达但尚未处理的信号集合 |
修改 | 可通过 sigprocmask 修改 | 由内核自动管理 |
行为 | 信号到达时,如果在阻塞集 → 标记为未决 | 信号解除阻塞后 → 执行处理 |
-
理解:阻塞信号是“我不想处理的信号”,未决信号是“已经来了但是还没处理的信号”。
8. 进程间通信总结与综合示例
进程间通信(IPC)是一种让独立进程互相协作和交换信息的机制。在 Linux 中,信号是最基础的一种 IPC 方式,它具有异步、轻量级的特点,但只传递状态信息而不传递数据。
8.1 总结核心概念
概念 | 功能 | 备注 |
---|---|---|
信号(Signal) | 通知进程发生了某个事件 | 异步通信;仅标志事件,不传数据 |
信号编号与名称 | 标识不同事件 | 1-31 常规信号;34-64 实时信号 |
默认动作 | 系统收到信号后的处理 | 终止、忽略、暂停、核心转储等 |
阻塞信号集 | 临时屏蔽信号 | 使用 sigprocmask 或 sa_mask |
未决信号集 | 已到达但未处理信号 | 信号在阻塞时会进入未决集合 |
信号处理函数 | 自定义处理逻辑 | 使用 signal 或 sigaction 注册 |
定时器信号 | 延迟或周期触发信号 | alarm 、setitimer ,可实现周期任务 |
8.2 信号操作 API 回顾
-
发送信号
-
kill(pid, sig)
→ 向其他进程发送信号 -
raise(sig)
→ 向自己发送信号 -
abort()
→ 向自己发送 SIGABRT 并生成 core 文件
-
-
阻塞与解除
-
sigemptyset
,sigfillset
,sigaddset
,sigdelset
,sigismember
-
sigprocmask(SIG_BLOCK / SIG_UNBLOCK / SIG_SETMASK, ...)
-
-
处理信号
-
signal(signum, handler)
→ 注册简单自定义函数 -
sigaction(signum, &act, &oldact)
→ 注册高级自定义函数,可设置阻塞信号和特殊标志
-
-
定时器信号
-
alarm(seconds)
→ 单次定时器 -
setitimer(ITIMER_REAL, &new, &old)
→ 可循环定时器,精度到微秒
-
8.3 综合示例
下面示例演示:
-
父进程创建子进程
-
阻塞 SIGINT
-
子进程在特定时间发送 SIGINT
-
使用自定义处理函数处理信号
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
void handle_sigint(int sig) {
printf("收到 SIGINT 信号:%d\n", sig);
}
int main() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
// 阻塞 SIGINT
sigprocmask(SIG_BLOCK, &set, NULL);
pid_t pid = fork();
if (pid == 0) { // 子进程
sleep(2); // 延迟发送
kill(getppid(), SIGINT); // 发送 SIGINT 给父进程
exit(0);
} else if (pid > 0) { // 父进程
signal(SIGINT, handle_sigint); // 注册自定义处理函数
printf("父进程阻塞 SIGINT 3秒\n");
sleep(3);
// 解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
// 等待子进程
wait(NULL);
printf("父进程结束\n");
}
return 0;
}
运行分析:
-
子进程在父进程阻塞 SIGINT 时发送信号 → 信号进入未决集合
-
父进程解除阻塞后 → 信号处理函数
handle_sigint
执行 -
展示了阻塞集、未决集、信号处理函数的综合使用
8.4 学习要点
-
信号是异步通信:信号不会传递数据,只标记事件。
-
阻塞信号是保护机制:在关键操作期间,阻塞信号避免打断。
-
未决信号保证可靠性:即使信号被阻塞,也不会丢失。
-
定时器与信号结合使用:可实现周期任务或延时操作。
-
自定义处理函数:灵活控制信号处理行为,避免直接使用默认动作导致进程异常终止。