Linux进程7-signal信号处理方式验证、可重入函数举例、信号集函数验证、信号集阻塞验证

目录

[1. signal函数](#1. signal函数)

1.1进程接收到信号后的处理方式

[1.2 signal 函数](#1.2 signal 函数)

[1.2.1 signal 函数默认处理](#1.2.1 signal 函数默认处理)

[1.2.2 signal 函数忽略处理](#1.2.2 signal 函数忽略处理)

[1.2.3 signal 函数自定义处理](#1.2.3 signal 函数自定义处理)

[1.2.4 signal 函数返回值](#1.2.4 signal 函数返回值)

2.可重入函数

2.1如何判断函数是否可重入

2.2自定义信号处理函数举例

[2.2.1 sleep](#2.2.1 sleep)

[2.2.2 alarm](#2.2.2 alarm)

[2.2.3 read](#2.2.3 read)

[2.2.4 lseek](#2.2.4 lseek)

3.信号集

[3.1. ‌基本概念与作用‌](#3.1. ‌基本概念与作用‌)

[3.2. ‌核心API函数‌](#3.2. ‌核心API函数‌)

[3.2.1 sigemptyset](#3.2.1 sigemptyset)

[3.2.2 sigfillset](#3.2.2 sigfillset)

[3.2.3 sigismember](#3.2.3 sigismember)

[3.2.4 sigaddset](#3.2.4 sigaddset)

[3.2.5 sigdelset](#3.2.5 sigdelset)

[3.3 程序验证](#3.3 程序验证)

4.信号阻塞集

4.1函数原型


1. signal函数

1.1进程接收到信号后的处理方式

1 、执行系统默认动作
2 、忽略此信号
3 、执行自定义信号处理函数 。程序中可用函数 signal() 改变信号的处理方式。
注意:SIGKILL和SIGSTOP这两个信号 只能以默认的方式处理,不能忽略或者捕捉( 执行自定义信号处理函数 )。

1.2 signal 函数

signal 函数原型:

objectivec 复制代码
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);

功能:
注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),
即确定收到信号后处理函数的入口地址。

参数:
signum:信号编号
handler 的取值:
    忽略该信号:SIG_IGN
    执行系统默认动作:SIG_DFL
    自定义信号处理函数:信号处理函数名

返回值:
成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址。
失败:返回 SIG_ERR

1.2.1 signal 函数默认处理

程序:

objectivec 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
	int num = 0;
	
	//以默认的方式处理信号
	//终端按下 Ctrl + C 
     if(signal(SIGINT, SIG_DFL) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    else if(signal(SIGQUIT, SIG_DFL) == SIG_ERR)
    {  //终端按下 Ctrl + \ 
        perror("fail to signal");
        exit(1);
    }  
    else if(signal(SIGTSTP, SIG_DFL) == SIG_ERR)
    {  //终端按下 Ctrl + z 
        perror("fail to signal");
        exit(1);
    }
    else
    {}
    
   while(1)
    {
        num++;
        printf("程序运行第 %d 次\n", num);
        sleep(2);
    }
    
    return 0;
}

运行结果:分别运行 ./a.out,执行不同的终止信号。

1.2.2 signal 函数忽略处理

程序:

objectivec 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
	int num = 0;
	
	//以默认的方式处理信号
	//终端按下 Ctrl + C 
     if(signal(SIGINT, SIG_IGN) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    else if(signal(SIGQUIT, SIG_IGN) == SIG_ERR)
    {  //终端按下 Ctrl + \ 
        perror("fail to signal");
        exit(1);
    }  
    else if(signal(SIGTSTP, SIG_IGN) == SIG_ERR)
    {  //终端按下 Ctrl + z 
        perror("fail to signal");
        exit(1);
    }
    else
    {}
    
   while(1)
    {
        num++;
        printf("程序运行第 %d 次\n", num);
        sleep(3);
    }
    
    return 0;
}

运行结果:信号已忽略的方式执行,终端输入终止命令,程序还会一直运行。

程序只能在另外一个终端,执行kill 命令终止运行进程。

1.2.3 signal 函数自定义处理

程序:

objectivec 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>

void handler(int sig) 
{
	if(sig == SIGINT)
	{
		printf("终端按下 Ctrl + C ,信号编号(%d)\n", sig);
	}
	else if(sig == SIGQUIT)
	{
		printf("终端按下 Ctrl + \\ ,信号编号(%d)\n", sig);
	}
	else if(sig == SIGTSTP)
	{
		printf("终端按下 Ctrl + z ,信号编号(%d)\n", sig);
	} 
}

int main()
{
	int num = 0;
	
	//以自定义的方式处理信号
	//终端按下 Ctrl + C 
     if(signal(SIGINT, handler) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    else if(signal(SIGQUIT, handler) == SIG_ERR)
    {  //终端按下 Ctrl + \ 
        perror("fail to signal");
        exit(1);
    }  
    else if(signal(SIGTSTP, handler) == SIG_ERR)
    {  //终端按下 Ctrl + z 
        perror("fail to signal");
        exit(1);
    }
    else
    {}
    
   while(1)
    {
        num++;
        printf("程序运行第 %d 次\n", num);
        sleep(3);
    }
    
    return 0;
}

运行结果:程序执行对应的自定义函数。

1.2.4 signal 函数返回值

程序:

objectivec 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>

typedef void (*sighandler_t)(int);
sighandler_t old_handler;

//自定义信号处理
void handler(int sig)
{
    printf(" 自定义信号处理 信号编号: %d\n", sig);

	//使用原 old_handler默认处理函数执行
     if(signal(SIGINT, old_handler) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
}

int main() 
{
	int num = 0;
	
    // 注册 SIGINT 处理函数,并保存原处理函数地址(默认处理)
     old_handler = signal(SIGINT, handler);
     //signal()函数的返回值是signal()函数上一次的行为
    //old_handler 保存的是 默认处理函数 SIG_DEF 地址
    //SIGINT :终端按下 Ctrl + C 终止
    
    if (old_handler == SIG_ERR) 
    {
        perror("signal() failed");
        exit(1);
    }
       
    while(1)
    {
        num++;
        printf("程序运行第 %d 次\n", num);
        sleep(2);
    }
    return 0;
}

运行结果:程序运行后,main函数第一次收到Ctrl + C 终止信号,会先执行自定义信号处理函数(signal函数,第二个参数传参不为:默认、忽略处理,传入了自定义处理函数)。在自定义处理函数中,第二次收到Ctrl + C 终止信号,执行上次 信号默认处理方式(main函数第一次signal函数的返回值,为 信号信号默认处理入口地址)终止程序运行。

2.可重入函数

可重入函数‌是一种在多线程或中断场景下能够安全并发执行的函数,其核心特性在于不会因多次调用导致数据竞争或状态混乱。
编写可重入函数:
1、不使用(返回)静态的数据、全局变量(除非用信号量互斥)。
2、不调用动态内存分配、释放的函数。
3、不调用任何不可重入的函数(如标准 I/O 函数)。
注:
即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,首先要保存 errno 的值,结束时,再恢复原值。因为,信号处理过程中,errno 值随时可能被改变。

可重入函数与不可重入函数的区别

特性 可重入函数 不可重入函数
数据存储 使用局部变量或参数 依赖全局/静态变量或共享资源
线程安全性 支持多线程并发调用 可能导致数据竞争或状态错乱
示例函数 localtime_rstrtok_r localtimestrtok

2.1如何判断函数是否可重入

(1)查阅文档 man 手册中会明确标注函数是否为异步信号安全(Async-Signal-Safe)。
例如: man 7 signal # 查看信号安全的函数列表
终端执行: man 7 signal > signal.txt 命令,将数据重定向到signal.txt,查看

(2)POSIX 标准中,以 _r 结尾的函数通常是可重入的(如 strtok_r)。

2.2自定义信号处理函数举例

2.2.1 sleep

程序:

objectivec 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

void handler(int sig)
{
    printf(" Ctrl + C 自定义信号处理函数执行 \n");
}

int main(int argc, char *argv[])
{
	int num = 0;
	// SIGINT :终端按下 Ctrl + C 终止
    signal(SIGINT, handler);

    //sleep是一个可重入函数,但是当执行信号处理函数之后,不会回到原本的位置继续睡眠
    sleep(10);
    printf("sleep 结束\n");

    //alarm函数是一个可重入函数,当他执行时,如果有信号产生并执行信号处理函数,执行完毕后,会继续运行
    //alarm(10);

    while(1)
    {
         num++;
        printf("程序运行第 %d 次\n", num);
        sleep(1);
    }

	return 0;
}

运行结果:程序运行时,先睡眠10s,在执行后续程序。在睡眠3s时终端按下 ctrl+c 终止命令,直接执行完自定义信号处理函数,sleep(10)也运行结束,直接执行while(1)里面的程序。

2.2.2 alarm

程序:

objectivec 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

void handler(int sig)
{
    printf(" Ctrl + C 自定义信号处理函数执行 \n");
}

int main(int argc, char *argv[])
{
	int num = 0;
	// SIGINT :终端按下 Ctrl + C 终止
    signal(SIGINT, handler);

    //sleep是一个可重入函数,但是当执行信号处理函数之后,不会回到原本的位置继续睡眠
    //sleep(10);

    //alarm函数是一个可重入函数,当他执行时,如果有信号产生并执行信号处理函数,执行完毕后,会继续运行
    alarm(10);

    while(1)
    {
         num++;
        printf("程序运行第 %d 次\n", num);
        sleep(1);
    }

	return 0;
}

运行结果:程序运行3秒后,按下ctrl + c终止信号,handler函数执行结束,回到alarm函数继续运行,由于alarm闹钟定时10秒程序运行结束,所以程序继续运行7秒后退出。

2.2.3 read

程序:

objectivec 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

void handler(int sig)
{
    printf(" Ctrl + C 自定义信号处理函数执行 \n");
}

int main(int argc, char *argv[])
{
	// SIGINT :终端按下 Ctrl + C 终止
    signal(SIGINT, handler);

    char buf[32] = "";
    
    //read也是一个可重入函数,在等待终端输入时,如果产生信号并执行信号处理函数,信号处理
    //函数执行完毕后,可以继续输入数据,read可以读取到信号处理函数之后的数据
    if(read(0, buf, 20) == -1)//从终端读取内容,0:stdin 标准输入
    {
        perror("fail to read");
        exit(1);
    }

    printf("buf = [%s]\n", buf);

	return 0;
}

运行结果:文件IO read函数从终端读取输入的数据,按下ctrl + c终止信号,handler函数执行结束,回到read函数继续运行,再次输入数据,按下回车,读取到的数据为最后一次数据的数据。

2.2.4 lseek

程序1:

objectivec 复制代码
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int fd;

//信号自定义处理函数,调用 lseek 实现定时调整文件偏移量
void handler(int sig)  
{
    //重定位偏移量为SEEK_END 文件末尾
    int len2 = lseek(fd, 0, SEEK_END); 
    printf("总偏移量:%ld !\n", len2);
    exit(1);
}

int main() 
{
	//读写打开
    //fd = open("./file1.txt", O_RDWR);

    //只写打开,不存在创建,存在清0
	fd = open("./file2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
	if (-1 == fd)
	{
		perror("fail to open file ");
		return -1;
	}

	//handler 信号的自定义处理函数
    signal(SIGALRM, handler);
    alarm(3);  // 3秒后触发一次 SIGALRM信号
    
    int len1 = 5;
    int len = 0;
    while (1) 
    {
        //lseek(fd, 0, SEEK_SET);
        
        //从开头位置偏移len1个位置
        len = lseek(fd, len1, SEEK_SET);
        len1+=len;
        printf("偏移量len:%ld ,下次开始偏移位置设置len1:%ld!\n",len, len1);
        write(fd, "abcd", 4);//文件IO操作,写入数据
        sleep(1);
    }
    return 0;
}

运行结果:在alarm闹钟信号到达后,在信号自定义处理函数,调用 lseek 实现定时调整文件偏移量。

文本中写入的数据:最后一次写入完abcd的偏移量为24.

程序2:

objectivec 复制代码
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int fd;

//信号自定义处理函数,调用 lseek 实现定时调整文件偏移量
void handler(int sig)  
{
    //重定位偏移量为SEEK_END 文件末尾
    int len2 = lseek(fd, 0, SEEK_END); 
	printf("总偏移量:%ld !\n", len2);
	
	//重定位偏移量为SEEK_SET 文件开头
     len2 = lseek(fd, 0, SEEK_SET); 
    printf("读取偏移量:%ld !\n", len2);

	char buf[64] = {0};
 
   ssize_t  num = read(fd, buf, sizeof(buf));
    	
   // printf("strlen(buf) = %d\n", strlen(buf));
    printf("read size = %d\n", num);//读取字节大小
    printf("read buf = %s\n", buf);
     
   close(fd);  	 	
    
    exit(1);//退出
}

int main() 
{
	//读写打开
    //fd = open("./file1.txt", O_RDWR);

    //读写打开,不存在创建,存在清0
	fd = open("./file2.txt", O_RDWR | O_CREAT | O_TRUNC, 0664);
	if (-1 == fd)
	{
		perror("fail to open file ");
		return -1;
	}

	//handler 信号的自定义处理函数
    signal(SIGALRM, handler);
    alarm(3);  // 3秒后触发一次 SIGALRM信号
    
    int len1 = 0;
    int len = 0;
    while (1) 
    {
        //lseek(fd, 0, SEEK_SET);
        
        //从开头位置偏移len1个位置
        len = lseek(fd, len1, SEEK_SET);
        len1+=5;
        printf("偏移量len: %ld ,下次开始偏移位置设置len1: %ld \n",len, len1);
        write(fd, "abcd ", 5);//文件IO操作,写入数据
        sleep(1);
    }
    return 0;
}

运行结果:main函数向txt文本写入数据,在alarm闹钟信号到达后,在信号自定义处理函数,调用 lseek 实现定时调整文件偏移量,使用read函数读取内容。

3.信号集

3.1. ‌基本概念与作用‌

信号集(Signal Set)是Linux中用于批量管理信号的数据结构,本质为位掩码(bitmask),每个bit对应一种信号状态。POSIX标准定义了sigset_t数据类型来表示信号集。

主要作用包括: ‌

  • 信号屏蔽‌:通过设置阻塞信号集,控制进程/线程对特定信号的响应
  • 状态查询‌:通过未决信号集(pending)查看已产生但未处理的信号 ‌
  • 批量操作‌:支持对多个信号进行统一处理,如初始化、添加、删除等

sigset.h存储路径:终端输出 sudo find /usr/include -name "sigset.h"

sigset_t是一个数组:

3.2. ‌核心API函数

函数 功能 示例场景
sigemptyset() 初始化空信号集 创建新信号集前的初始化操作
sigfillset() 初始化包含所有信号的完全集 需要屏蔽所有信号时使用
sigaddset() 向信号集添加指定信号 选择性屏蔽SIGINT/SIGTERM等信号
sigdelset() 从信号集移除指定信号 解除对特定信号的阻塞
sigismember() 检查信号是否在集合中 调试时验证信号集状态
sigprocmask() 修改进程级信号屏蔽字 主进程全局信号控制
pthread_sigmask() 修改线程级信号屏蔽字 多线程环境下隔离信号处理
sigwait() 阻塞等待指定信号到达 异步事件同步化处理

3.2.1 sigemptyset

objectivec 复制代码
初始化一个空的信号集

#include <signal.h>
int sigemptyset(sigset_t *set);

功能:
初始化由 set 指向的信号集,清除其中所有的信号即初始化一个空信号集。

参数:
set:信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。

返回值:
成功返回 0,失败返回 -1。

3.2.2 sigfillset

objectivec 复制代码
初始化一个满的信号集
#include <signal.h>
int sigfillset(sigset_t *set);

功能:
初始化信号集合 set, 将信号集合设置为所有信号的集合。

参数:
信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。

返回值:
成功返回 0,失败返回 -1。

3.2.3 sigismember

objectivec 复制代码
判断某个集合中是否有某个信号
#include <signal.h>
int sigismember(const sigset_t *set,int signum);

功能:
查询 signum 标识的信号是否在信号集合 set 之中。

参数:
set:信号集标识符号的地址。
signum:信号的编号。

返回值:
在信号集中返回 1,不在信号集中返回 0
错误,返回 -1

3.2.4 sigaddset

objectivec 复制代码
向某个集合中添加一个信号
#include <signal.h>
int sigaddset(sigset_t *set, int signum);

功能:
将信号 signum 加入到信号集合 set 之中。

参数:
set:信号集标识的地址。
signum:信号的编号。

返回值:
成功返回 0,失败返回 -1。

3.2.5 sigdelset

objectivec 复制代码
从某个信号集中删除一个信号
#include <signal.h>
int sigdelset(sigset_t *set, int signum);

功能:
将 signum 所标识的信号从信号集合 set 中删除。

参数:
set:信号集标识的地址。
signum:信号的编号。

返回值:
成功:返回 0
失败:返回 -1

3.3 程序验证

程序:

objectivec 复制代码
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    //创建一个信号集
	sigset_t set;
	int ret = 0;

    //初始化一个空的信号集
	sigemptyset(&set);

	printf("判断SIGINT信号是否在信号集中\n");
    //判断SIGINT信号是否在信号集中
	ret = sigismember(&set, SIGINT);
	if(ret == 0)
    {
	   printf("SIGINT该信号不在信号集 \n ret = %d\n", ret);
    }	

    //判断SIGQUIT信号是否在信号集中
	ret = sigismember(&set, SIGQUIT);
	if(ret == 0)
    {
	   printf("SIGQUIT该信号不在信号集 \n ret = %d\n", ret);
    }	

	 printf("将指定的信号添加到信号集中\n");
    //将指定的信号添加到信号集中
    sigaddset(&set, SIGINT);
	sigaddset(&set, SIGQUIT);
	
	ret = sigismember(&set, SIGINT);
	if(ret == 1)
	{	
        printf("SIGINT该信号在信号集 \n ret = %d\n", ret);
    }

    ret = sigismember(&set, SIGQUIT);
	if(ret == 1)
	{	
        printf("SIGQUIT该信号在信号集 \n ret = %d\n", ret);
    }
    
	printf("将指定的信号删除\n");
	ret =  sigdelset(&set, SIGQUIT);
	if(ret == 0)
	{	
        printf("SIGQUIT信号已在信号集删除 \n ret = %d\n", ret);
    }

	ret = sigismember(&set, SIGQUIT);
	if(ret == 0)
    {
	   printf("SIGQUIT该信号不在信号集 \n ret = %d\n", ret);
    }	
     
	return 0;
}

运行结果:

4.信号阻塞集

信号阻塞集是 Linux 进程或线程用来控制哪些信号会被暂时阻塞 的机制。被阻塞的信号不会立即递送给进程,而是进入"未决(Pending)"状态,直到解除阻塞后才被处理。

4.1函数原型

objectivec 复制代码
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能:
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,
新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

参数:
    how:操作类型:
    SIG_BLOCK:将 set 中的信号加入当前阻塞集(取并集)。
    SIG_UNBLOCK:将 set 中的信号从当前阻塞集中移除。
    SIG_SETMASK:直接将当前阻塞集替换为 set。

    set:要操作的信号集(若为 NULL,则 how 参数无效)。
    oldset:返回旧的阻塞集(可传 NULL)。

程序:

objectivec 复制代码
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	int i=0;
	
    //创建信号集并在信号集中添加信号
    sigset_t set;//创建一个信号集
	sigemptyset(&set);//初始化一个空的信号集
	sigaddset(&set, SIGINT);//将SIGINT信号添加到set信号集中

	while(1)
	{
        //将set信号集添加到信号阻塞集中
		sigprocmask(SIG_BLOCK, &set, NULL);
		for(i=0; i<5; i++)
		{
			printf(" SIGINT:ctrl+c 信号还在阻塞集中,不响应\n");
			sleep(2);
		}
		
        //将set信号集从信号阻塞集中删除
        sigprocmask(SIG_UNBLOCK, &set, NULL);
		for(i=0; i<5; i++)
		{
			printf(" SIGINT:ctrl+c 信号已不在阻塞集中,可以立即响应\n");
			sleep(2);
		}
	}
	
	return 0;
}

运行结果:(1)未退出阻塞集,按下终止信号ctrl+c,等待退出阻塞集,立即响应,程序运行结束。

(2)已退出阻塞集,按下终止信号ctrl+c,立即响应,程序运行结束。

相关推荐
一道秘制的小菜30 分钟前
AimRT从入门到精通 - 04RPC客户端和服务器
linux·运维·服务器·c++·aimrt
Brandon汐2 小时前
Linux中的系统延时任务和定时任务与时间同步服务和构建时间同步服务器
linux·运维
又逢乱世2 小时前
Ubuntu 安装 MySQL8
linux·运维·mysql·ubuntu
共享家95272 小时前
冯·诺依曼体系:现代计算机的底层逻辑与百年传承
linux
庐阳寒月2 小时前
linux多线(进)程编程——(10)信号
linux·c++·嵌入式
孞㐑¥2 小时前
Linux之基础开发工具(yum,vim,gcc,g++)
linux·c++·经验分享·笔记
Yang三少喜欢撸铁2 小时前
通过Docker部署Prometheus + Grafana搭建监控平台【超详细版】
linux·服务器·docker·容器·grafana·prometheus
一道秘制的小菜3 小时前
Linux第20节 --- inode和文件系统
linux·运维·服务器·c++·文件
BIN-XYB3 小时前
Ubuntu搭建Conda+Python开发环境
linux·运维·ubuntu
钟剑锋-JeffChong3 小时前
一文详解 Linux下的开源打印系统CUPS(Common UNIX Printing System)
linux·嵌入式·qt5·打印机·cups