目录
[1. signal函数](#1. signal函数)
[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 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.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 程序验证)
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_r 、strtok_r |
localtime 、strtok |
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,立即响应,程序运行结束。
