加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_新乡站长网 (https://www.0373zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

Linux内核编程--进程通信信号

发布时间:2022-12-19 11:34:57 所属栏目:Unix 来源:
导读:  程序错误:比如进行“除以0”运算这样的非法操作

  外部信号:在Linux终端输入Ctrl+C,会产生SIGINT信号,定时器到期会产生SIGALRM信号

  显式请求:比如kill -9 pid, kill函数允许进程发

  程序错误:比如进行“除以0”运算这样的非法操作
 
  外部信号:在Linux终端输入Ctrl+C,会产生SIGINT信号,定时器到期会产生SIGALRM信号
 
  显式请求:比如kill -9 pid, kill函数允许进程发送信号给其他进程或进程组
 
  特殊说明:
 
  SIGPIPE, socket网络程序必须处理的信号,否则当客户端退出后,服务器端仍向客户端的socket发送数据,引起系统Crash。
 
  SIGCHLD, Linux中当子进程结束时,子进程并未被完全销毁,因为父进程还要用它的信息。如果父进程没有处理SIGCHLD信号或者调用wait/waitpid()等待子进程结束,就会产生僵尸进程。。
 
  四,信号的5种默认处理动作
 
  TERM 终止进程
 
  IGN 当前进程忽略掉这个信号
 
  CORE 终止进程,并生成一个Core文件
 
  STOP 暂停当前进程
 
  CONT 继续执行当前被暂停的进程
 
  五,信号的几种状态:产生、未决、递达。
 
  1) 产生
 
  a) 当用户按某些终端键时,将产生信号。
 
  终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT
 
  终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT
 
  终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。
 
  b) 硬件异常将产生信号。
 
  除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
 
  c) 软件异常将产生信号。
 
  当检测到某种软件条件已发生(如:定时器alarm),并将其通知有关进程时,产生信号。
 
  d) 调用系统函数(如:kill、raise、abort)将发送信号。
 
  注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
 
  e) 运行 kill /killall命令将发送信号。
 
  此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。
 
  2) 未决状态:在信号产生和递送之间的时间间隔内,称信号是未决的(pending)
 
  如果为进程产生了一个被该进程设置为阻塞的信号,而且对该信号的动作是默认或者捕捉该信号,则内核为该进程将此信号保持为未决状态,直到该进程对此信号解除阻塞,或者将对此信号的动作改为忽略。
 
  内核在递送一个原来阻塞的信号给进程时(而不是在产生信号时),才决定对他的处理方式。所以,进程在信号递送给他之前仍可以改变该信号的处理动作。
 
  “未决”和“阻塞”的区别:
 
  信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
 
  信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
 
  每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)
 
  3) 递送状态:产生的信号被通知给进程,信号被处理
 
  六,信号的种类:
 
  类型
 
  信号值范围
 
  说明
 
  不可靠信号
 
  信号值< SIGRTMIN, Unix早期信号
 
  注册函数为signal, .每次处理完信号后,要重置信号的值
 
  可靠信号
 
  信号值 在[SIGRTMIN, SIGRTMAX]之间
 
  发送函数为sigqueue, 注册函数为sigaction
 
  实时信号
 
  实时信号都支持排队,都是可靠信号
 
  非实时信号
 
  非实时信号都不支持排队unix进程通信,都不是可靠信号
 
  七,进程对信号的处理:
 
  进程在执行信号相应处理函数之前,首先要把信号在进程中注销。
 
  进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的生命终止。
 
  当进程接收到一个信号时,就需要把接收到的信号添加 pending 这个队列中。
 
  相关视频推荐
 
  Linux进程间通信-信号量、消息队列与共享内存
 
  Linux内核,就这么学,才简单
 
  学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
 
  【文章福利】小编推荐自己的Linux内核源码分析交流群:【点击812855908加入】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!

  八,信号的处理流程:
 
  信号捕捉样例:
 
  #include
  #include
  #include
  void sig_process(int signo){
      switch(signo){
          case SIGHUP:
              printf("Get a signal -- SIGHUP\n");
              break;
          case SIGINT:
              printf("Get a signal -- SIGINT\n");
              break;
          case SIGQUIT:
              printf("Get a signal -- SIGQUIT\n");
              break;
      }
      return;
  }
  int main(){
      signal(SIGHUP,  sig_process);
      signal(SIGINT,  sig_process);
      signal(SIGQUIT, sig_process);
      for (;;) {
          sleep(1);
      }
      return 0;
  }
  在终端输入Ctr+C,运行结果:

  进程管理结构体中,与信号有关的字段:
 
  struct task_struct {
      ...
      int sigpending;
      ...
      struct signal_struct *sig;
      sigset_t blocked;
      struct sigpending pending;
      ...
  }
  sigpending 表示进程是否有信号需要处理(1表示有,0表示没有)
 
  成员 blocked 表示被屏蔽的信息,每个位代表一个被屏蔽的信号
 
  成员 sig 表示信号相应的处理方法,其类型是 struct signal_struct
 
  #define  _NSIG  64
  struct signal_struct {
    atomic_t  count;
    struct k_sigaction action[_NSIG];
    spinlock_t  siglock;
  };
  typedef void (*__sighandler_t)(int);
  struct sigaction {
    __sighandler_t sa_handler;
    unsigned long sa_flags;
    void (*sa_restorer)(void);
    sigset_t sa_mask;
  };
  struct k_sigaction {
    struct sigaction sa;
  };
  九,常用的信号发送函数:
 
  函数
 
  备注
 
  函数名:int kill(pid_t pid, int sig)功能:给任意进程发送信号
 
  1.pid =0时,表示信号将送往所有与调用 kill的那个进程属同一个使用组的进程。2.pid >0时,pid 是信号要送往的进程ID。3.pid = -1时,信号将送往调用进程有权给其发送信号的所有进程,除了进程1(init)。4.pid
 
  函数名:int sigqueue(pid_t pid, int sig, const union sigval value)功能:给任意进程发送信号,并且可以传递数据value为随信号一起传递的数据
 
  1.新的发送信号函数,主要用于实时信号,也支持前32种信号,常配合sigaction一起使用2.发送的信号只能发给一个进程,不能发送给进程组3.sig=0时的用法等同于kill函数
 
  函数名:int raise(int sig)功能:给本进程或者线程发送指定信号(自己给自己发),等价于 kill(getpid(), sig)
 
  1.在单线程程序中等价于 kill(getpid(), sig)2.在多线程程序中等价于 pthread_kill(pthread_self(), sig)3.该函数会在信号处理函数执行完成后返回
 
  函数名:unsigned int alarm(unsigned int seconds)功能:在seconds秒后给本进程发送SIGALRM信号
 
  1.发送信号后的默认处理函数是终止进程2.若seconds=0, 则任何未决状态的SIGALRM都会被取消3.alarm函数,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸……无论进程处于何种状态,alarm都计时
 
  函数名:void abort(void)功能:给自己发送异常终止信号SIGABRT,并产生core文件,等价于kill(getpid(), SIGABRT);
 
  1.该函数先解除对SIGABRT信号的屏蔽2.该函数最终的结果是终止进程3. 如果SIGABRT被注册了一个捕获函数,那么执行abort()还会导致进程终止吗?由于SIGABRT被执行完捕获函数后会恢复为默认,然后abort再次发送SIGABRT,进程依然被终止
 
  十,常用的信号处理函数:
 
  signal该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它
 
  关于sigaction函数中:
 
  struct sigaction结构体:
 
  struct sigaction {
      void(*sa_handler)(int); //旧的信号处理函数指针
      void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
      //不要同时设置sa_handle和sa_sigaction, 给其中之一赋值就行
      sigset_t   sa_mask;      //信号阻塞集
      int        sa_flags;     //信号处理的方式
      void(*sa_restorer)(void); //已弃用
  };
  sa_flags:通常设置为0,表示使用默认属性。
 
  sa_handler:指定信号捕捉后的处理函数,即注册回调函数。该成员也可以赋值为SIG_IGN,表示忽略该信号,也可注册为SIG_DFL,表示执行信号的默认动作。
 
  sa_mask:临时阻塞信号集(或信号屏蔽字)先来看这样一个情景:
 
  某个信号已经注册了回调函数,当内核传递这个信号过来时,会先经过一个阻塞信号集,先阻塞掉部分信号。再去执行对应的回调函数。如下图示:
 
  十一,信号集:
 
  多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t信号集的常用函数:
 
  #include   
  //sigset_t set
  int sigemptyset(sigset_t *set);       //将set集合置空
  int sigfillset(sigset_t *set);          //将所有信号加入set集合
  int sigaddset(sigset_t *set, int signo);  //将signo信号加入到set集合
  int sigdelset(sigset_t *set, int signo);   //从set集合中移除signo信号
  int sigismember(const sigset_t *set, int signo); //判断信号是否存在
  sigset_t类型变量必须使用sigemptyset或sigfillset初始化,以防止该变量所在内存位置的原有数据对sigset_t的影响。进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。阻塞信号集: 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(处理发生在解除屏蔽后)。未决信号集:信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
 
  阻塞信号集的处理:
 
  #include
  int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  功能:
      检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
  参数:
      how : 信号阻塞集合的修改方法,有 3 种情况:
          SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。?相当于 mask = mask|set。
          SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
          SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set。
      set : 要操作的信号集地址。
          若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
      oldset : 保存原先信号阻塞集地址
  返回值:
      成功:0,
      失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。
  未决信号集的处理:
 
  #include
  int sigpending(sigset_t *set);
  功能:读取当前进程的未决信号集
  参数:
      set:未决信号集
  返回值:
      成功:0
      失败:-1
  十二,代码实例:
 
  Demo1:
 
  #include
  #include
  #include
  #include
  #include
  #include
  void handler(int sig)
  {
      printf("获取到SIGINT信号!\n");
      exit(0);
  }
  int main(void)
  {
      int pid;
      pid = fork();
      if(pid < 0)
      {
          printf("进程创建出错:%d.\n",errno);
          exit(0);
      }
      else if(pid == 0)
      {
          printf("子进程创建成功,进程ID为:%d.\n",getpid());
          signal(SIGINT,handler);
          while(1)
          {
              sleep(1);
              printf("通过!\n");
          }
      }
      else
      {
          printf("这是父进程,进程ID为:%d.",getpid());
          sleep(6);
          kill(pid,SIGINT);
          wait(NULL);
          printf("父进程退出!\n");
      }
      return 0;
  }

  Demo2:
 
  #include
  #include
  #include
  #include
  #include
  #include
  int main()
  {
      pid_t pid = fork();
      if (pid == 0)
      {//子进程
          int i = 0;
          for (i = 0; i<5; i++)
          {
              printf("in son process\n");
              sleep(1);
          }
      }
      else
      {//父进程
          printf("in father process\n");
          sleep(2);
          printf("kill sub process now \n");
          kill(pid, SIGINT);
      }
      return 0;
  }

  Demo3:
 
  #include
  #include
  #include
  #include
  #include
  #include
  int main()
  {
      sigset_t set;   // 定义一个信号集变量
      int ret = 0;
      sigemptyset(&set); // 清空信号集的内容
      // 判断 SIGINT 是否在信号集 set 里
      // 在返回 1, 不在返回 0
      ret = sigismember(&set, SIGINT);
      if (ret == 0)
      {
          printf("SIGINT is not a member of set \nret = %d\n", ret);
      }
      sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set
      sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set
      // 判断 SIGINT 是否在信号集 set 里
      // 在返回 1, 不在返回 0
      ret = sigismember(&set, SIGINT);
      if (ret == 1)
      {
          printf("SIGINT is a member of set \nret = %d\n", ret);
      }
      sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除
      // 判断 SIGQUIT 是否在信号集 set 里
      // 在返回 1, 不在返回 0
      ret = sigismember(&set, SIGQUIT);
      if (ret == 0)
      {
          printf("SIGQUIT is not a member of set \nret = %d\n", ret);
      }
      return 0;
  }
 

(编辑:开发网_新乡站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章