zoukankan      html  css  js  c++  java
  • 进程间通信_信号

    基本概念

     信号作为最简单的进程间通信手段,具有简单和开销小的优点,但是携带的信息有限。信号相当于软件层面的“中断”,它的实现手段导致了信号有很强的延时性。当一个进程收到另一个进程的信号时,无论程序执行到什么位置。必须立即停止运行,处理信号,处理结束后再继续执行后续指令,注意所有信号的产生及处理全部都是由内核完成的。信号必须满足一定的条件才能发送,不能够随意乱发。

    产生信号的方式

      1.按键产生,如:Ctrl+c(结束进程)、Ctrl+z(暂停进程)、Ctrl+(结束进程);
      2.系统调用产生,如:kill,ralse,abort;
      3.软件条件产生,如:定时器alarm;
      4.硬件异常产生,如:段错误,内存对齐出错;
      5.命令产生:kill。

    处理信号的方式

      1.执行默认动作;
      2.忽略;
      3.捕捉,调用用户定义的信号处理函数。

    未决信号集和信号屏蔽字

      如下图所示,进程的内核空间中的pcb控制块中有未决信号集和信号屏蔽字两个位图(只有0和1两个状态),当进程没有收到信号时,未决信号中的位全部都为0,假如键盘上按下了Ctrl+c产生了一个结束当前进程的信号,则未决信号集里面对应的位会变成1,如果信号处理完毕后对应位又会变0,但是如果信号屏蔽字里面对应位为1,则信号不能递达,信号被阻塞,只有将其改回0后才能处理信号。

    信号的分类

     信号分为常规信号(131)和实时信号(3464),常规信号都有对应的默认事件发生(除了用户自定义的信号),而实时信号没有对应的默认事件发生,实时信号一般做底层驱动才会用到。

      常规信号有以下特点:
       1.不排队(当阻塞了很多个同一个信号,解除阻塞后只会处理一个),可嵌套;
       2.未响应,将丢弃;
       3.产事件,发信号;
       4.挂起多,随机响。
      实时信号有以下特点:
       1.要排队,不嵌套;
       2.不忽略,不丢弃;
       3.实时信,无对应。(实时信号,无对应的系统事件)

    信号的四要素

      1.编号 2.名称 3.事件对应信号 4.默认处理动作
      注意:
       1.信号使用之前应该清楚四要素才能使用,使用命令 man 7 signal 查看每个信号的作用;
       2.不同芯片对应的信号编号可能会不同,但是名字是一样的,所以记名字比记编号更加通用。

      注意:
       1.9号信号和19号信号为特殊信号,只能执行默认动作不可以屏蔽。

    信号相关API

    kill()
        函数功能:给指定进程的pid发送一个信号。
        头 文 件:
                #include <sys/types.h>
                #include <signal.h>
        定义函数:
                int kill(pid_t pid, int sig);
        参数分析:
                pid: 小于-1:信号将被发送给组ID等于|-pid| 的进程组里面的所有进程
                      等于-1:信号将被发送给所有进程(如果当前进程对其有权限)
                      等于 0: 信号将被发送给与当前进程同一个进程组内的所有进程
                      大于 0:信号将被发送给 PID 等于 pid 的指定进程
                 sig:要发送的信号
        返 回 值:
                成功 : 0
                失败 :-1
    
    signal()
        函数功能:注册信号捕捉函数。
        头 文 件:
                 #include <signal.h>
        定义函数:
                 void (*signal(int sig, void (*func)(int)))(int);
        参数分析:
                 sig:要捕捉的信号
                 func:信号处理函数,当捕捉到指定信号后便会进入这个函数。
                 当第二个参数传递SIG_IGN时,捕捉的信号会被忽略;传递SIG_DFL时,则执行该信号的缺省动作。
    
        返 回 值:
                成功 :最近一次调用该函数时第二个参数的值
                失败 :SIG_ERR
    
    

      注意:
       1.signal()函数一般与函数()kill配套使用,以此来捕捉某个信号,以此来改变信号的缺省行为,执行用户所需要的行为。

    信号实验程序

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <signal.h>
    
    void func(int arg)
    {
        printf("Currently in signal response function
    ");
    }
    
    int main(int argc, char const *argv[])
    {
    
        union sigval val;
        signal(SIGUSR2 , func); 
        kill(getpid(), SIGUSR2);
    
        return 0;
    }
    
    
    alarm()
        函数功能:当时间减为0秒时产生一个SIGALRM信号将当前进程杀死,也可以捕捉该信号执行别的操作,相当于一个定时器(自然计时法)。
        头 文 件:
                #include <unistd.h>
        定义函数:
                unsigned int alarm(unsigned int seconds);
        参数分析:
                seconds: 需要增加的定时秒数
    
        返 回 值:
                成功 :上次定时剩余时间
                失败 :没有失败
    

      注意:
       1.每个进程只有一个定时器。
       2.实际执行时间 = 系统时间 + 用户空间 + 等待时间,其中等待时间是最长的,因为系统中很多设备是共用的,尤其是使用printf打印一些东西到终端,如果将其打印到文件将大大提高效率,优化程序也是一般从io入手的,查看程序的运行时间在要执行程序的前面加上time就可以了。

    alarm实验程序

    int main(int argc, char const *argv[])
    {
         alarm(1);
         int i = 0;
        while(1)
        {
           
            printf("%d
    ", i);
            i++;
        }
        return 0;
    }
    
    
    setitimer()
        函数功能:设置定时器,可代替alarm函数,精度微秒us,可以实现周期定时。
        头 文 件:
                #include <sys/time.h>
        定义函数:
                int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
        参数分析:
                which:定时方式:ITIMER_REAL(系统真实时间)
                                ITIMER_VIRTUAL(用户态花费时间)
                                ITIMER_PROF(系统和用户态的总和时间)
                struct itimerval {
                   struct timeval it_interval; //周期定时,间隔时间
                   struct timeval it_value;    //定时时间
                };
                struct timeval {
                   time_t      tv_sec;         //秒
                   suseconds_t tv_usec;        //微秒
                };
                new_value:需要增加的定时时间
                old_value:上次定时剩余时间
    
        返 回 值:
                成功 :0
                失败 :-1
    

    信号集

      由前面可知信号集有阻塞信号集(信号屏蔽字)和未决信号集,用户只可以操作阻塞信号集,通过操作阻塞信号集来影响未决信号集。

    信号集设定

      因为阻塞信号集在内核的PCB中,用户不可以随意更改内核,所以可以自己定义一个集合并设置需要的位(信号集操作函数),再与阻塞信号集位与 或 位或(设置信号屏蔽字)。信号集操作函数就是做这种操作的。

    信号集操作函数
    sigset_t  set;   自定义set信号集
    int sigemptyset(sigset_t *set); 清空信号集set
    int sigfillset(sigset_t *set); 将信号集set全部置1
    int sigaddset(sigset_t *set, int signum); 将指定的信号置1
    int sigdelset(sigset_t *set, int signum); 将指定的信号清0
    int sigismember(const sigset_t *set, int signum); 当信号在对应的未决信号集中为1则函数sigismember()返回1
    int sigpending(sigset_t  set); 查看未决信号集,set为传出的
    设置信号屏蔽字
    sigprocmask()
          函数功能:将自定义信号集set与阻塞信号集位与 或 位或操作
          头 文 件:
                  #include <signal.h>
          定义函数:
                  int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
          参数分析:
                 how:SIG_BLOCK: 设置阻塞
                      SIG_SETMASK:取消阻塞
                      SIG_UNBLOCK:设置阻塞,但是会替换掉原有的设置,所以一般不用
                 set:设定好的信号集
                 oldset:用以回复阻塞信号集的原有设置
          返 回 值:
                  成功:0
                  失败:-1
    

    信号集实验程序

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <signal.h>
    #include <errno.h>
    
    void func(int arg)
    {
        printf("This is signal response arg: %d
    ", arg);
    }
    
    void print_set(sigset_t *set)
    {
        int i;
    
        //判断未决信号集中的所有信号
        for (i = 1; i < 32; i++)
        {
            //当信号i在对应的未决信号集中为1则函数sigismember()返回1
            if (sigismember(set, i))
            {
                putchar('1');
            }
            else
            {
                putchar('0');
            }
        }
        printf("
    ");
    }
    
    int main(int argc, char const *argv[])
    {
        sigset_t set, pset;
    
        //注册信号1(关闭终端),2(Ctrl+c),3(Ctrl+)的处理函数
        for (int i = 1; i < 4; i++)
        {
            signal(i , func);
        }
    
        //在此进程中产生信号1,2,3
        for (int i = 3; i > 0; i--)
        {
            raise(i);
        }
    
        //清空用户自定义屏蔽字
        sigemptyset(&set);
    
       // 增加需要屏蔽的信号
        for (int i = 1; i < 4; i++)
        {
            sigaddset(&set, i);
        }
    
        //将信号1,2,3屏蔽
        int ret = sigprocmask(SIG_BLOCK, &set, NULL);
        if (-1 == ret)
        {
            perror("sigprocmask error");
            return -1;
        }
        
        printf("sleep
    ");
        sleep(3);
        printf("wake up
    ");
    
        //实时监测未决信号集,当用户按下Ctrl+c,Ctrl+时对应的位将被置1,表示该信号处理未执行
        while(1)
        {
            int ret = sigpending(&pset);
            if (-1 == ret)
            {
                perror("sigpending error");
            }
            
            print_set(&pset);
            sleep(1);
        }
    
        return 0;
    }
    

    输出结果:

    sigaction实现信号捕捉

      上面讲到的函数signal()是ANSI定义的标准,并且在不同的操作系统中的行为也有所不同,这将造成程序的移植性不好,所以应该避免使用它,取而代之的是使用函数sigaction()。

    sigaction()
          函数功能:注册信号捕捉函数。
          头 文 件:
                  #include <signal.h>
          定义函数:
                  int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
          参数分析:
                  signum:需要捕捉的信号
                  struct sigaction
                  {
                       void (*sa_handler)(int); //需要注册的信号响应函数
                       void (*sa_sigaction)(int, siginfo_t *, void *);//将sa_flags设置为SA_SIGINFO时,使用这个函数类型指针来注册信号响应函数,用以获取信号携带的数据
                       sigset_t sa_mask; //当在执行信号响应函数的时候,需要屏蔽的信号由这个自定义信号集决定,信号响应函数执行完后将决定权交还给PCB中的信号屏蔽字
                       int sa_flags; //为0时,在执行信号响应函数的时候,屏蔽产生执行信号响应函数的信号(默认选项)
                       void (*sa_restorer)(void);
                  };
                     act:信号处理参数结构体
                  oldact:原有的信号处理参数结构体
          返 回 值:
                  成功:0
                  失败:-1
    
    sigqueue()
         函数功能:
                发生一个信号,并且携带数据,需要配合函数sigaction()使用。
         头 文 件: 
                #include <signal.h>
         定义函数:
                int sigqueue(pid_t pid, int sig, const union sigval value);
         参数分析:
                pid:目标进程 PID
                sig:要发送的信号
                value:携带的额外数据
                union sigval
                {
                      int sigval_int;
                      void * sigval_prt;
                }
    
    

    sigaction实现信号捕捉实验程序

    #include <stdio.h>
    #include <signal.h>
    #include <strings.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    void func(int sig, siginfo_t *val, void *p)
    {
        printf("Currently in :%d signal response function
    ", sig);
        printf("The data carried is :%s
    ", (char *)val->si_ptr);
    }
    
    int main(int argc, char const *argv[])
    {
        //1.定义一个信号处理结构体,并将其初始化
        struct sigaction act;
        bzero(&act, sizeof(act));
        act.sa_sigaction = func;   
        act.sa_flags |= SA_SIGINFO;//设置信号可以携带数据
    
        //2.设置信号处理结构体
        sigaction(SIGINT, &act, NULL);
    
        //3.设置信号携带的联合体
        union sigval val;
        val.sival_ptr = "Hello 2021";
    
        //4.产生信号,并发送给自己
        sigqueue(getpid(), SIGINT, val);
    
        return 0;
    }
    

    内核实现信号捕捉流程简析

      如下图步骤1所示用户空间进入内核需要一个契机,这个契机就是程序因为中断、异常、系统调用等进入内核。信号属于软中断,当信号产生时系统会从用户空间进入内核空间,检查信号是否产生,如果信号产生了并且设置了捕捉的话则执行信号响应函数,信号响应函数处理完毕之后调用sigreturn再次进入内核,最后再从内核返回用户空间执行后续代码。

  • 相关阅读:
    Unix配置定时执行任务
    在Mac上使用Make编译时出现clang: error: unsupported option '-fopenmp'的解决办法
    git的一些操作
    Github误上传多余的文件夹后解决办法
    Ubuntu18.04安装使用YOLOv3
    本地IDEA中使用Spark直连集群上的Hive
    如何利用dokcer提交我的比赛代码
    Activiti7之整合spring和spring boot
    Activiti7之网关
    Activiti7之组任务
  • 原文地址:https://www.cnblogs.com/ding-ding-light/p/14254255.html
Copyright © 2011-2022 走看看