目录
阻塞信号(屏蔽信号)
在 Linux 信号处理机制中,信号阻塞允许进程暂时屏蔽某些信号,使这些信号在解除阻塞前不会被处理。这种机制主要用于防止信号在某些关键代码段中被意外中断。
- 信号阻塞:指将某些信号加入进程的信号屏蔽字(Signal Mask)中,暂时屏蔽这些信号。被阻塞的信号虽然会被记录,但不会立即递送给进程。
- 解除阻塞:从信号屏蔽字中移除信号,之前阻塞的信号会立即递送并处理。
信号阻塞相关函数
sigprocmask
函数
功能:获取或修改当前进程的信号屏蔽字。
函数原型
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数
how
:操作方式,取以下值之一:
SIG_BLOCK
:将set
指定的信号加入到当前信号屏蔽字中(阻塞信号)。SIG_UNBLOCK
:从当前信号屏蔽字中移除
set
指定的信号(解除阻塞)。SIG_SETMASK
:将当前信号屏蔽字设置为
set
。set
:指向信号集合的指针,表示要阻塞或解除阻塞的信号集合。如果为NULL
,不会修改信号屏蔽字。oldset
:指向信号集合的指针,用于保存之前的信号屏蔽字。如果为NULL
,不保存旧值。
返回值
- 成功返回
0
。- 失败返回
-1
,并设置errno
。
sigpending
函数
功能:获取当前被阻塞且未处理的信号集合。
函数原型
#include <signal.h>
int sigpending(sigset_t *set);
- 参数:
set
:指向信号集合的指针,用于保存被阻塞但尚未处理的信号。- 返回值:
- 成功返回
0
。- 失败返回
-1
。
sigsuspend
函数
功能:挂起进程,并临时替换信号屏蔽字,直到信号到达。
函数原型
#include <signal.h>
int sigsuspend(const sigset_t *mask);
- 参数:
mask
:指定临时信号屏蔽字,挂起进程时生效。- 返回值:
- 永远返回
-1
,并设置
errno = EINTR
,表示被信号中断。- 用途:
- 用于替代
pause
,避免竞争条件。
信号集合相关函数
sigset_t类型的变量(本质上是个数组) --》linux中专门用来保存阻塞信号,称之为信号阻塞掩码集
常用操作函数:
#include <signal.h>
int sigemptyset(sigset_t *set); // 清空信号集合
int sigfillset(sigset_t *set); // 将所有62个信号加入集合
int sigaddset(sigset_t *set, int signum); // 向set集合中添加信号
int sigdelset(sigset_t *set, int signum); // 从set集合中删除信号
int sigismember(const sigset_t *set, int signum); // 检查信号是否在集合中,在返回1,不在0
阻塞信号代码实现
示例:设置阻塞信号
#include "myhead.h"
/*
阻塞信号(屏蔽信号):把信号挂起,暂时不响应(但是信号还在存在的)
忽略信号:把信号丢弃(信号没有了)
此例子:屏蔽SIGINT,SIGQUIT为例
阻塞信号代码实现思路:
第一步:定义一个集合,该集合用来存放所有你要屏蔽的信号
集合:用来把多个需要屏蔽的信号保存起来
sigset_t类型本质是个整型数组
#define SIGINT 2
//定义集合变量
sigset_t myset;
//把要屏蔽的信号添加到该集合变量
sigemptyset(&myset);
sigaddset(&myset,SIGINT);
sigaddset(&myset,SIGQUIT);
第二步:调用sigprocmask函数,把第一步集合中所有的信号设置屏蔽
sigprocmask(SIG_BLOCK,&myset,备份原来已有的屏蔽信号的)
*/
int main()
{
//定义集合变量,本质就是数组
sigset_t myset;
//清空集合
sigemptyset(&myset);
//添加两个需要屏蔽的信号
sigaddset(&myset,SIGINT);
sigaddset(&myset,SIGQUIT);
//设置屏蔽刚才集合中的所有信号
sigprocmask(SIG_BLOCK,&myset,NULL);
//新增一个信号
//sigaddset(&myset,SIGHUP);
//删除一个信号
sigdelset(&myset,SIGQUIT);
sigset_t oldset;
sigemptyset(&oldset);
//再次设置信号的屏蔽,oldset是把当前已经设置屏蔽的信号做备份的
sigprocmask(SIG_BLOCK,&myset,&oldset);
//证明oldset的确把之前设置屏蔽的信号做了备份
if (sigismember(&oldset,SIGINT))
printf("INT存在!\n");
if (sigismember(&oldset,SIGQUIT))
printf("QUIT存在!\n");
if(sigismember(&oldset,SIGHUP))
printf("HUP存在!\n");
return 0;
}
示例:阻塞后的现象
#include "myhead.h"
int main(int argc,char **argv)
{
//定义集合变量,就是数组
sigset_t myset;
//清空集合
sigemptyset(&myset);
//添加两个需要屏蔽信号
sigaddset(&myset,SIGINT);
sigaddset(&myset,SIGQUIT);
//设置屏蔽刚才集合中的所有信号
sigprocmask(SIG_BLOCK,&myset,NULL);
pause();
return 0;
}
阻塞信号与忽略信号对比
示例:设置阻塞信号后收不到被屏蔽的信号,解除阻塞后可以接收到且马上开始处理
#include "myhead.h"
int main()
{
//定义集合变量,本质就是数组
sigset_t myset;
//清空集合
sigemptyset(&myset);
//添加两个需要屏蔽的信号
sigaddset(&myset,SIGINT);
sigaddset(&myset,SIGQUIT);
//设置屏蔽刚才集合中的所有信号
sigprocmask(SIG_BLOCK,&myset,NULL);
for (int i = 0; i < 10; i++)
{
printf("此时发送SIGINT和SIGQUIT不会被终止\n");
sleep(1);
}
//解除信号屏蔽
sigprocmask(SIG_UNBLOCK,&myset,NULL);
for (int i = 0; i < 10; i++)
{
printf("此时再发送信号会被终止\n");
sleep(1);
}
pause();
return 0;
}
运行结果:如果在第一个for循环的时候发了信号会被屏蔽,等到信号一解除马上开始处理之前被屏蔽的信号
示例:对比忽略信号
#include "myhead.h"
/*
此例子对比一下信号的忽略跟信号的屏蔽
*/
int main(int argc,char **argv)
{
int i;
signal(SIGINT,SIG_IGN);
signal(SIGQUIT,SIG_IGN);
for(i=0; i<10; i++)
{
printf("此时外界发送INT或者QUIT,对我没有影响,我忽略了他们!\n");
sleep(1);
}
//偷偷摸摸恢复两个信号的默认动作
signal(SIGINT,SIG_DFL);
signal(SIGQUIT,SIG_DFL);
//再运行
for(i=0; i<10; i++)
{
printf("不要太嘚瑟!\n");
sleep(1);
}
pause();
return 0;
}
运行结果:忽略的时候发送信号不会处理会直接丢弃,解除忽略后可以接收信号且之前发的信号不会被挂起滞留
raise函数
raise
是一个 C 标准库函数,用于向当前进程发送一个信号。它的作用相当于让进程向自身发送信号,从而触发信号处理机制或执行默认的信号处理操作。
函数原型
#include <signal.h>
int raise(int sig);
- 参数:
sig
:要发送的信号编号(如
SIGINT
,SIGTERM
等)。- 返回值:
- 成功返回
0
。- 失败返回非零值,并设置
errno
。
示例:
#include "myhead.h"
/*
raise自己给自己发送信号
*/
void fun(int sig)
{
printf("SIGINT收到了!\n");
}
int main(int argc,char **argv)
{
printf("程序运行了!\n");
signal(SIGINT,fun);
raise(SIGINT);
pause();
return 0;
}
alarm函数
alarm
是一个标准的 POSIX 函数,用于在指定时间(以秒为单位)后向当前进程发送一个 SIGALRM
信号。它常用于实现简单的定时器功能。
函数原型
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:
seconds
:指定多少秒后发送SIGALRM
信号。如果设置为0
,会取消之前的定时器。
示例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void alarm_handler(int signum) {
printf("捕获到 SIGALRM 信号!\n");
}
int main() {
signal(SIGALRM, alarm_handler); // 注册 SIGALRM 信号处理函数
printf("设置定时器:5秒后触发 SIGALRM\n");
alarm(5); // 设置5秒定时器
pause(); // 挂起进程,等待信号到来
printf("定时器触发后继续执行\n");
return 0;
}
POSIX 和 System V 对比
POSIX 和 System V 是两套 进程间通信(IPC)机制规范,它们都用于进程之间的数据传递,但历史不同、风格不同、API 不同。
特性 | POSIX IPC | System V IPC |
---|---|---|
📚 标准来源 | POSIX.1(IEEE 标准) | AT&T System V UNIX |
⏰ 发展年代 | 相对较新(更现代) | 较早(历史悠久) |
👀 可读性 | 接口更简单、易懂 | 接口偏复杂、风格老旧 |
🧩 命名方式 | 通过**名称(name)**标识资源 | 通过**key(key_t)**标识资源 |
⚙️ API 接口 | 使用 shm_open , mmap , sem_open , mq_open 等 | 使用 shmget , msgget , semget 等 |
📦 支持类型 | 共享内存、消息队列、信号量(全都有) | 同样支持共享内存、消息队列、信号量 |
🧼 资源清理 | 支持自动清理(如 O_UNLINK ) | 资源需手动移除(如 msgctl(..., IPC_RMID) ) |
👥 多用户可见性 | 支持共享名称,多个进程可以通过路径访问 | 必须用 ftok() 生成 key,稍麻烦 |
🧱 内核对象存储 | 文件系统(虚拟路径 /dev/shm 、/dev/mqueue ) | 直接在内核内存中维护 |
🔒 同步机制 | 支持阻塞、非阻塞、通知等 | 支持,但较原始 |
🛠️ 使用难度 | 更易于学习和维护 | 灵活但复杂 |
示例
System V 消息队列
int msgid = msgget(ftok("/tmp", 65), IPC_CREAT | 0666);
msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), 0, 0);
msgctl(msgid, IPC_RMID, NULL);
POSIX 消息队列
mqd_t mqd = mq_open("/myqueue", O_CREAT | O_RDWR, 0666, NULL);
mq_send(mqd, "hello", strlen("hello") + 1, 0);
mq_receive(mqd, buffer, sizeof(buffer), NULL);
mq_close(mqd);
mq_unlink("/myqueue");