Linux 信号机制详解(二):信号的阻塞、屏蔽与定时控制

阻塞信号(屏蔽信号)

在 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 IPCSystem 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");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值