• 信号处理函数创建signal、sigaction、kill、sigqueue



    前言

    信号是事件发生时对进程的通知机制。也称之为软件中断
    信号分两类:
    ①、用于内核向进程通知事件,构成标准信号。编号范围1~31。不支持排队处理。可供使用:SIGUSR1和SIGUSR2。不可靠信号。
    ②、由实时信号构成,编号范围32~63.采用SIGRTMIN+x 形式.支持排队(队列化管理)。信号编号越小,优先级越高,同级看接收时间先后。可靠信号。可携带数据信息

    信号的处理:忽略、捕捉(设置处理程序)、默认行为
    忽略:内核将信号丢弃,信号对进程没有产生影响,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)
    捕捉(设置处理程序):设置一个某待捕捉的信号处理函数,当接受到该信号时,就会去处理对应的函数。
    默认行为:每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
    在这里插入图片描述


    信号处理函数的注册
    ①、signal——原始版
    ②、sigaction——首选版 //可收到数据信息
    信号发送
    ①、kill——原始版
    ②、sigqueue——首选版 //可发送信息

    一、信号处理函数signal()

    1、信号处理函数注册signal()

    void (*signal(int sig, void (*func)(int)))(int);
    等效于:  传入一个函数指针,返回一个函数指针。
    typedef void (*sighandler_t)(int);//参数int是传入信号的编号,方便一个信号处理函数对应多个信号
    
    sighandler_t signal(int sig, sighandler_t handler);
    成功,返回之前的处置  失败:SIG_ERR
    handler:信号处理函数
    		或者是SIG_IGN:忽略这个信号 SIG_DFL:恢复默认
    
    signum:哪个信号

    2、信号发送kill()

    int kill(pid_t pid, int sig);
    返回0成功
    pid:进程id
    sig:要发送的信号
    
    pid > 0:将发送个该 pid 的进程
    pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。
    pid < 0:将信号发送给进程组ID 为 pid 的绝对值得,并且发送进程具有权限向其发送信号的所有进程
    pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)

    3、练习demo.c

    #include<signal.h>
    #include<stdio.h>
    #include <unistd.h>
    
    static volatile int flag = 0;
    //typedef void (*sighandler_t)(int);
    void handler(int signum)
    {
    	if(signum == SIGINT|| signum == SIGTERM)
    	{
    		flag = 1;
    		return;
    	}
        if(signum == SIGIO)
            printf("SIGIO   signal: %d\n", signum);
        else if(signum == SIGUSR1)
            printf("SIGUSR1   signal: %d\n", signum);
        else
            printf("error\n");
    }
    
    int main(void)
    {
        //sighandler_t signal(int signum, sighandler_t handler);
        signal(SIGIO, handler);
        signal(SIGUSR1, handler);
        printf("set handler signal:%d  %d\n", SIGIO, SIGUSR1);
        
        printf("receive a signal before\r\n"); 
        
        while(!flag)
        {
    	    pause(); /*程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,当我们
    	    		按下Ctrl-C时(获接受到一个信号),信号被捕捉,并且使得pause退出等待状态。*/
    	    printf("receive a signal \r\n"); 
        }
        return 0;
    }

    4、阻塞信号(信号掩码)sigprocmask()与等待状态信号sigpending()

    阻塞信号:信号发出后,并不能直接被接收到(传递延后),处于阻塞状态,只能等解除阻塞或者设为忽略
    等待状态信号:被设为阻塞的信号,被发出后就是处于等待状态信号

    阻塞的设置和等待状态信号是通过状态字来记录的。分别为sigprocmask()和sigpending()

    ①、阻塞状态字获取和设置

    int sigprocmask(int how, const sigset_t * set,
    							sigset_t * oldset);
    success: 0  error: -1
    how:
    	SIG_BLOCK:将set信号集添加到信号掩码里(并集 |)
    	SIG_UNBLOCK:将set信号集从信号掩码里移除(&)
    	SIG_SETMASK:将set信号集赋给信号掩码(=)
    set:想要操作的信号集的数据对象
    oldset:之前的信号集数据对象   如不需要可设为NULL
    
    只可读取目前处于等待状态的信号
    int sigpending(sigset_t *set);
    success: 0  error: -1
    set:存放返回的状态

    该函数管理信号,是通过信号集的数据结构来进行管理的,信号集可以通过以下的函数进行管理。

    ②、信号集操作函数(状态字表示)

    int sigemptyset(sigset_t *set);  //初始化 set 中传入的信号集,清空其中所有信号
    int sigfillset(sigset_t *set);  //把信号集填1,让 set 包含所有的信号
    int sigaddset(sigset_t *set, int signum);//把信号集对应位置为1
    int sigdelset(sigset_t *set, int signum);//吧信号集对应位置为0
    int sigismember(const sigset_t *set, int signum);//判断signal是否在信号集

    设置/解除信号阻塞的步骤:

    a、分配内存空间sigset sigset bset;
    b、置空sigemptyset(&bset);
    c、添加信号****sigaddset(&bset, SIGINT);
    d、添加其他需要管理的信号…
    e、设置信号集中的信号处理方案(此处为解除阻塞)sigprocmask(SIG_UNBLOCK, &bset, NULL);

    5、等待信号pause()、raise()、killpg()

    将暂停进程的执行,直到信号处理器函数中断该调用为止

    int pause(void);

    进程向自身发送信号

    int raise(int sig);

    向某一进程组的所以成员发送一个信号

    int killpg(pid_t pgrp,int sig);

    二、信号处理函数sigaction() 首选

    sigaction是设置信号的另一种方法,与signal区别
    ①、允许获得信号时无需将其改变
    ②、可携带额外信息数据(主要)

    实时信号:SIGRTMIN~SIGRTMAX之间的信号,表示法:SIGRTMIN+x
    实时信号可支持排队。设阻塞的信号,多次发该信号,并不是最终只发一次,而是将发出的信号排队处理,即能接受多个同信号

    1、信号处理函数注册sigaction()

    int sigaction(int sig, const struct sigaction* act,
          					 struct sigaction* oldact);
     sucess: 0    error:-1   
    sig:获取或改变的信号编码(除SIGKILL和SIGSTOP)
    act:信号新处置的对象,如仅对旧信号感兴趣可设为NULL
    oldact:之前的信号处置,不意可NULL
    
    
    struct sigaction {
    union{
    void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
    void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
    };
    sigset_t   sa_mask;/*阻塞关键字的信号集,可以在调用捕捉函数之前,把信号(包括自身
    信号)添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。防止在执行处理函数时被自己后指定信号打断。*/
    
    int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
    void (*sa_restorer)(void);//仅供内部使用
     };
    回调函数句柄sa_handler、sa_sigaction只能任选其一
    sa_mask:设置执行处理函数时不可被中断的信号(包括自身);  
    sa_flags:处理过程选项: SA_SIGINFO(携带额外信息)  SA_RESTART(自动重启中断的系统调用)

    在处理函数的struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。
    sigval si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

     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 */
        union    sigval si_value;    /* Signal value data from sigqueue */
        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 */
        int      si_band;     /* Band event */
        int      si_fd;       /* File descriptor */
    }
    
    union sigval {
       int   sival_int;
       void *sival_ptr;
     };

    2、信号发送sigqueue()

    使用前提:
    ①、制定了 SA_SIGINFO 的标志
    ②、sa_sigaction 成员提供了信号捕捉函数

    int sigqueue(pid_t pid, int sig, const union sigval value);
    pid:进程id
    sig:信号编号
    value:存放额外携带信息
    
    union sigval {
       int   sival_int;
       void *sival_ptr;
     };

    发送端:

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <signal.h>
    #include <stdlib.h>
    
    #define MYSIG SIGRTMIN+5
    
    int main(int argc,char *argv[])
    {
    	int signo=MYSIG; 
    	union sigval mysigval; 
    	int i;	
    	pid_t pid;
    
    	pid=(pid_t)atoi(argv[1]);
       
    	for(i=0;i<10;i++)
    	{
    		mysigval.sival_int=i;  
    		//向指定进程发送一个信号和数据
    		printf("sigqueue signo=%d, mysigval.sival_int=%d\n",signo, mysigval.sival_int);  
    		if(sigqueue(pid, signo, mysigval)==-1)	
    		{  
    			printf("sigqueue fail\n");	
    			return -1;	
    		}	
    		sleep(1);
    	}	
    	return 0;
    }

    3、练习demo.c

    接受端

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <signal.h>
    #define MYSIG SIGRTMIN+5
    int flag = 1;
    
    //信号响应函数 
    void new_act(int signum, siginfo_t * info, void * myact)  
    {  
        printf("new_act=%d\n", signum);  
        printf("new_act->%d,%d\n", info->si_int,info->si_value.sival_int);  /*从发送端传过来的数据,两个值相同*/
        if(info->si_int == 9)
        {
            flag = 0;
        }
    }  
    
    int main(int argc,char *argv[])
    {
        int signo=MYSIG;  
        pid_t pid;
        struct sigaction myAct;       //指明对信号响应的设置信息
        
        pid = getpid();// 调用getpid函数的进程是什么,就返回该进程的ID
        printf("process id is %d,%d\r\n",pid, signo);
    /*
    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_sigaction成员而不是sa_handler作为信号处理函数。
        myAct.sa_flags=SA_SIGINFO;  
        //指明信号响应函数   
        myAct.sa_sigaction=new_act;  
        //安装信号,为MYSIG设置响应函数 
        if(sigaction(signo, &myAct, NULL)<0)  
        {  
            printf("sigaction fail\n");  
        }  
      
        while(flag)  
        {  
            sleep(2);  
            printf("sleep \n");  
        }  
        return 0;
    }

    在这里插入图片描述


    三、扩展定时SIGALRM

    定时器产生的SIGALRM

    函数原型:int setitimer(int which, const struct itimerval *new_value,  
                struct itimerval *old_value);  
    函数描述:计时器功能。
    参数说明:
    Which:表示类型,可选的值有:
    ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。
    ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
    ITIMER_PROF:以该进程在用户态和内核态下所费的时间来计算,它送出SIGPROF信号。
    
    new_value:用来对计时器进行设置
    old_value:通常用不上,设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。
    
    
    struct itimerval {  
        struct timeval it_interval; /* next value 第一次以后发出信号的时间间隔*/  
        struct timeval it_value;    /* current value 第一次发出信号的时间间隔*/  
    };  
      
    struct timeval {  
        time_t      tv_sec;         /* seconds */  
        suseconds_t tv_usec;        /* microseconds */  
    };  
    itimeval又是由两个timeval结构体组成,timeval包含tv_sec和tv_usec两部分,其中tv_se为秒,tv_usec为微秒(即1/1000000秒)。
    settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号,然后重置为it_interval,继续对it_value倒计时,一直这样循环下去。
    基于此机制,setitimer既可以用来延时执行,也可定时执行。

    参考与引用:https://www.jianshu.com/p/f445bfeea40a

     
  • 相关阅读:
    内网或无域名服务器集成微信公众号接口
    记录一次重新学习SetInternal和SetTimeout
    SQLServer日志过大导致还原失败的解决方案
    批量修改SQLServer数据库表字段属性
    收缩SQLServer数据库解决日志占用空间过大(2008R2以上版本)
    SAP GUI安装出现ocx错误提示的解决办法
    .NetCore自动转换枚举类显示自定义中文名称
    EFCore-脚手架Scaffold发生Build Failed问题的终极解决
    .NetCore自定义WebAPI返回Json的格式大小写的三种方式
    存储过程模糊搜索,按匹配率排序初探
  • 原文地址:https://www.cnblogs.com/lidabo/p/16846072.html
Copyright © 2020-2023  润新知