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

摘要:
1.Linux中的信号(32个信号)中断某些功能的阻塞https://zhidao.baidu.com/question/1766690354480323100.html#defineSIGHUP1#defineSIGINT2#defineSIGQUIT3#defineSIGILL4#defineSIGTRAP5#defineSIGABRT6#defineSIGIOT6#defineSIG

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

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

Linux基础(08)信号通信机制第1张Linux基础(08)信号通信机制第2张
#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()只能向一个进程发送信号,而不能发送信号给一个进程组。

  收发信号的具体实现:

Linux基础(08)信号通信机制第3张Linux基础(08)信号通信机制第4张
#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
Linux基础(08)信号通信机制第5张Linux基础(08)信号通信机制第6张
#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处理, 高并发服务器一般都是这样写的

Linux基础(08)信号通信机制第7张Linux基础(08)信号通信机制第8张
#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,'

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Webpack+Gulp+React+ES6开发.getCellType()的几种类型值下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux上 Can't connect to X11 window server using XX as the value of the DISPLAY 错误解决方法

在Linux上运行需要图形界面的程序时出现如下错误提示: No protocol specified Exception in thread "main" java.awt.AWTError: Can't connect to X11 window server using ':1.0' as the value of the DISPLAY varia...

97 条 Linux 常用命令及Vim命令总结

一:Vim编辑模式命令 基本上Vim共分为3种模式,分别是一般模式,编辑模式和命令行模式,这三种模式的作用分别如下简述: 一般模式:默认模式。打开vim直接进入的是一般模式,在这个模式下,可以进行的操作有:移动光标,复制,粘贴,删除。 编辑模式:编辑文件内容,在界面左下方会出现INSERT的字样。 命令行模式:查找、读取、保存、替换字符、显示行号、离开v...

Linux系统编程——水平触发和边沿触发

事件模型 Edge Triggered (ET) 边缘触发只有数据到来,才触发,不管缓存区中是否还有数据。 Level Triggered (LT) 水平触发只要有数据都会触发。 首先介绍一下LT工作模式: LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符...

linux ftp 命令详解

ftp命令是Internet用户使用最频繁的命令之一,不论是在DOS还是UNIX操作系统下使用FTP,都会遇到大量的FTP内部命令,熟悉并灵活应 用FTP的内部命令,可以大大方便使用者,对于现在拨号上网的用户,如果ISP提供了shell可以使用nohup,那么ftp将是你最省钱的上 download方式,ftp的命令行格式为:ftp -v -d -i -n...

linux版本mat使用

1、下载linux版本的mat wget http://eclipse.stu.edu.tw/mat/1.9.0/rcp/MemoryAnalyzer-1.9.0.20190605-linux.gtk.x86_64.zip 2、解压zip包 unzip MemoryAnalyzer-1.9.0.20190605-linux.gtk.x86_64.zip 3...

Linux之Ansible

一、安装ansible 环境是centos7.0 主管服务器ip:192.168.175.134,只需安装ansible在本机即可,其余服务器无需安装,ansible通讯是用ssh 首先更换yum源 cd /etc/yum.repos.d/ cp CentOS-Base.repo CentOS-Base.repo.bak wget -O /etc/yu...