Linux信号异步通知

Linux中的信号

每个信号都有以SIG开头的名称,并定义为唯一的正整数。在 Shell 命令行提示符 下,输入kill -l命令,将显示所有信号的信号值和相应的信号名,类似如下所示:
在这里插入图片描述

几个比较常用的信号

几个比较常用的信号
SIGIGN:忽略改信号

SIGDEF:采用系统默认方式处理信号

SIGCHLD:在一个进程终止或者停止时,将该信号发送给其父进程,父进程的wait函数通常用来捕捉这个信号

SIGINT:当用户按中断键(delete/ctrl+c)时将产生这个信号

SIGKILL:此信号可以用于杀死一个进程

SIGSTOP:这是个作业控制信号,用于停止一个进程 这个信号和SIGKILL是仅有的两个不能被捕获或忽略的信号

SYSUSR1/2:用户定义的信号,用于应用程序

这些信号具体的作用下面列举一些:

在这里插入图片描述

信号的基本操作

  • 发送信号
  • 安装中断
  • 递送信号
  • 捕获信号
  • 屏蔽信号

如何发起异步操作

kill
kill 命令 eg:kill -signum pid
int kill(pid,signum);
pid>0 发给pid进程
pid=0 发给当前进程组的所有进程
pid=-1 发送给所有进程
pid<0 发送给|PID|所对应的组上

再发起的时候如何获得pid呢?

pid = getpid();  //获得进程的pid号
gpid = getgpid(); //获得进程组的pid号

如何给自己发送信号呢?

raise
自举信号,会给自己发送一个信号
int raise(int sig);
int kill(getpid(),signum);

安装和捕获信号

  • 忽略信号
  • 自定义捕捉函数
  • 系统默认信号函数

安装信号

第一种安装方式

signal
signal(signum,handler)

kill -SIGUSR1 pid
SIGKILL SIGSTOP 不能够被安装
通过signal安装信号只能传递一个参数,这个参数就是信号的值
此处附上一个demo:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sig_usr(int sig);

int main(int argc,char *argv[])
{ 
	int i = 0;
  	if(signal(SIGUSR1,sig_usr) == SIG_ERR)
    	printf("Cannot catch SIGUSR1\n");
  if (signal(SIGUSR2,sig_usr) == SIG_ERR)
    printf("Cannot catch SIGUSR2\n");
	//signal(SIGINIT,sig_usr)
  while(1) {
    printf("%2d\n", i);
    pause(); 
    /* pause until signal handler
       has processed signal */
    i++;
  }
  return 0;
}

void sig_usr(int sig)
{
  if (sig == SIGUSR1)
    printf("Received SIGUSR1\n");
  else if (sig == SIGUSR2)
    printf("Received SIGUSR2\n");
  else
    printf("Undeclared signal %d\n", sig);
}

运行效果及如何触发
在这里插入图片描述

第二种安装方式

sigaction对象

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

struct sigaction 
{
       void     (*sa_handler)(int); //和第一种安装方式一样
       void     (*sa_sigaction)(int, siginfo_t *, void *); //第二种安装方式
       sigset_t   sa_mask;  //信号集合
       int        sa_flags;  //会影响信号接受特殊标志
       void     (*sa_restorer)(void);

};


SA_SIGINFO:
siginfo_t {
   int      si_signo;    /* Signal number */
   int      si_errno;    /* An errno value */
   int      si_code;     /* Signal code */
   int      si_trapno;   /* Trap number that caused
                            hardware-generated signal
                            (unused on most architectures) */
   pid_t    si_pid;      /* Sending process ID */
   uid_t    si_uid;      /* Real user ID of sending process */
   int      si_status;   /* Exit value or signal */
   clock_t  si_utime;    /* User time consumed */
   clock_t  si_stime;    /* System time consumed */
   sigval_t si_value;    /* Signal value */
   int      si_int;      /* POSIX.1b signal */
   void    *si_ptr;      /* POSIX.1b signal */
   int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
   int      si_timerid;  /* Timer ID; POSIX.1b timers */
   void    *si_addr;     /* Memory location which caused fault */
   long     si_band;     /* Band event (was int in
                            glibc 2.3.2 and earlier) */
   int      si_fd;       /* File descriptor */
   short    si_addr_lsb; /* Least significant bit of address
                            (since Linux 2.6.32) */
}
传递参数

设置参数通过 struct siginfo_t 中的sigval_t si_value来设置
然后通过sigqueue来发送信号

