目录
- 前言
- 一、信号集基础API浅析
-
- [1.1 sigemptyset](#1.1 sigemptyset)
- [1.2 sigfillset](#1.2 sigfillset)
- [1.3 sigaddset](#1.3 sigaddset)
- [1.4 sigdelset](#1.4 sigdelset)
- [1.5 signismember](#1.5 signismember)
- [1.6 sigprocmask](#1.6 sigprocmask)
- [1.7 sigpending](#1.7 sigpending)
- [1.8 sigwait](#1.8 sigwait)
- 二、demo演练
-
- [2.1 sigismember检查信号](#2.1 sigismember检查信号)
- [2.2 主线程pthread_sigmask阻塞后无法捕捉到特定信号](#2.2 主线程pthread_sigmask阻塞后无法捕捉到特定信号)
- [2.3 主线程pthread_sigmask阻塞后sigwait()捕捉特定信号](#2.3 主线程pthread_sigmask阻塞后sigwait()捕捉特定信号)
- [2.4 sigfillset添加所有信号、alarm发送信号、ctrl+c退出](#2.4 sigfillset添加所有信号、alarm发送信号、ctrl+c退出)
- [2.5 示例4进化:添加线程,volatile 从内存读取最新值](#2.5 示例4进化:添加线程,volatile 从内存读取最新值)
- 三、信号集的应用场景
- 四、信号集VS信号量
前言
在 Linux 中,信号集(Signal Set)是一种用于管理和选择信号的机制。信号集可以用来屏蔽、限制或对特定信号进行操作,是信号处理的一个重要组成部分。信号是Linux系统中用于进程间通信的一种机制,它允许一个进程通知另一个进程发生了某种事件。
信号集的作用和用途可以概括如下:
-
信号屏蔽和处理:
信号集允许进程暂时屏蔽(忽略)或处理一组信号。进程可以设置一个信号集,指定在执行某些操作时不希望被某些信号打扰。这对于控制程序的执行流程和避免因信号处理不当导致的竞态条件非常重要。
-
原子操作:
对信号集的操作是原子的,这意味着在设置或清除信号集时,不会有其他信号处理函数同时修改信号集,从而保证了操作的一致性和安全性。
-
信号的阻塞和解除阻塞:
信号集可以用来阻塞一组信号,使得这些信号暂时不会影响进程。当进程准备好处理这些信号时,可以解除阻塞,允许信号被传递给进程。
-
信号的等待和检测:
进程可以使用信号集等待一组信号中的任何一个发生。这允许进程在等待信号时,不必无限期地阻塞,而是可以指定一个信号集,当信号集中的任何一个信号到达时,进程可以被唤醒并处理该信号。
-
提高程序的响应性和稳定性:
通过合理使用信号集,程序可以更好地响应外部事件,同时避免因信号处理不当导致的程序崩溃或数据不一致。
-
系统调用的配合使用:
信号集常与一些系统调用(如sigprocmask、sigsuspend、sigwait等)配合使用,以实现信号的灵活管理。
-
多线程程序中的信号处理:
在多线程程序中,信号集可以用来同步线程间的信号处理,确保信号的适当传递和处理。
一、信号集基础API浅析
1.1 sigemptyset
sigemptyset是初始化set所指向的信号集,让其中所有的信号的对应的比特位清零,表示该信号集不包含任何有效信号。
c
#include <signal.h>
int sigemptyset(sigset_t *set);
1.2 sigfillset
sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
c
#include <signal.h>
int sigfillset(sigset_t *set);
1.3 sigaddset
sigaddset把某个特定信号加上
c
#include <signal.h>
int sigaddset(sigset_t *set, int signo);
1.4 sigdelset
sigdelset把某个特定信号去掉
c
#include <signal.h>
int sigdelset(sigset_t *set, int signo);
1.5 signismember
signismember是为了判断某个信号是否在该信号集中。
c
#include <signal.h>
int sigismember(const sigset_t *set, int signo);
1.6 sigprocmask
sigprocmask函数用于检查或修改当前进程的信号屏蔽字(signal mask)。信号屏蔽字决定了在屏蔽期间哪些信号会被阻塞,即暂时不会被处理。
语法
c
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
- int how:操作标志,决定如何修改信号屏蔽字:
- SIG_BLOCK:把 set 指向的信号集中的信号添加到当前信号屏蔽字中。
- SIG_UNBLOCK:从当前信号屏蔽字中移除 set 指向的信号集中的信号。
- SIG_SETMASK:用 set 指向的信号集替换当前信号屏蔽字。
- const sigset_t *set:指向要修改的新信号集的指针。
- sigset_t *oldset:如果不为 NULL,则存储之前的信号屏蔽字
返回值
成功时返回 0。
失败时返回 -1,并设置 errno 以指示错误类型。
c
1.7 sigpending
用于获取当前进程挂起(未决)的信号集。未决信号是在被阻塞后尚未处理的信号。
c
#include <signal.h>
int sigpending(sigset_t *set);
1.8 sigwait
用于同步等待信号的到来。它将指定的信号集中的信号之一移出队列并返回其编号。
c
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
- const sigset_t *set:指向一个 sigset_t 类型的信号集变量,该信号集指定要等待的信号。
- int *sig:指向一个整数变量,用于存储被捕获的信号编号。
二、demo演练
2.1 sigismember检查信号
c
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t set;
// 初始化信号集为空
if (sigemptyset(&set) == -1) {
perror("sigemptyset");
return 1;
}
// 检查信号集是否包含 SIGINT
if (sigismember(&set, SIGINT)) {
printf("SIGINT is in the set\n");
} else {
printf("SIGINT is not in the set\n");
}
return 0;
}
程序运行输出:
c
SIGINT is not in the set
2.2 主线程pthread_sigmask阻塞后无法捕捉到特定信号
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
void handler(int s){
printf("handler :%d\n" ,s);
}
int main(int argc, char**argv)
{
signal(SIGINT , handler);
sigset_t mask;
sigaddset(&mask , SIGINT);
pthread_sigmask(SIG_BLOCK,&mask, NULL); //阻塞 SIGINT 信号
//用ctrl+\ 终止
while(1)
pause();
return 0;
}
程序运行输出:卡在死循环里。
去掉pthread_sigmask该行后,ctrl+c,SIGINT 信号绑定的程序handler()会正常执行。
2.3 主线程pthread_sigmask阻塞后sigwait()捕捉特定信号
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
//gcc 别忘了 -lpthread
void handler(int s){
printf("handler :%d\n" ,s);
}
int main(int argc, char**argv)
{
signal(SIGINT , handler);
sigset_t mask;
sigaddset(&mask , SIGINT);
pthread_sigmask(SIG_BLOCK,&mask, NULL);
//用ctrl+\ 终止
// while(1)pause();
int sig = 0;
while(1){
if (sigwait(&mask,&sig) != 0 ){
perror("sigwait :");
continue;
}
printf(" ! main got signal : %d\n" , sig);
}
return 0;
}
程序运行输出: 按下ctrl+c,主线程捕捉到信号
c
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
^C ! main got signal : 2
- sigwait(&mask, &sig) 是一个阻塞操作,它将等待 mask 中的信号到来。尽管主线程设置了对 SIGINT 的屏蔽,但是 sigwait() 允许等待到这个信号。一旦 SIGINT 信号到达,由 Ctrl+C 发送,信号会被添加到信号队列,而 sigwait() 允许线程在收到信号时进行处理。此时不会调用 handler,而是直接将信号交给 sigwait(),并变成返回值。
2.4 sigfillset添加所有信号、alarm发送信号、ctrl+c退出
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
volatile sig_atomic_t goon = 1;
static void * t1(void *arg){
sigset_t * mask = (sigset_t *)arg;
int sig = 0;
while(1){
if ( sigwait(mask,&sig) != 0 ){
perror(" thread sigwait :");
continue;
}
printf("thread got signal : %d\n" , sig);
if(SIGINT == sig){
goon = 0;
break;
}
}
}
int main(int argc, char**argv)
{
sigset_t mask;
sigfillset(&mask); //添加所有信号
pthread_sigmask(SIG_BLOCK,&mask, NULL);
pthread_t t;
pthread_create(&t,0,t1,&mask);
int sig = 0;
while(goon){
alarm(1);
sleep(1);
}
pthread_join(t,0);
puts("end");
return 0;
}
程序运行输出: 每秒发送对应信号并捕获输出,按下ctrl+c,进程退出
c
thread got signal : 14
thread got signal : 14
thread got signal : 14
thread got signal : 14
thread got signal : 14
thread got signal : 14
^Cthread got signal : 2
end
2.5 示例4进化:添加线程,volatile 从内存读取最新值
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
volatile sig_atomic_t goon = 1;
static void * worker (void *arg){
while(goon){
printf("worker is running , tid:%ld\n" , pthread_self());
sleep(5);
}
puts("worker is done");
}
static void * sig_handler_thread(void *arg){
sigset_t * mask = (sigset_t *)arg;
int sig = 0;
pthread_t tid = pthread_self();
while(1){
if ( sigwait(mask,&sig) != 0 ){
printf("sigwait error : %s\n" , strerror(errno));
continue;
}
printf("thread :%ld got signal : %d\n" , tid,sig);
if(SIGINT == sig){
goon = 0;
break;
}
}
}
int main(int argc, char**argv)
{
sigset_t mask;
sigfillset(&mask);
pthread_sigmask(SIG_BLOCK,&mask, NULL);
pthread_t tid1, tid2;
pthread_create(&tid1,0,sig_handler_thread,&mask);
pthread_create(&tid2,0,worker,NULL);
int sig = 0;
while(goon){
alarm(1);
sleep(1);
}
pthread_join(tid2,0);
pthread_join(tid1, NULL);
puts("end");
return 0;
}
程序运行输出: 与上例类似,自行理解。
c
worker is running , tid:139906703542016
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
worker is running , tid:139906703542016
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
thread :139906711934720 got signal : 14
worker is running , tid:139906703542016
thread :139906711934720 got signal : 14
^Cthread :139906711934720 got signal : 2
worker is done
end
三、信号集的应用场景
定义: 信号集是一种用于管理多个信号的机制,允许进程或线程一起阻塞或等待多个信号。
常用场景:
-
信号处理:
在需要处理特定信号时,可以使用信号集来定义要阻塞的信号。例如,在多线程程序中,可以屏蔽某些信号,使得信号在关键代码执行期间不会中断,从而避免状态不一致。
-
线程同步:
当多个线程需要等待特定事件(例如消息到达或信号触发)时,可以使用 sigwait() 等函数来实现。这样可以避免在特定上下文中出现信号直接调用处理程序的问题,也能够更好地管理线程状态。
-
状态监控:
信号集可以用来监控系统状态变化。例如,在处理 Unix/Linux 系统的守护进程时,可以使用信号集进行状态更新和管理,从而处理后台服务的停启和运行状态。
-
多线程信号传递:
在多线程应用中,使用信号集可以协调多个线程之间的信号传递,确保在运行时能够安全的捕获并处理来自外部的信号,如终止信号或定时信号。
四、信号集VS信号量
信号量: 主要用于控制对共享资源的访问,帮助管理并发操作,适合资源限制和冲突的问题。
信号集: 主要用于信号的处理和管理,帮助确保在多线程和多进程环境中有效捕获和处理信号。