Linux(高级编程)9————进程间通信6(信号1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FangXiaXin/article/details/84646966

信号是什么?

  • 信号实质是一种软中断,用于通知进程发生了某些事件,实际上信号也可以算作进程间通信的一种方式,因为我们可在进程通过另一个进程发送信号,来告诉另一个进程发生什么事。
    这样来讲我们听起来可能还会比较晕。

深入理解信号:

  • 在我们生活中其实就有信号的例子,例如:当我们经过十字路口时会看到红绿灯,红绿灯其实就可以类比信号。它来告知进程1(行人)此时应该停下还是通过该路口;同时它也告知进程2(车辆)是否需要停下还是继续经过;这是信号对行人和车辆的作用。

信号的生命周期又是怎样的呢?

  • 信号的一生经历了这样的过程:
    产生->注册->注销->处理
    类比于我们生活中的红绿灯同样也经过了这样的过程:

    产生(红绿灯的每个颜色的出现即信号的产生)->注册(红绿灯最终都是要被行人和车辆注意的,当人们和车辆在脑海中形成了这个意识的时候,也就是信号注册成功)->注销(当我们脑海中有了这个意识的时候,我们就不需要在关注红绿灯了,此时我们就已经做出了处理,停止或通过路口不过在信号中和人的思维有一些不同它们是先将信号从“脑”中踢了出去,然后在处理的。)->处理(当我们看到红绿灯时,我们的一种动作即是对信号的处理,停止或者继续通过)。

  • 其实在上述过程中还有一个过程或者状态:阻塞/屏蔽
    还是红绿灯例子:当我们看到了红灯亮了的时候,我们是立即停下了吗?其实我们并没有,当我们脑海有了这个意识(即信号的注册)时,我们可能还低头看手机或者思索一些问题,突然当我们多向前走了几步,然后突然又想起是红灯,然后才停下来的;其实进程也是这样的它们并不会看到信号立即就会处理,而是先放在“脑海”,在处理完某些事之后处理信号的。
    那么信号的生命周期应该是这样的:
    产生->注册->“阻塞/屏蔽”->注销->处理
    而阻塞/屏蔽并不是信号所硬性要求和必须的所以对它打起了引号。

  • 说了这么多让我们先来看看Linux下面都有那些信号
    我们可以通过kill命令-l选项查看linux下所有的信号:
    在这里插入图片描述
    红色方框里面的 1—31的31个信号为非可靠信号,其余的34—64的 31个信号为可靠信号由此可知Linux下一共有62个信号。关于可靠信号和非可靠信号留在下面讲。关于这31个非可靠信号是从unix下继承过来的,后来由于信号的不足,Linux有添加了后面的34到64这31个可靠信号。
    对信号各个阶段理解:

  • 1.信号的产生:

    • 信号的产生有三中方式:
      1.硬件中断 如:当我们在Linux下运行某一个程序时按下ctrl+c时终止了该程序(进程)SIGINT信号。
      2.硬件异常 如:程序发生某些错误如我么常见的(segment fault(core dumped))。
      3.软件中断:如kill、raise、alarm、sigqueue(在后面一一介绍)。
      下面了解一下一些关于信号的接口:
      1.kill函数:
       int kill(pid_t pid, int sig);
      
      功能:向指定进程(pid)发送指定信号(sig)。
      返回值:成功返回0;失败返回-1。
      sig:可以为上述62个信号中的任意一个。
      2.raise函数:
      int raise(int sig);
      
      功能:等同于kill(getpid(),sig),给进程/线程自身发送。
      返回值:成功返回0;失败返回一个非0值。
      3.sigqueue函数:
       int sigqueue(pid_t pid, int sig, const union sigval value);
      
      功能:给指定进程发送指定的信号,同时可以通过value携带 一个参数过去。
      返回值:成功返回0;失败返回-1并设置errno值
      4.alarm函数:
       unsigned int alarm(unsigned int seconds);
      
      功能:指定在seconds秒后发送一个SIGALRM时钟信号给进程。
      返回值:返回seconds当前剩余的秒数,返回0表示没有时钟。
      注意: 当seconds传为0时,取消前面所设置的闹钟。
      5.abort函数:
      void abort(void);
      
      功能:类似于kill(getpid(),SIGABRT);向调用进程发送一个SIGABRT信号。
      SIGABRT为6号信号,当我们对正在运行的进程发送这个信号时,我们的进程会结束掉,并报错(core dumped),core dump会产生一个核心转储文件,但我们的Linux通常是默认关闭的,但我们可以通过ulimit -c [重新所要设置存储核心转储文件的大小];即可开启核心转储文件的生成,此时我们给进程发送这个信号的时候,就会在当前目录下看到例如:core.3954这样的一个文件3954告知我们是哪个进程产生的。说到这里,我们就有必要知道这个核心转储文件是干嘛的了:当程序异常的时候,会记录一个核心转储文件,该文件中记录的是程序的运行数据,当我们的程序异常崩溃时,而这个错误出现的时间可能间隔很长,此时我们的调试就很难做,错误很难定位时,此时核心转储文件就可以帮助我们使用gdb调试查看错误信息,定位错误。
      gdb 调试:file 加载可执行程序
      core-file core.pidjia加载core文件就可以看到错误的定位。
      由于核心转储文件可能含有系统安全信息,以及文件增多带来的开销,linux默认情况是关闭的。
  • 2.信号的注册:

    • 信号的注册即将这个信号传递给进程,将这个信号记录到进程中,让进程知道有这么个信号。
      信号是记录在进程PCB中。信号集合sigset_t结构体中,可以在/usr/include/bits目录中看到sigset.h,在这个头文件中即可看到该结构体。其实进程记录一个信号是通过这个结构体位图记录的,这个位图就是指定信号的存储位置strcut sigpending pending的结构体。可以在/usr/src/kernels/3.10.0-327.el7.x86_64/include/linux这个目录下看到sched.h头文件,在这里面就有PCB信息(注意:内核版本我们·可能是不一样的哦!!)。

      struct sigpending{
      	 struct sigqueue *head, *tail;
      	sigset_t signal;
      };
      
      struct sigqueue{
      struct sigqueue *next;
      siginfo_t info;
      }
      

      信号注册过程:
      1.将信号对应位图signal置1
      2.加入未决信号信息链表中
      在这里插入图片描述

  • 3.“信号的阻塞与屏蔽”:
    在PCB中还有一个sigset_t blocked的位图,用来标识那些信号要被阻塞。在进程看到pending集合中收到了那些信号,然后就开始处理这些信号,但是在处理这些信号之前,进程会先对比一下信号有没有在blocked位图里面,如果在里面意味着这个信号将不被处理,直到解除阻塞。
    其过程是这样的:
    在这里插入图片描述
    信号阻塞先关接口:
    在此之前得有一些设置:sigset_t mask

    • sigemptyset函数:
      int sigemptyset(sigset_t *set);
      功能:清空集合set中的信号,防止已经存在在set中其它信号造成未知错误。
      返回值:成功返回0,失败返回-1;

    • sigfillset函数:
      int sigismember(const sigset_t *set, int signum);
      功能:向集合中添加所有信号。
      返回值:成功返回0,失败返回-1;

    • sigaddset函数:
      int sigaddset(sigset_t *set, int signum);
      功能:向集合中添加指定信号。
      返回值:成功返回0,失败返回-1;

    • sigprocmask函数:
      int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
      参数:how:对集合中信号所要做的操作:SIG_BLOCK(将set集合中的信号设置为新的信号屏蔽字)、SIG_UNBLOCK(解除set集合中的信号屏蔽字)、SIG_SETMASK(将set集合中的信号设置到blocked集合中)。
      oldset:保存原有的信号集合。
      功能:一个进程的信号屏蔽字规定了当前阻塞而给该进程的信号集。调用函数sigprocmask可以检测或更改其信号屏蔽字,或者在一个步骤中同时执行这两个操作。
      返回值:成功返回0,失败返回-1。

    • sigismember函数:
      int sigismember(const sigset_t *set, int signum);
      参数:signum 表示所要检测的信号值(或信号的宏)。
      功能:检测一个信号是否在set中。
      返回值:成功返回0,失败返回-1。

    • sigpending函数:

      int sigpending(sigset_t *set);
      

      功能:将当前(信号注册集合)中的信号取出放入set中。

  • 4.信号的注销:
    从pending集合中将要处理的信号移除,该过程就是信号的注销。
    注意:信号实现将要处理的信号移除,然后才处理的。
    移除分为两种情况:
    在这里插入图片描述

  • 5.信号处理:
    信号处理方式分为:默认处理忽略处理自定义处理
    默认处理:操作系统原有定义好的对于一个信号的处理方式。
    忽略处理:切记忽略和阻塞不是一回事,一个信号如果被忽略了就等于直接被丢弃,不会修改位图,不会被注册。
    自定义处理:我们用户自己定义一个信号的处理方式,然后告诉操作系统该信号来了就按自定义方式处理。
    信号是在进程从内核态切换到用户态的时候去检测一下有没有信号需要被处理,然后才处理的。

    • 关于信号处理的接口:
      signal函数:
      	typedef void (*sighandler_t)(int);
         	sighandler_t signal(int signum, sighandler_t handler);
      
      参数:signum:要处理的信号(宏/信号值)。
      handler:SIG_IGN 忽略处理;SIG_DFL 默认处理;函数指针,该信号的自定处理方式。
      sigaction函数:
      int sigaction(int signum, const struct sigaction *act,
      struct sigaction *oldact);
      
      参数:act处理方式只不过是封装在这个结构体中,下面详细说明。
      oldact:用于保存原先对该信号的处理方式。
       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_flags:0 时使用sa_handler ;1 时使用sa_sigaction,这种处理方式会结收信号携带的个参数。
      sa_mask:在处理信号的时候,希望这个处理不会被其它信号的到来而打扰,因此sa_mask就是用于在处理信号时要阻塞,就把其他信号添加到这个集合中暂时阻塞。
      有两个比较特殊的信号: SIGSTOP、SIGKILL这两个信号无法被忽略,也无法被自定义处理。
  • 本片博客讲述信号的一生和各个阶段的处理方式以及部分简单原理,由于时间和篇幅问题,很多问题还未能解决,会在下一篇博客中继续更新。

猜你喜欢

转载自blog.csdn.net/FangXiaXin/article/details/84646966