异步处理方式之信号(4):信号集的使用
发布日期:2021-06-23 04:43:38 浏览次数:6 分类:技术文章

本文共 10671 字,大约阅读时间需要 35 分钟。

8 信号集

8.1信号集的基本操作函数

​ 前面我们已经知道,不同的信号的编号可能超过一个整型量所包含的位数,因此我们不能使用整型量中的一位来表示一种信号,也就是说我们不能使用整型变量来表示信号集。POSIX.1定义了一种新的数据类型:信号集(sigset_t), 并且还定义了5个处理信号的函数。

#include 
int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo); 上述四个函数返回值:若成功返回0;失败返回-1int sigismember(const sigset_t *set, int signo); 返回值:为真返回1;为假返回0
  • 函数sigemptyset初始化由set指向的信号集,清除其中所有的信号;
  • 函数sigfillset初始化set指向的信号集,使其包含所有的信号;

所有的应用程序在使用信号集之前,都需要调用sigemptyset或者sigfillset对信号集进行初始化。一旦初始化了一个信号集,以后便可以对信号集进行增加、删除特定的信号:

  • 函数sigaddset将一个信号添加到已有的信号集中;
  • 函数sigdelset从一个现有的信号集中删除一个特定的信号;

对所有以信号集为参数的函数,总是以信号集的地址作为传递的参数。

​ 这个原因在于:因为我们要在函数中修改信号集中的信号,因此需要地址传递方式(简单的值传递是行不通的),否则无法实现真正的修改。

8.2 函数sigprocmask: 进程屏蔽字

​ 我们在前面已经提到过:进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。进程的信号屏蔽字是通过函数sigprocmask来进行检测或修改的,或者同时进行检测和修改。

#include 
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); 返回值:成功返回0;失败返回-1;
  • 首先,如果oset是非空指针,那么进程的当前信号屏蔽字通过oset返回
  • 其次,如果set为非空指针,则参数how只是如何修改当前进程的信号屏蔽字;下表便是how的取值以及相关的说明。
  • 如果set为空指针,则不修改该进程的信号屏蔽字,how值是无意义的
序号 how 说明
1 SIG_BLOCK 将set中的信号添加到当前进程的信号屏蔽字中(两者取或运算,即并集)
2 SIG_UNBLOCK 将set中的信号从当前进程信号屏蔽字中删除(set补集的交集)
3 SIG_SETMASK 将set设置为当前进程的信号屏蔽字(不关心原进程的信号屏蔽字)

在调用sigprocmask后如果有任何悬而未决、不再阻塞的信号(原来信号是阻塞,现在改为非阻塞),则在sigprocmask函数返回前至少将其中之一递送给该进程。

示例:获取当前进程屏蔽字

/*************************************************************************             > File Name: sigprocmask_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 08时15分06秒 ************************************************************************/#include 
#include
#include
/*获取当前进程的信号屏蔽字,并测试包括哪些信号。*/void getSigProcMask(){
sigset_t set; printf("Enter %s\n", __func__); if(sigemptyset(&set)!=0){
perror("sigemptyset error:"); }else if(sigprocmask(0,NULL,&set)!=0){
perror("sigprocmask error:"); }else{
printf("Sigprocmask contains following signals:"); if(sigismember(&set, SIGINT)) printf(" SIGINT"); if(sigismember(&set, SIGQUIT)) printf(" SIGQUIT"); if(sigismember(&set, SIGALRM)) printf(" SIGALRM"); if(sigismember(&set, SIGCHLD)) printf(" SIGCHLD"); printf("\n"); } printf("Exit %s\n", __func__);}

8.3 函数sigpending

​ sigpending函数返回一个信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前悬而未决的。该信号集通过set参数返回。

#include 
int sigpending(sigset_t *set); 返回值:成功返回0;失败返回-1;

示例:查询当前挂起的信号

/*************************************************************************             > File Name: sigpending_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 08时36分45秒 ************************************************************************/#include 
#include
/*初始化进程屏蔽字*/void testPendingSignal(){
/*定义三个信号集*/ sigset_t new_set; /*,用来设置新的屏蔽字*/ sigset_t old_set; /*,用来获取之前的信号屏蔽字*/ sigset_t pending_set; /*,获取正在挂起的信号屏蔽字*/ /*清空三个信号集*/ sigemptyset(&new_set); sigemptyset(&old_set); sigemptyset(&pending_set); /*将SIGQUIT信号添加至信号集中*/ sigaddset(&new_set, SIGINT); /*设置当前进程的信号屏蔽字*/ if(sigprocmask(SIG_BLOCK, &new_set, &old_set)!=0){
printf("%s error!!!\n", __func__); return; } printf("sleeping.......\n"); sleep(5); if(sigpending(&pending_set)!=0){
printf("%s error!!!\n", __func__); return; } if(sigismember(&pending_set, SIGINT)) printf("SIGINT is in pending_set\n"); if(sigismember(&pending_set, SIGQUIT)) printf("SIGQUIT is in pending_set\n"); /*将进程的信号屏蔽字回复到修改之前的状态*/ if(sigprocmask(SIG_SETMASK, &old_set, NULL)!=0){
printf("%s error!!!\n", __func__); return; } printf("%s...exit....\n", __func__);}void main(int argc, char *argv[]){
testPendingSignal(); while(1){
pause(); }}

