进程间通信_信号

基本概念

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

产生信号的方式

  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再次进入内核,最后再从内核返回用户空间执行后续代码。

原文地址:https://www.cnblogs.com/ding-ding-light/p/14254255.html