Linux进程7-signal信号处理方式验证、可重入函数举例、信号集函数验证、信号集阻塞验证
目录
1. signal函数
1.1进程接收到信号后的处理方式
1.2 signal 函数
1.2.1 signal 函数默认处理
1.2.2 signal 函数忽略处理
1.2.3 signal 函数自定义处理
1.2.4 signal 函数返回值
2.可重入函数
2.1如何判断函数是否可重入
2.2自定义信号处理函数举例
2.2.1 sleep
2.2.2 alarm
2.2.3 read
2.2.4 lseek
3.信号集
3.1. 基本概念与作用
3.2. 核心API函数
3.2.1 sigemptyset
3.2.2 sigfillset
3.2.3 sigismember
3.2.4 sigaddset
3.2.5 sigdelset
3.3 程序验证
4.信号阻塞集
4.1函数原型
1. signal函数
1.1进程接收到信号后的处理方式
1.2 signal 函数
signal 函数原型:
#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 函数默认处理
程序:
#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 函数忽略处理
程序:
#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 函数自定义处理
程序:
#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 函数返回值
程序:
#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.可重入函数
可重入函数与不可重入函数的区别
特性 | 可重入函数 | 不可重入函数 |
---|---|---|
数据存储 | 使用局部变量或参数 | 依赖全局/静态变量或共享资源 |
线程安全性 | 支持多线程并发调用 | 可能导致数据竞争或状态错乱 |
示例函数 | localtime_r 、strtok_r | localtime 、strtok |
2.1如何判断函数是否可重入

2.2自定义信号处理函数举例
2.2.1 sleep
程序:
#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
程序:
#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
程序:
#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:
#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);//只写打开,不存在创建,存在清0fd = 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:
#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);//读写打开,不存在创建,存在清0fd = 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
初始化一个空的信号集#include <signal.h>
int sigemptyset(sigset_t *set);功能:
初始化由 set 指向的信号集,清除其中所有的信号即初始化一个空信号集。参数:
set:信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。返回值:
成功返回 0,失败返回 -1。
3.2.2 sigfillset
初始化一个满的信号集
#include <signal.h>
int sigfillset(sigset_t *set);功能:
初始化信号集合 set, 将信号集合设置为所有信号的集合。参数:
信号集标识的地址,以后操作此信号集,对 set 进行操作就可以了。返回值:
成功返回 0,失败返回 -1。
3.2.3 sigismember
判断某个集合中是否有某个信号
#include <signal.h>
int sigismember(const sigset_t *set,int signum);功能:
查询 signum 标识的信号是否在信号集合 set 之中。参数:
set:信号集标识符号的地址。
signum:信号的编号。返回值:
在信号集中返回 1,不在信号集中返回 0
错误,返回 -1
3.2.4 sigaddset
向某个集合中添加一个信号
#include <signal.h>
int sigaddset(sigset_t *set, int signum);功能:
将信号 signum 加入到信号集合 set 之中。参数:
set:信号集标识的地址。
signum:信号的编号。返回值:
成功返回 0,失败返回 -1。
3.2.5 sigdelset
从某个信号集中删除一个信号
#include <signal.h>
int sigdelset(sigset_t *set, int signum);功能:
将 signum 所标识的信号从信号集合 set 中删除。参数:
set:信号集标识的地址。
signum:信号的编号。返回值:
成功:返回 0
失败:返回 -1
3.3 程序验证
程序:
#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函数原型
#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)。
程序:
#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,立即响应,程序运行结束。