程序的执行结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out sleeping.......^C^C^C^C^CSIGINT is in pending_settoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

​ 因为我们在程序中设置了进程屏蔽字,所以进程会阻塞SIGINT信号。在sleep(5)的睡眠等待的过程中,我连续按了5次"Ctrl+C“产生了五次SIGINT信号。但是由于进程暂时屏蔽该信号,不会讲该信号递送给进程,因此程序也不会响应(SIGINT的默认动作是结束当前进程),而当我重新恢复进程的信号屏蔽字后,先前产生的SIGINT信号被立即递送到进程(应该是在sigprocmask函数返回之前就递送了),因此函数testPendingSignal()的最后一行并没有打印出来。

为了解除对该信号的阻塞,我们用先前的信号屏蔽字重新设置了(SIG_SETMASK)进程的信号屏蔽字。

这样做的原因在于: 可能先前的进程已经屏蔽了该信号(例如其他函数接口设置了,但是自己并不知道)。如果我们简单的使用SIG_UNBLOCK来将信号从进程屏蔽字中删除可能会影响其他程序正常的功能。因此这里推荐使用SIG_SETMASK的方式重新设置修改之前信号屏蔽字。

8.4 函数sigaction

​ sigaction函数的功能是检查或者修改(也可以是检查和修改)与指定信号相关联的动作。 该函数是对signal函数的改进

#include 
int sigaction(int signo, const struct sigaction *restrict act, struct sinaction *restrict oact); 返回值:成功返回0;失败返回-1;

​ 其中:

  • 参数signo是要检测或者修改其具体动作的信号编号。如果act指针非空,则要修改信号的动作;如果oact指针非空,则是通过oact指针将返回该信号的上一个处理动作(如果act为空,即未修改,就是当前信号的处理动作)。struct sigaction的结构如下(Linux形式上可能有所不同):
struct sigaction{
void (*sa_handler)(int); /*addr of signal handler*/ /*or SIG_IGN, or SIG_DFL*/ sigset_t sa_mask; /*adddtional signals to block*/ int sa_flags; /*signal options, 详细信息见下表*/ /*alternate handler*/ void (*sa_sigaction)(int, siginfo_t *, void *);};

​ 当设置信号动作时,如果sa_handler指针指向一个信号捕捉函数的地址(不是常量SIG_IGN、SIG_DEL), 则sa_mask字段说明了一个信号集。 在调用该信号捕捉函数时,该信号屏蔽字要添加到进程的信号屏蔽字中,当信号处理函数处理完毕后,在重新将进程的信号屏蔽字恢复为原先的值。这样做最主要的目的在于:防止两个相同的信号同时(间隔很短)到来时产生竞态。也就是说应该在处理该信号的过程中阻塞该类型的后续信号

​ 当然,这个函数也是不支持信号入队的:如果阻塞过程中发生了多个该信号,那么只会递送一次该信号。

  • sa_flags字段指明了对信号处理的各个选项:
序号 选项 说明
1 SA_INTERRUPT 由此信号中断的系统调用不自动重启(sigaction默认处理方式)
2 SA_NOCLDSTOP 若signo为SIGCHLD, 当子进程停止时不产生此信号;当子进程终止时产生此信号
3 SA_NOCLDWAIT 若signo为SIGCHLD, 当子进程终止时不产生僵尸进程;若调用进程随后调用wait,则阻塞到它所有进程都终止。
4 SA_NODEFER 捕捉到此函数时,在执行信号捕捉函数时系统不自动阻塞该信号(除非sa_mask中包括此信号)
5 SA_ONSTACK 递交给替换栈上的进程
6 SA_RESETHAND 在信号捕捉函数入口处将此信号的处理函数改为SIG_DFL, 并清除SA_SIGINFO标志
7 SA_RESTART 由此信号中断的系统调用自动重启
8 SA_SIGINFO 此选项对信号处理程序提供一个附加信息。
  • sa_sigaction字段是一个替代的信号处理程序,在sigaction结构中使用SA_SIGINFO标志时,使用该信号处理程序。对于sa_handlersa_sigaction,实现上一般是一个共用体,因此应用只能一次使用他们中的一个。下面是Linux2.6.12上的实现,从这里可以看出_sa_sigaction和sa_handler是一个共用体:
struct sigaction {
union {
__sighandler_t _sa_handler; void (*_sa_sigaction)(int, struct siginfo *, void *); } _u; sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void);};

示例:使用sigaction实现signal

