• Linux基础(08)信号通信机制


    1.Linux中的信号(有32个)   信号会中断一些函数的阻塞

    https://zhidao.baidu.com/question/1766690354480323100.html

    #define SIGHUP         1
    #define SIGINT         2
    #define SIGQUIT         3
    #define SIGILL         4
    #define SIGTRAP         5
    #define SIGABRT         6
    #define SIGIOT         6
    #define SIGBUS         7
    #define SIGFPE         8
    #define SIGKILL         9
    #define SIGUSR1        10
    #define SIGSEGV        11
    #define SIGUSR2        12
    #define SIGPIPE        13
    #define SIGALRM        14
    #define SIGTERM        15
    #define SIGSTKFLT    16
    #define SIGCHLD        17
    #define SIGCONT        18
    #define SIGSTOP        19
    #define SIGTSTP        20
    #define SIGTTIN        21
    #define SIGTTOU        22
    #define SIGURG        23
    #define SIGXCPU        24
    #define SIGXFSZ        25
    #define SIGVTALRM    26
    #define SIGPROF        27
    #define SIGWINCH    28
    #define SIGIO        29
    #define SIGPOLL        SIGIO
    /*
    #define SIGLOST        29
    */
    #define SIGPWR        30
    #define SIGSYS        31
    /* signal 31 is no longer "unused", but the SIGUNUSED macro remains for backwards compatibility */
    #define    SIGUNUSED    31
    
    /* These should not be considered constants from userland.  */
    #define SIGRTMIN    32
    #define SIGRTMAX    _NSIG
    View Code

      SIGCHLD的更多细节 子进程状态变更了,例如停止、继续、退出等,都会发送这个信号通知父进程。而父进程就能通过 wait/waitpid 来获悉这些状态了 https://segmentfault.com/a/1190000015060304

      信号因为是异步机制(可能会信号叠加)会造成系统的不稳定,所以应尽量减少信号(应用于一些突发事件) 

      如:Linux终端的Ctrl+C(强制结束进程)这样的信号. 

    2.如何发起异步操作  

      2.1(kill -signum pid) kill其实是发送信号的宏观指令 如kill -9(SIGKILL) pid

        signal()把信号绑定指定函数

        signal的大概使用: signal(signum(SIGINT) , myfunc) , 当发出SIGINT信号时执行myfunc函数  https://www.runoob.com/cprogramming/c-function-signal.html

        int kill(pid,signum);

        pid>0 发给pid进程

        pid=0 发给当前进程组的所有进程

        pid=-1 发送给所有进程

        pid<0 发送给|PID|所对应的组上

        pid = getpid()获得当前进程的ID  gpid = getgpid():获得当前进程的组ID

      2.2自举信号

        自己给自己发送信号 , 也就是给程序本身 raise 一个信号,然后执行信号所绑定的函数

        int raise(int sig); == int kill(getpid , signum)

      2.3定时函数  

         usigned int alarm(unsigned int seconds)  函数会在指定seconds之后收到SIGALRM信号

        如: 两秒后打印一个printf  signal(SIGALRM , printf);

                     alarm(2);

        alarm是一次性的 大概实现是在内核的一个  jiffies(程序开始时从0每隔10毫秒+1的一个数)  上累加的,如一个程序开始执行1秒后触发, 那就是 jiffies+100 触发. 因为一个程序只有一个jiffise,所以多次使用alarm时后面的alarm会覆盖前面的

        ualarm是多次的  useconds_t ualarm(useconds_t usecs , useconds_t interval);

        第一个参数是第一次产生的时间 , 第二个参数是间隔时间

        因为alarm和ualarm(不推荐使用)时间不准确 所以有了setitimer(推荐使用) 精确的定时器

        int getitimer(int which, struct itimerval *curr_value);
        
    int setitimer(int which, const struct itimerval new_value, struct itimerval *old_value)

        详细 https://blog.csdn.net/fjt19900921/article/details/8072903

    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 微秒*/
    };

        Linux会提供三个定时器,每个定时器返回的信号都是不同的 ITIMER_REAL ITIMER_VIRTUAL ITIMER_PROF ,一般没什么区别,使用第一个就行了

    3.安装和捕获信号

      3.1忽略信号

        signal(signum , SIG_IGN); SIG_IGN = 接收到信号,但是作空处理和屏蔽信号(让信号在屏蔽队列等待,何时关闭屏蔽,何时才接收信号)有区别

      3.2自定义捕捉函数

        signal(signum , handler);

        SIGKILL 和 SIGSTOP 不能被捕捉

      3.3系统默认信号函数

        如 ctrl+c 会发送SIGINT 执行系统的函数结束进程

      3.4因为signal()局限小不推荐使用 , 所以有了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 *);
           //上面两个选其一使用 ,选哪个取决于flag的状态
           sigset_t   sa_mask;    //一般信号屏蔽时用的(把你想屏蔽的信号加入到sa_mask集合, 再利用信号屏蔽函数进行操作 看3.5)
           int        sa_flags;  //会影响信号接受特殊标志
           void     (*sa_restorer)(void);  //不做使用NULL.有必要时传入个sigaction对象,用作备份方便恢复,sigaction()会把旧的sigaction对象和所绑定的设置传出
       };
    sa_flags = 0 调用第一个函数
    sa_flags = SA_INTERRUPT:由此信号中断的系统调用不会自动重启动(针对sigaction的XSI默认处理方式不会自动重启)

           SA_RESTART:  由此信号中断的系统调用会自动重启。

           SA_NOCLDSTOP:一般当进程终止或停止时都会产生SIGCHLD信号,但若对SIGCHLD信号设置了该标志,当子进程停止时不产生此信号。当子进程终止时才产生此信号。
           SA_NODEFER:
           SA_RESETHAND:
           这两个标志对应早期的不可靠信号机制,除非明确要求使用早期的不可靠信号,否则不应该使用。这里也不多做介绍
           SA_SIGINFO:当我们没有使用 SA_SIGINFO 标志时,调用的是 sa_handler指定的信号处理函数 
                  我们使用了 SA_SIGINFO标志 然后使用了 sa_sigaction字段表示的处理函数。

    sigset_t 信号集合 
    
    siginfo_t {
       int      si_signo;    /* Signal number */             //信号编号
       int      si_errno;    /* An errno value */            //如果为非0值则错误代码于之关联
       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 */          //发送信号的进程ID
       uid_t    si_uid;      /* Real user ID of sending process */  //发送信号的用户ID
       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) */
    }

      3.5信号屏蔽集合的操作函数

        int sigemptyset(sigset_t* set);          清空信号集合
        int sigfillset(sigset_t* set);            填满信号集合(把所有信号都放进去)
        int sigaddset(sigset_t* set, int signum);      往set集合里追加signum
        int sigdelset(sigset_t* set, int signum);      把signum从set中删除
        int sigismember(const sigset_t *set, int signum); 测试set里是否有signum

    #include<stdio.h>
    #include<stdlib.h>
    #include<signal.h>
    void myHandler(int sig);
    int main(int argc,char *argv[])
    { 
          struct sigaction act, oact;
    
          act.sa_handler = myHandler;
          sigemptyset(&act.sa_mask); /*initial. to empty mask*/
          act.sa_flags = 0;
          sigaction(SIGUSR1, &act, &oact); 
        while (1) 
        { 
            printf("Hello world.
    "); 
            pause();
        }
      }
    
      void myHandler(int sig)
      {
        printf("I got signal: %d.
    ", sig);
      }

    4.sigqueue() 发送信号函数  https://www.cnblogs.com/mickole/p/3191804.html

      之前学过kill,raise,alarm,abort等功能稍简单的信号发送函数,现在我们学习一种新的功能比较强大的信号发送函数sigqueue.

      sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。

      int sigqueue(pid_t pid, int sig, const union sigval val)  调用成功返回 0;否则,返回 -1

      sigqueue的第一个参数是指定接收信号的进程ID,

           第二个参数确定即将发送的信号,

           第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

      typedef union sigval {  //可以存放4字节的数据 指针只能在同一进程传递 https://www.cnblogs.com/mickole/p/3191804.html

                   int  sival_int;  

                   void *sival_ptr;

      }sigval_t; 

      si_value :系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数( void (*sa_sigaction)(int, siginfo_t *, void *))的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。

     sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

      收发信号的具体实现:

    #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
    ",sig,info->si_value.sival_int,info->si_int);
    }
    sigvalue_recv
    #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;
    }
    sigvalue_send

    5.设置信号屏蔽集

      利用信号屏蔽集合的操作函数,设置好信号屏蔽集, 再使用sigprocmask()进行添加或删除

      int sigprocmask(int how, const sigset_t set, sigset_t *oldset);
      how = SIG_BLOCK    将设置好的信号屏蔽集set加入到当前进程的屏蔽集里
         SIG_UNBLOCK    将设置好的信号屏蔽集set从当前进程的屏蔽集里删除
         SIG_SETMASK    将设置好的信号屏蔽集set设置为当前进程的屏蔽集

      第三个参数作备份用的

    6.未决信号  因为未捕获或屏蔽的信号称为未决信号

      屏蔽信号不是不接收该信号了,而是接收信号后把该信号放入信号屏蔽的队列里不做处理, 这些信号称为未决信号 , 队列里的信号不会重复,因为信号会被覆盖. 

      忽略的信号不会加入到信号屏蔽的队列里

      int sigpending(sigset_t *set);  查询有多少未决信号

    7.阻塞的信号函数

      int sigsuspend(const sigset_t *mask);  阻塞进程(收到mask里的信号继续阻塞),等待 mask 之外的信号唤醒

      阻塞信号的实现 https://blog.csdn.net/weiyuefei/article/details/72457739

       屏蔽信号会收到信号但是不做处理,他会收到未决信号队列里, 而阻塞信号会收到也会处理,但是不会返回

    8.信号通信

      

      信号可以中断函数的阻塞 , 如: accept被信号打断后的处理

    ACCEPT:
      confd = accept(srvfd , (struct sockaddr *)&tcilent , &tclientaddlen)   if(-1 == confd)   {     if(errno == EINTR)     { goto ACCEPT;     }     else     {       return -1;     }   }

      因为信号函数不能做时间长的(信号处理函数时间过长可能会出现被其他信号中断的情况) , 容易死锁的线程不安全的操作 , 

      所以有了把信号转变成事件的操作: 信号处理函数通过管道pipe(pfd) , wirte(pfd[0] , signum/void* ptr , size),往管道写入数据 ,

         管道的另一端会往 fd 写入数据, 然后发生event , 这样就可以把信号转换成事件了, 再通过epoll处理, 高并发服务器一般都是这样写的

    #include<stdio.h>
    #include<stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include<sys/time.h>
    #include<error.h>
    #include<arpa/inet.h>
    #include<sys/epoll.h>
    #include <fcntl.h>
    #include<errno.h>
    
    
    
    
    #if  1
    /*****************************************************************************
     函 数 名  : MyHandler
     功能描述  : 信号处理函数
     输入参数  : int sig , struct siginfo_t *info , void *ctx
     输出参数  : 无
     返 回 值  : void 
     调用函数  : 
     被调函数  : 
     
     修改历史      :
      1.日    期   : 2019年11月8日 星期五
        作    者   : ljf
        修改内容   : 新生成函数
    
    *****************************************************************************/
    void  MyHandler( int sig , struct siginfo_t *info , int *pipefd)
    {
        char *buff = "hello world
    ";
        size_t len = sizeof(buff);
        write(pipefd[1], buff, len);
    }
    
    /*****************************************************************************
     函 数 名  : readn
     功能描述  : 不被信号所打断的完整读取
     输入参数  : int fd , void* buf , size_t len
     输出参数  : 无
     返 回 值  : ssize_t 
     调用函数  : 
     被调函数  : 
     
     修改历史      :
      1.日    期   : 2019年11月8日 星期五
        作    者   : ljf
        修改内容   : 新生成函数
    
    *****************************************************************************/
    ssize_t readn(int fd , void* buf , size_t len)
    {
        size_t nleft = len;
        ssize_t nread;
        char* pbuf = buf;
        while ( nleft > 0 )
        {
            if ( (nread = read(fd , pbuf , nleft)) == -1 )
            {
                if(errno == EINTR)    //如果被信号所中断
                    nread = 0;        //continue 因为nread=0 所以等同于重新开始下一次循环
                else
                    return -1;
            }else if ( nread == 0 )
            {
                break;
            }
            nleft -= nread;    //实际大小减去已读取的大小 等于未读取的大小
            pbuf += nread;    //从未完的位置开始继续读取
        }
        return (len - nleft);
    }
    
    
    
    int main(int argc, char *argv[])
    {
        struct sigaction act , oact;
        int pipe_fd[2] = {0,};
        int epoll_fd;
        
        pipe(pipe_fd);
        
        act.sa_handler = MyHandler;        //捕捉信号和处理
        sigemptyset(&act.sa_mask);
        act.sa_flags = SA_SIGINFO;
        if ( sigaction(SIGUSR1 , &act , pipe_fd) < 0 )
        {
            perror("sigaction");
            exit(1);
        }
    
        epoll_fd = epoll_create(256);
        if ( epoll_fd < 0 )
        {
            perror("epoll_create1");
            exit(2);
        }
        struct epoll_event ep_ev;
        ep_ev.events = EPOLLIN;
        ep_ev.data.fd = pipe_fd[0];
        if ( epoll_ctl(epoll_fd , EPOLL_CTL_ADD , pipe_fd[0] , &ep_ev) < 0 )
        {
            perror("epoll_ctl");
            exit(3);
        }
    
        struct epoll_event ready_event[128];    //存放就绪事件
        int maxnum = 128;                        //就绪事件最大数
        int timeout = 3000;                        //超时时间10s
        int ret = 0;                            //接收就绪事件的数量
        while(1)
        {
            ep_wait:
            switch ( ret = epoll_wait(epoll_fd , ready_event , maxnum , timeout) )
            {
    
                case -1 :
                {
                    if ( errno == EINTR )
                    {
                        goto ep_wait;
                    }
                    else
                    {
                        perror("epoll_wait");
                        exit(4);
                    }
                }break;
    
                case 0 :
                    fputs("time out...
    ", stdout);
                    break;
                default:
                {
                    int i=0;
                    char buf[1024];
                    for (  ; i< ret ; i++ )
                    {
                        if ( ready_event[i].events & EPOLLIN )
                        {
                            memset(buf,'',sizeof(buf));
                            size_t r_s = readn(pipe_fd[0] , buf ,12);
                            fputs(&buf , stdout);
                            ep_ev.data.fd = ready_event[i].data.fd;
                            ep_ev.events = EPOLLOUT | EPOLLET;
                            epoll_ctl(epoll_fd , EPOLL_CTL_MOD , ready_event[i].data.fd , &ep_ev);
                        }
                        if ( ready_event[i].events & EPOLLOUT )
                        {
                            fputs("OK
    ", stdout);
                        }
                    }
                    
                }break;
            }
    
        }
    
    
        
        return 0;
    }
    #endif /* #if signal to event */
    View Code

    总结: 信号是内核发送给某一进程的一种消息 ,信号机制是Linux系统中用于进程之间相互通信或操作的一种机制

    1. Linux内核的信号都有一个默认绑定的执行函数 , 可以使用signal(num,func)重定向信号对函数的绑定 ,SIGKILL和SIGSTOP不能被捕捉和重定向

    kill(pid,signum) 指定进程发送信号 , raise(signum)当前进程自举信号 , signal(num,SIG_IGN)SIG_IGN忽略信号(做空处理),和屏蔽信号不同

    2. alarm(seconds)定时函数等待指定时间后发起SIGALRM信号 , 一个进程只能有一个alarm ,因为它是从程序开启时开始计时的,比如设置5,秒, 那么是程序开始5秒后触发ualarm(second, interval)多次触发定时器, second是第一次触发的时间 ,interval是间隔的触发时间 , 多个alarm,后面的会覆盖前面的

    3.因为alarm和ualarm时间都不准确 ,所有有了settimer(which,itimerval *new_value, itimerval *old_value) which定时器类型有三种一般都使用第一种ITIMER_REAL , new_value代表定时器第一次触发和间隔触发的时间 , old_value备份上次时间一般用不上设置NULL

    4.signal局限有点小所以有了sigaction, 创建sigaction对象并分配(指定信号函数,指定屏蔽信号集), 再使用sigaction()让信号函数和信号绑定

    5.sigqueue()可以给指定进程 ,带参的发送信号 , 配合sigaction()使用,sigaction使用SA_SIGINFO标志来接收带参信号中的参数

  • 相关阅读:
    ArcGIS Pro获得一个要素图层一种方法
    ArcGIS Pro layout clone
    ActiveMapViewChanged和选择变化
    ArcGIS Pro 改变栅格的数据源
    ArcGIS Pro自定义图标
    Windows Server 2016 路由和远程访问
    IIS应用程序池_缓存回收
    asp.net RSA密钥之C#格式与Java格式转换(PEM格式)
    MD5和Hash
    C# list与数组的转换
  • 原文地址:https://www.cnblogs.com/yxnrh/p/11732919.html
Copyright © 2020-2023  润新知