int sigqueue(pid_t pid, int sig, const union sigval val)

调用成功返回 0;否则,返回 -1。

sigval_t

union sigval {
    int sival_int;  //传递int类型的数据
    void *sival_ptr; //传递任意类型的数据
};
demo
  • 发送端 sigqueue
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<string.h>

int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        printf("arguments error!");
        exit(0);
    }

    pid_t pid=atoi(argv[1]);//将进程号转化为整数
    union sigval v;
    v.sival_int=100;
    //这儿只是利用SIGINT来发送数据,
    //任何信号都行,只需要发送端与接收端规定一致即可
    sigqueue(pid,SIGINT,v);
    return 0;
}
  • 接收端
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
//#include<sys/siginfo.h>

void handler(int,siginfo_t *,void *);

int main(void)
{
   struct sigaction act;
   act.sa_sigaction=handler;
   sigemptyset(&act.sa_mask);
   act.sa_flags=SA_SIGINFO;
   if(sigaction(SIGINT,&act,NULL)<0)
   {
       printf("error");
       exit(0);
   }
   for(;;)
       pause();
   return 0;
}

void handler(int sig,siginfo_t * info,void *ctx)
{
   printf("recv a sid=%d data=%d data=%d\n",sig,info->si_value.sival_int,info->si_int);
}

在这里插入图片描述

忽略信号

signal(signum,SIG_IGN)
忽略就是不对这个或者这些信号进行任何操作

signal(SIGINT,SIG_IGN)  //忽略SIGINT信号
signal(SIGPIPE,SIG_IGN) //忽略SIGPIPE信号

信号集合

  • sigset_t sa_mas
int sigemptyset(sigset_t *set);  //清空信号集合
int sigfillset(sigset_t *set);  //填满信号集合
int sigaddset(sigset_t *set, int signum); //向信号集合中添加一个信号
int sigdelset(sigset_t *set, int signum); //删除信号集合中的摸个信号
int sigismember(const sigset_t *set, int signum);

*设置信号屏蔽集合
 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);//批量进行屏蔽集合的操作
SIG_BLOCK   设置屏蔽信号
SIG_UNBLOCK  设置解除屏蔽信号
SIG_SETMASK  重新设置系统信号设置

未决信号

sigpending(sigset)
我们之前设置了信号屏蔽集合,如果我们发送的信号是信号屏蔽集合中的信号,那么这个信号就会被放到未决信号的队列中,而且只有一次放入未决信号的机会,意思就是如果你发送了10次,那么这个信号只会有一次被加入到未决信号的队列中
在取消信号集合的阻塞的时候,这时候未决信号就发挥了作用,他会立马依次执行信号队列中被取消屏蔽的信号函数

sigsuspend 函数 等待信号

int sigsuspend(const sigset_t *mask);
sigsuspend函数作用 :如果在等待信号发生时希望去休眠,则使用sigsuspend函数是非常合适的

demo
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

void handler(int sig)
{
	printf("get a sig ,num is: %d\n",sig);
	
}

void print_sig(sigset_t *p)
{
	int i=1;
	for(;i<32;i++)
	{
		if(sigismember(p,i))
		{
			printf("1");
		}else
		{
			printf("0");
		}
	}
	printf("\n");
	
}

int main()
{
	signal(2,handler);
	sigset_t s,p,o;
	sigemptyset(&s);         //清空信号集合S
	sigemptyset(&o);         //清空信号集合o
	sigaddset(&s,SIGINT);    //把SIGINT信号加入到信号集合S中
	
	sigprocmask(SIG_SETMASK,&s,&o);  //把信号集合s中所有的信号设置成阻塞  信号集合o做备份
	
	int count=0;
	while(1)
	{
		sigemptyset(&p); //清空信号集合p
		sigpending(&p);   //获取一个未决信号组
		print_sig(&p); //探测有哪几个个信号在未决信号中 
		sleep(1);
		if(count++ ==10)
		{
			sigprocmask(SIG_SETMASK,&o,NULL); //还原信号集合o
			printf("recover block\n");
			sleep(3);
		}	
	}
	return 0;
}

在这里插入图片描述

在SIGINT在被阻塞之前 运行程序会探测到SIGINT在未决信号组中,当SIGINT被解除阻塞之后,未决信号立马执行。

最后关闭这个程序CTRL+c 肯定是不行了,可以使用kill -9 pid 来结束此进程

猜你喜欢

转载自blog.csdn.net/weixin_45309916/article/details/107456472