/*************************************************************************             > File Name: sigaction2signal.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 10时30分48秒 ************************************************************************/#include 
#include
/** 我们使用sigaction函数来实现signal的功能*/typedef void Sigfunc(int);Sigfunc *signal(int signo, Sigfunc *func){
struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif if(sigaction(signo, &act, &oact) < 0) return (SIG_ERR); return (oact.sa_handler);}static void alarm_handler(int signo){
printf("Catch SIGALRM signal!!!\n");}void sigaction2signal_demo(){
signal(SIGALRM, alarm_handler); alarm(1); sleep(2); alarm(1);}void main(int argc, char *argv[]){
//signal_demo(); //exec_funcs(); //signal_lost_test(); sigaction2signal_demo(); while(1){
pause(); }}

执行结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out Catch  SIGALRM signal!!!Catch  SIGALRM signal!!!^Ctoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

8.5 函数sigsuspend

​ 首先看sigsuspend的函数原型:

#include 
int sigsuspend(const sigset_t *sigmask); 返回值:-1

sigsuspend函数的使用方法在下面的示例中进行说明。·

示例1:错误用法

​ 我们已经知道,修改进程的信号屏蔽字可以阻塞所选择的信号,或者解除对他们的阻塞。使用这种技术可以保护不希望被信号打断的临界代码区。如果希望对一个信号解除阻塞,然后调用pause以等待以前被阻塞的信号发生,发生什么事情呢? 我们依然使用程序来说明:

/*************************************************************************             > File Name: sigsuspend_demo.c             > Author: Toney Sun             > Mail: vip_13031075266@163.com       > Created Time: 2020年04月28日 星期二 10时58分19秒 ************************************************************************/#include 
#include
static void handler(int signo){
printf("Catch SIGINT!!!\n");}void invalid_usages(){
sigset_t newmask, oldmask; sigfillset(&newmask);//阻塞所有信号 sigemptyset(&oldmask); //sigaddset(&newmask, SIGINT); signal(SIGINT, handler); if(sigprocmask(SIG_BLOCK, &newmask, &oldmask)<0){
printf("sigprocmask SIG_BLOCK error\n"); return; } /*临界代码区:critical region of code begin*/ sleep(2); /*此区域的代码不会被信号打断*/ /*临界代码区:critical region of code end*/ /*解除对SIGINT的阻塞*/ if(sigprocmask(SIG_SETMASK, &oldmask, NULL)<0){
printf("sigprocmask SIG_SETMASK error\n"); return; } printf("Before pause\n"); pause(); printf("After pause\n");}void main(int argc, char *argv[]){
//signal_demo(); //exec_funcs(); //signal_lost_test(); invalid_usages();}

我在程序执行到sleep(2);时连续按下“Ctrl+C”, 这里会被阻塞,没有什么问题。2秒过后,进程屏蔽字解除信号阻塞,然后调用pause()等待信号的到来。那么问题来了,信号是在sigprocmask调用返回之前就递送到进程的,因此paus()是捕捉不到SIGINT信号的,之后程序会一直阻塞,直到下一个信号的到来(后面我又重新按下“Ctrl+C“退出程序的)。结果如下:

toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out ^C^C^C^C^CCatch SIGINT!!!Before pause^CCatch SIGINT!!!After pausetoney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

针对上述的问题,系统实现了一个原子操作:解除阻塞信号并等待

之前,我对于这个sigsuspend()函数的功能总是不得要领,不明白它的真正目的。现在有一点清楚了: suspend()函数的出现就是为了解决上述的问题。它的作用包括两部分:

  • 修改当前的进程屏蔽字(将其修改为sigmask
  • 阻塞当前进程
  • 非sigmask中的信号到来时:
    • 首先执行信号的处理函数
    • 然后恢复进程的信号屏蔽字(调用suspend之前的信号屏蔽字)
    • 最后进程继续运行

上述这几步都是suspend函数自动完成的。

示例2:suspend的用法

void sigsuspend_demo(){
sigset_t newmask, oldmask, waitmask; sigfillset(&newmask);//阻塞所有信号 sigemptyset(&oldmask); sigfillset(&waitmask);//等待所有信号 sigdelset(&waitmask, SIGINT);//等待除了SIGINT之外的所有信号 signal(SIGINT, handler); if(sigprocmask(SIG_BLOCK, &newmask, &oldmask)<0){
printf("sigprocmask SIG_BLOCK error\n"); return; } if(sigsuspend(&waitmask) < 0){
printf("sigsuspend error\n"); return; } /*临界代码区:critical region of code begin*/ sleep(2); /*此区域的代码不会被SIGINT信号打断*/ /*临界代码区:critical region of code end*/ /*阻塞等待SIGINT信号*/ if(sigsuspend(&waitmask) < 0){
printf("sigsuspend error\n"); return; } /* .... */}

转载地址:https://blog.csdn.net/s2603898260/article/details/105810333 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:我运了盘“外国人”的“泡子”(有吴哥,金
下一篇:灵隐寺

发表评论

最新留言

不错!
[***.144.177.141]2024年03月27日 12时10分49秒