异步处理方式之信号(4):信号集的使用

8 信号集

8.1信号集的基本操作函数

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

#include <signal.h>

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;失败返回-1
int sigismember(const sigset_t *set, int signo);
		返回值:为真返回1;为假返回0
  • 函数sigemptyset初始化由set指向的信号集,清除其中所有的信号;
  • 函数sigfillset初始化set指向的信号集,使其包含所有的信号;

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

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

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

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

8.2 函数sigprocmask: 进程屏蔽字

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

#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
		返回值:成功返回0;失败返回-1
  • 首先,如果oset是非空指针,那么进程的当前信号屏蔽字通过oset返回
  • 其次,如果set为非空指针,则参数how只是如何修改当前进程的信号屏蔽字;下表便是how的取值以及相关的说明。
  • 如果set为空指针,则不修改该进程的信号屏蔽字,how值是无意义的
序号how说明
1SIG_BLOCK将set中的信号添加到当前进程的信号屏蔽字中(两者取或运算,即并集)
2SIG_UNBLOCK将set中的信号从当前进程信号屏蔽字中删除(set补集的交集)
3SIG_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 <stdio.h>
#include <error.h>
#include <signal.h>
/*获取当前进程的信号屏蔽字,并测试包括哪些信号。*/
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 <signal.h>
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 <stdio.h>
#include <signal.h>

/*初始化进程屏蔽字*/
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_set

toney@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 <signal.h>
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字段指明了对信号处理的各个选项:
序号选项说明
1SA_INTERRUPT由此信号中断的系统调用不自动重启(sigaction默认处理方式)
2SA_NOCLDSTOP若signo为SIGCHLD, 当子进程停止时不产生此信号;当子进程终止时产生此信号
3SA_NOCLDWAIT若signo为SIGCHLD, 当子进程终止时不产生僵尸进程;若调用进程随后调用wait,则阻塞到它所有进程都终止。
4SA_NODEFER捕捉到此函数时,在执行信号捕捉函数时系统不自动阻塞该信号(除非sa_mask中包括此信号)
5SA_ONSTACK递交给替换栈上的进程
6SA_RESETHAND在信号捕捉函数入口处将此信号的处理函数改为SIG_DFL, 并清除SA_SIGINFO标志
7SA_RESTART由此信号中断的系统调用自动重启
8SA_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 <stdio.h>
#include <signal.h>

/*
*     我们使用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!!!
^C
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$

8.5 函数sigsuspend

​ 首先看sigsuspend的函数原型:

#include <signal.h>
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 <stdio.h>
#include <signal.h>


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 pause
toney@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;
      }
    
     /* 
     	.... 
     */

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叨陪鲤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值