Linux进程间通信(二)

信号

信号的概念

信号是Linux进程间通信的最古老的一种方式。信号是软件中断,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某个突发事件。

一旦产生信号,就要执行信号处理函数,处理完信号处理函数,再回来执行主函数,这就是中断。

一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号的进程中的销毁,执行信号处理函数。

Linux进程间通信(二)

注意:这里信号的产生、注册、注销都是信号的内部机制,而不是信号的函数实现。

查看信号

可以通过 kill -l命令查看系统定义的信号列表:

Linux进程间通信(二)

可以看到,不存在编号为0的信号。其中1-31号信号成为常规信号(也叫普通信号和标准信号),34-64称为实时信号,驱动编程与硬件相关。名字上差别不大,而前32个名字各不相同。

如果你想了解某个信号的产生条件和默认处理动作,可以通过指令 man signal_id signal

例如: man 2 signal 就是查看二号信号的作用

信号四要素

  • 编号
  • 名称
  • 事件
  • 默认处理动作

Linux进程间通信(二)

可以看到有一些信号具有三个”value”,第一个值通常对alpha和sparc架构有用,中间值针对x86和arm架构,最后一个应用于mips架构。

不同的操作系统定义了不同的系统信号,这里我们之研究linux系统中的信号。

Action为默认动作:

  • Term:终止进程
  • Ign:忽略信号
  • Core:终止进程,生成Core文件。(检查死亡原因,用于gdb调试)
  • Stop:终止(暂停)进程
  • Cont:继续执行进程
  • *强调 9)SIGKILL和19)SIGSTOP信号,不允许忽略和捕捉,只能执行默认的动作,甚至不能将其设为阻塞

信号的产生

通过按键产生

当用户按下某些终端按键的时候,将产生信号

  • 按键 Ctrl C可以发送2号信号(SIG_INT),默认处理动作是终止进程
  • 按键按下 Ctrl \,发送3号信号(SIG_QUIT),默认处理动作是终止进程并且 Core Dump
  • 按键按下 Ctrl z查安生终端信号SIGSTOP ,默认动作是暂停进程的执行

Core Dump是什么?

当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到 磁盘上,文件名通常是core,这叫做Core Dump。我们可以通过使用gdb调试查看core文件查看进程退出的原因,这也叫 事后调试

通过系统调用

下面介绍三个系统函数

(1)kill函数

#include<sys types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
&#x529F;&#x80FD;&#xFF1A;&#x7ED9;&#x6307;&#x5B9A;&#x8FDB;&#x7A0B;&#x53D1;&#x9001;&#x6307;&#x5B9A;&#x4FE1;&#x53F7;&#xFF08;&#x4E0D;&#x4E00;&#x5B9A;&#x6740;&#x6B7B;&#xFF09;
&#x53C2;&#x6570;&#xFF1A;
    pid&#xFF1A;&#x53D6;&#x503C;&#x6709;4&#x79CD;&#x60C5;&#x51B5;&#xFF1A;
      pid > 0&#xFF1A;&#x5C06;&#x4FE1;&#x53F7;&#x4F20;&#x9001;&#x7ED9;&#x8FDB;&#x7A0B;ID&#x4E3A;pid&#x7684;&#x8FDB;&#x7A0B;&#x3002;
      pid = 0&#xFF1A;&#x5C06;&#x4FE1;&#x53F7;&#x4F20;&#x9001;&#x7ED9;&#x5F53;&#x524D;&#x8FDB;&#x7A0B;&#x6240;&#x5728;&#x8FDB;&#x7A0B;&#x7EC4;&#x4E2D;&#x7684;&#x6240;&#x6709;&#x8FDB;&#x7A0B;&#x3002;
      pid = -1&#xFF1A;&#x5C06;&#x4FE1;&#x53F7;&#x4F20;&#x9001;&#x7ED9;&#x7CFB;&#x7EDF;&#x5185;&#x6240;&#x6709;&#x7684;&#x8FDB;&#x7A0B;&#x3002;
      pid < -1&#xFF1A;&#x5C06;&#x4FE1;&#x53F7;&#x4F20;&#x7ED9;&#x6307;&#x5B9A;&#x8FDB;&#x7A0B;&#x7EC4;&#x7684;&#x6240;&#x6709;&#x8FDB;&#x7A0B;&#x3002;&#x8FD9;&#x4E2A;&#x8FDB;&#x7A0B;&#x7EC4;&#x53F7;&#x7B49;&#x4E8E;pid&#x7684;&#x7EDD;&#x5BF9;&#x503C;&#x3002;
      sig&#xFF1A;&#x4FE1;&#x53F7;&#x7684;&#x7F16;&#x53F7;&#xFF0C;&#x8FD9;&#x91CC;&#x53EF;&#x4EE5;&#x586B;&#x6570;&#x5B57;&#x7F16;&#x53F7;&#xFF0C;&#x4E5F;&#x53EF;&#x4EE5;&#x586B;&#x4FE1;&#x53F7;&#x7684;&#x5B8F;&#x5B9A;&#x4E49;&#xFF0C;&#x53EF;&#x4EE5;&#x901A;&#x8FC7;
&#x547D;&#x4EE4;kill -l&#x8FDB;&#x884C;&#x76F8;&#x5E94;&#x67E5;&#x770B;&#x3002;&#x4E0D;&#x51C6;&#x8350;&#x76F4;&#x63A5;&#x4F7F;&#x6570;&#x5B57;&#xFF0C;&#x5E94;&#x4F7F;&#x7528;&#x5B8F;&#x540D;&#xFF0C;&#x56E0;&#x4E3A;&#x4E0D;&#x540C;&#x64CD;&#x4F5C;&#x7CFB;&#x7EDF;&#x4FE1;&#x53F7;&#x7F16;&#x53F7;&#x53EF;&#x80FD;&#x4E0D;&#x540C;&#xFF0C;&#x4F46;&#x540D;&#x79F0;&#x4E00;&#x81F4;&#x3002;
&#x8FD4;&#x56DE;&#x503C;&#xFF0E;
    &#x6210;&#x529F;&#xFF1A;0
    &#x5931;&#x8D25;&#xFF1A;-1
</signal.h></sys>
  • super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。
  • 普通用户不能向其他普通用户发送信号,终止其进程,只能向自己创建的进程发送信号。

代码示例:

#include<stdio.h>
#include<signal.h>
#include<sys types.h>
#include<unistd.h>
 int main()
 {
    pid_t pid;
    //&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x5B50;&#x8FDB;&#x7A0B;
    pid = fork()&#xFF1B;
     {
        printf("child process do work.....\n");
        sleep(1);
     }
      exit(0);//&#x5B50;&#x8FDB;&#x7A0B;&#x9000;&#x51FA;
    }
    else
    {
      //&#x7236;&#x8FDB;&#x7A0B;
      sleep(3);
      printf("&#x5B50;&#x8FDB;&#x7A0B;&#x4E0D;&#x542C;&#x8BDD;&#x4E86;&#xFF0C;&#x8BE5;&#x9000;&#x51FA;&#x4E86;....\n");
      kill(pid,15);
      printf("&#x7236;&#x8FDB;&#x7A0B;&#x8BE5;&#x7ED3;&#x675F;&#x4E86;&#xFF0C;&#x5DF2;&#x7ECF;&#x5B8C;&#x6210;&#x4E86;&#x5B83;&#x7684;&#x4F7F;&#x547D;\n");

    }
   return 0;
 }
</unistd.h></sys></signal.h></stdio.h>

运行结果如下

Linux进程间通信(二)

(2)raise函数

#include<signal.h>
int raise(int sig)&#xFF1B;
&#x529F;&#x80FD;&#xFF1A;&#x7ED9;&#x5F53;&#x524D;&#x8FDB;&#x7A0B;&#x53D1;&#x9001;&#x6307;&#x5B9A;&#x4FE1;&#x53F7;&#xFF08;&#x81EA;&#x5DF1;&#x7ED9;&#x81EA;&#x5DF1;&#x53D1;&#xFF09;&#xFF0C;&#x7B49;&#x4EF7;&#x4E8E;kill(getpid()&#xFF0C;sig)
&#x53C2;&#x6570;&#xFF1A;
    sig&#xFF1A;&#x4FE1;&#x53F7;&#x7F16;&#x53F7;
&#x8FD4;&#x56DE;&#x503C;&#xFF1A;&#x6210;&#x529F;&#xFF1A;0
       &#x5931;&#x8D25;&#xFF1A;&#x975E;0&#x503C;
</signal.h>

代码示例:

#include<stdio.h>
#include<signal.h>
#include<sys types.h>
#include<unistd.h>
 int main()
 {
   int i = 1;
   while(1)
   {
        printf("do working %d\n",i);
        //&#x7ED9;&#x81EA;&#x5DF1;&#x53D1;&#x9001;&#x4E00;&#x4E2A;&#x4FE1;&#x53F7;
        if(i == 4)
        {
            //&#x81EA;&#x5DF1;&#x7ED9;&#x81EA;&#x5DF1;&#x53D1;&#x9001;&#x7F16;&#x53F7;&#x4E3A;15&#x7684;&#x4FE1;&#x53F7;
            raise(SIGTERM);
        }
        i ++;
        sleep(1);
   }
   return 0;
 }
</unistd.h></sys></signal.h></stdio.h>

运行结果如下:

Linux进程间通信(二)

(3)abort函数

#include<stdlib.h>
void abort(void);
&#x529F;&#x80FD;&#xFF1A;&#x7ED9;&#x81EA;&#x5DF1;&#x53D1;&#x9001;&#x5F02;&#x5E38;&#x7EC8;&#x6B62;&#x4FE1;&#x53F7;6&#xFF09;SIGABRT&#xFF0C;&#x5E76;&#x4E14;&#x4EA7;&#x751F;core&#x6587;&#x4EF6;&#xFF0C;&#x7B49;&#x4EF7;&#x4E8E;kill(getpid(),SIGABRT);
&#x53C2;&#x6570;&#xFF1A;&#x65E0;
&#x8FD4;&#x56DE;&#x503C;&#xFF1A;&#x65E0;
</stdlib.h>

代码示例:

#include<stdio.h>
#include<signal.h>
#include<sys types.h>
#include<unistd.h>
 int main()
 {
   int i = 1;
   while(1)
   {
        printf("do working %d\n",i);
        //&#x7ED9;&#x81EA;&#x5DF1;&#x53D1;&#x9001;&#x4E00;&#x4E2A;&#x4FE1;&#x53F7;
        if(i == 4)
        {
            //&#x7ED9;&#x81EA;&#x5DF1;&#x53D1;&#x9001;&#x4E00;&#x4E2A;&#x7F16;&#x53F7;&#x4E3A;6&#x7684;&#x4FE1;&#x53F7;&#xFF0C;&#x9ED8;&#x8BA4;&#x7684;&#x884C;&#x4E3A;&#x5C31;&#x662F;&#x7EC8;&#x6B62;&#x8FDB;&#x7A0B;
            abort();
        }
        i ++;
        sleep(1);
   }
   return 0;
 }
</unistd.h></sys></signal.h></stdio.h>

运行结果如下:

Linux进程间通信(二)

通过软件条件产生

在上一篇博客介绍过,管道如果读端不读了,存储系统会发生 SIGPIPE 信号给写端进程,终止进程。这个信号就是由一种软件条件产生的,这里再介绍一种由软件条件产生的信号 SIGALRM(时钟信号)。

#include<unistd.h>
unsigned int alarm(unsigned int seconds)&#xFF1B;
&#x529F;&#x80FD;&#xFF1A;
    &#x8BBE;&#x7F6E;&#x5B9A;&#x65F6;&#x5668;&#xFF08;&#x95F9;&#x949F;&#xFF09;&#x3002;&#x5728;&#x6307;&#x5B9A;seconds&#x540E;&#xFF0C;&#x5185;&#x6838;&#x4F1A;&#x7ED9;&#x5F53;&#x524D;&#x8FDB;&#x7A0B;&#x53D1;&#x9001;14&#xFF09;SIGALRM&#x4FE1;&#x53F7;,&#x8FDB;&#x7A0B;&#x6536;&#x5230;&#x8BE5;&#x4FE1;&#x53F7;&#xFF0C;&#x9ED8;&#x8BA4;&#x52A8;&#x4F5C;&#x7EC8;&#x6B62;&#x3002;&#x6BCF;&#x4E2A;&#x8FDB;&#x7A0B;&#x90FD;&#x6709;&#x4E14;&#x53EA;&#x6709;&#x552F;&#x4E00;&#x7684;&#x4E00;&#x4E2A;&#x5B9A;&#x65F6;&#x5668;&#x3002;
     &#x53D6;&#x6D88;&#x5B9A;&#x65F6;&#x5668;alarm&#xFF08;0&#xFF09;&#xFF0C;&#x8FD4;&#x56DE;&#x65E7;&#x95F9;&#x949F;&#x4F59;&#x4E0B;&#x79D2;&#x6570;&#x3002;
&#x53C2;&#x6570;&#xFF1A;
     seconds&#xFF1A;&#x6307;&#x5B9A;&#x7684;&#x65F6;&#x95F4;&#xFF0C;&#x4EE5;&#x79D2;&#x4E3A;&#x5355;&#x4F4D;
&#x8FD4;&#x56DE;&#x503C;:
     &#x8FD4;&#x56DE;0&#x6216;&#x5269;&#x4F59;&#x7684;&#x79D2;&#x6570;
</unistd.h>
  • 定时,与进程状态无关(自然定时)就绪、运行、挂起(阻塞,暂停)、终止、僵尸…….无论进程处于何种状态,alarm都计时。

代码示例:

#include<stdio.h>
#include<signal.h>
#include<sys types.h>
#include<unistd.h>
 int main()
 {
   unsigned int ret = 0;
   //&#x7B2C;&#x4E00;&#x6B21;&#x8BBE;&#x7F6E;&#x95F9;&#x949F;5&#x79D2;&#x4E4B;&#x540E;&#x5C31;&#x8D85;&#x65F6; &#x53D1;&#x9001;&#x5BF9;&#x5E94;&#x7684;&#x4FE1;&#x53F7;
   ret = alarm(5);
   printf("&#x4E0A;&#x4E00;&#x6B21;&#x95F9;&#x949F;&#x5269;&#x4E0B;&#x7684;&#x65F6;&#x95F4;&#x662F;%u\n",ret);
   sleep(2);
   //&#x4E4B;&#x524D;&#x6CA1;&#x6709;&#x8D85;&#x65F6;&#x7684;&#x95F9;&#x949F;&#x88AB;&#x65B0;&#x7684;&#x95F9;&#x949F;&#x7ED9;&#x8986;&#x76D6;
   ret = alarm(4);
   printf("&#x4E0A;&#x4E00;&#x6B21;&#x95F9;&#x949F;&#x5269;&#x4E0B;&#x7684;&#x65F6;&#x95F4;&#x662F;%u\n",ret);
   printf("&#x6309;&#x4E0B;&#x4EFB;&#x610F;&#x952E;&#x7EE7;&#x7EED;...");
   getchar();
   return 0;
 }
</unistd.h></sys></signal.h></stdio.h>

运行结果:

Linux进程间通信(二)

通过硬件异常产生

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
这里给大家介绍两个硬件异常: CPU产生异常MMU产生异常

CPU产生异常 发生除零错误,CPU运行单元会产生异常,内核将这个异常解释为信号,最后OS发送SIGFPE信号给进程。

代码示例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
  // &#x7531;&#x8F6F;&#x4EF6;&#x6761;&#x4EF6;&#x4EA7;&#x751F;&#x4FE1;&#x53F7;  alarm&#x51FD;&#x6570;&#x548C;SIGPIPE
  // CPU&#x8FD0;&#x7B97;&#x5355;&#x5143;&#x4EA7;&#x751F;&#x5F02;&#x5E38;&#xFF0C;&#x5185;&#x6838;&#x5C06;&#x8FD9;&#x4E2A;&#x5F02;&#x5E38;&#x5904;&#x7406;&#x4E3A;SIGFPE&#x4FE1;&#x53F7;&#x53D1;&#x9001;&#x7ED9;&#x8FDB;&#x7A0B;
  int a = 10;
  int b = 0;
  printf("%d", a/b);
  return 0;
}
</stdlib.h></signal.h></unistd.h></stdio.h>

运行结果小伙伴们自己下去运行一下吧~这里就不截图了

MMU产生异常: 当进程访问非法地址时,mmu想通过页表映射来将虚拟转换为物理地址,此时发现页表中不存在该虚拟地址,此时会产生异常,然后OS将异常解释为SIGSEGV信号,然后发送给进程

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
  // MMU&#x786C;&#x4EF6;&#x4EA7;&#x751F;&#x5F02;&#x5E38;&#xFF0C;&#x5185;&#x6838;&#x5C06;&#x8FD9;&#x4E2A;&#x5F02;&#x5E38;&#x5904;&#x7406;&#x4E3A;SIGSEGV&#x4FE1;&#x53F7;&#x53D1;&#x9001;&#x7ED9;&#x8FDB;&#x7A0B;
  signal(11, handler);
  int* p = NULL;
  printf("%d\n", *p);
  return 0;
}
</stdlib.h></signal.h></unistd.h></stdio.h>

signal函数实现信号的捕捉

#include<signal.h>
typedef void(*sighandler_t)(int)&#xFF1B;//&#x5B83;&#x5B9A;&#x4E49;&#x4E86;&#x4E00;&#x4E2A;&#x7C7B;&#x578B;sighandler_t&#xFF0C;&#x8868;&#x793A;&#x6307;&#x5411;&#x8FD4;&#x56DE;&#x503C;&#x4E3A;void&#x578B;&#xFF08;&#x53C2;&#x6570;&#x4E3A;int&#x578B;&#xFF09;&#x7684;&#x51FD;&#x6570;&#xFF08;&#x7684;&#xFF09;&#x6307;&#x9488;
sighandler_t signal(int signum&#xFF0C;sighandler_t handler);
&#x529F;&#x80FD;&#xFF1A;
    &#x6CE8;&#x518C;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#xFF08;&#x4E0D;&#x53EF;&#x7528;&#x4E8E;SIGKILL&#x3001;SIGSTOP&#x4FE1;&#x53F7;&#xFF09;&#xFF0C;&#x5373;&#x786E;&#x5B9A;&#x6536;&#x5230;&#x4FE1;&#x53F7;&#x540E;&#x5904;&#x7406;&#x51FD;&#x6570;&#x7684;&#x5165;&#x53E3;&#x5730;&#x5740;&#x3002;&#x6B64;&#x51FD;&#x6570;&#x4E0D;&#x4F1A;&#x963B;&#x585E;&#x3002;
&#x53C2;&#x6570;:
    signum:&#x4FE1;&#x53F7;&#x7684;&#x7F16;&#x53F7;&#xFF0C;&#x8FD9;&#x91CC;&#x53EF;&#x4EE5;&#x586B;&#x6570;&#x5B57;&#x7F16;&#x53F7;&#xFF0C;&#x4E5F;&#x53EF;&#x4EE5;&#x586B;&#x4FE1;&#x53F7;&#x7684;&#x5B8F;&#x4E49;&#x3002;
    handler&#xFF1A;&#x53D6;&#x503C;&#x6709;3&#x79CD;&#x60C5;&#x51B5;&#xFF1A;
             SIG_IGN&#xFF1A;&#x5FFD;&#x7565;&#x8BE5;&#x4FE1;&#x53F7;
             SIG_DFL&#xFF1A;&#x6267;&#x884C;&#x7CFB;&#x7EDF;&#x9ED8;&#x8BA4;&#x52A8;&#x4F5C;
             &#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#x540D;&#xFF1A;&#x81EA;&#x5B9A;&#x4E49;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#xFF0C;&#x5982;&#xFF1A;func
             &#x56DE;&#x8C03;&#x51FD;&#x6570;&#x7684;&#x5B9A;&#x4E49;&#x5982;&#x4E0B;&#xFF1A;
             void func(int signo)
             {
                //signo&#x4E3A;&#x89E6;&#x53D1;&#x7684;&#x4FE1;&#x53F7;&#xFF0C;&#x4E3A;signal()&#x7B2C;&#x4E00;&#x4E2A;&#x53C2;&#x6570;&#x7684;&#x503C;
             }

&#x8FD4;&#x56DE;&#x503C;:
    &#x6210;&#x529F;&#xFF1A;&#x7B2C;&#x4E00;&#x6B21;&#x8FD4;&#x56DE;NULL,&#x4E0B;&#x4E00;&#x6B21;&#x8FD4;&#x56DE;&#x6B64;&#x4FE1;&#x53F7;&#x4E0A;&#x4E00;&#x6B21;&#x6CE8;&#x518C;&#x7684;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#x7684;&#x5730;&#x5740;&#x3002;&#x5982;&#x679C;&#x9700;&#x8981;&#x4F7F;&#x7528;&#x6B64;&#x8FD4;&#x56DE;&#x503C;&#xFF0C;&#x5FC5;&#x987B;&#x5728;&#x524D;&#x9762;&#x5148;&#x58F0;&#x660E;&#x6B64;&#x51FD;&#x6570;&#x6307;&#x9488;&#x7684;&#x7C7B;&#x578B;&#x3002;
    &#x5931;&#x8D25;&#xFF1A;&#x8FD4;&#x56DE;SIG_ERR
</signal.h>

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
//&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;
void fun1(int signum)
{
    printf("&#x6355;&#x6349;&#x5230;&#x4FE1;&#x53F7;:%d\n",signum);
}
//&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;2
void fun2(int signum)
{
    printf("&#x6355;&#x6349;&#x5230;&#x4FE1;&#x53F7;:%d\n",signum);
}
//&#x4FE1;&#x53F7;&#x6CE8;&#x518C;&#x51FD;&#x6570;
int main()
{
    //&#x4FE1;&#x53F7;&#x6CE8;&#x518C;
    //ctrl+c
    signal(SIGINT,fun1);
    //ctrl+\
    signal(SIGQUIT,fun2);
    while(1)
    {
      sleep(1);
    }
    return 0;
}
</stdlib.h></signal.h></unistd.h></stdio.h>

运行结果如下:

Linux进程间通信(二)

信号先注册,进程不要退出,然后等待信号的到达,信号到达之后就会执行信号处理函数。

阻塞信号

了解几个概念:

  • 实际执行信号的处理动作称为信号递达
  • 信号递达的三种方式:默认、忽略和自定义捕捉
  • 信号从产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以选择阻塞 (Block )某个信号
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作 注意
  • 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
  • OS发生信号给一个进程,此信号不是立即被处理的,那么这个时间窗口中,信号就需要被记录保存下来,那么信号是如何在内核中保存和表示的呢?

Linux进程间通信(二)

每个进程都存在一个PCB,在PCB中存在两个集合,一个是未决信号集,一个是阻塞信号集。

以SIGINT为例说明未决信号集和阻塞信号集的关系:

  • 当进程收到SIGINT信号(信号编号为2),首先这个信号会保存在未决信号集合中,此时对应的2号编号的这个位置上置为1,表示处于未决状态;在这个信号需要被处理之前首先要在阻塞信号集中的编号2的位置上区检查该值是否为1
  • 如果是1,表示SIGINT信号被当前进程阻塞了,这个信号暂时不被处理,所以未决集上该位置上的值保持为1,表示该信号处于未决状态
  • 如果是0,表示SIGINT信号没有被当前进程阻塞,这个信号需要被处理,内核会对SIGINT信号进行处理(执行默认动作,忽略或者执行用户自定义的信号处理函数),并将未决信号集合中编号2的位置将1置为0,表示该信号已经被处理了,这个时间非常短
  • 当SIGINT信号从阻塞信号集中解除阻塞之后,该信号就会被处理

注意:

未决信号集在内核中,要对内核进行操作只能通过系统调用,但是没有提供这样的方法,所以只能对未决信号集进行读操作,但是可以对阻塞信号集进行读写操作。

问题1:所有信号的产生都要由OS来进行执行,这是为什么?

信号的产生涉及到软硬件,且OS是软硬件资源的管理者,还是进程的管理者。

问题2:进程在没有收到信号的时候,能否知道自己应该如何对合法信号进行处理呢?

答案是能知道的。每个进程都可以通过task_struct找到表示信号的三张表。此时该进程的未决信号集表中哪些信号对应的那一位比特位是为0的,且进程能够查看阻塞信号集表知道如果收到该信号是否需要阻塞,可以查看handler表知道对该信号的处理动作。

问题3:OS如何发生信号?

OS给某一个进程发送了某一个信号后,OS会找到信号在进程中未决信号集表对应的那一位比特位,然后把那一位比特位由0置1,这样OS就完成了信号发送的过程。

信号集操作函数

sigset_t: 未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为 信号集,也被定义为一种数据类型。这个类型可以表示每个信号状态处于何种状态(是否被阻塞,是否处于未决状态)。阻塞信号集也叫做当前进程的信号屏蔽字,这里的”屏蔽”应该理解为阻塞而不是忽略。

实际上两个信号集在都是内核使用 位图机制来实现的,想了解的可以自己去了解下,但是操作系统不允许我们直接对其操作。而需要自定义另外一个集合,借助于信号集操作函数来对PCB中的这两个信号集进行修改。

信号集操作函数: sigset_t类型对于每种信号用一个bit表示”有效”或”无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释。
注意: 对应sigset类型的变量,我们不可以直接使用位操作来进行操作,而是一个严格实现系统给我们提供的库函数来对这个类型的变量进行操作。

下面是信号集操作函数的原型:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
</signal.h>
  • sigemptyset: 初始化set指向的信号集,将所有比特位置0
  • sigfillset: 初始化set指向的信号集,将所有比特位置1
  • sigaddset: 把set指向的信号集中signum信号对应的比特位置1
  • sigdelset: 把set指向的信号集中signum信号对应的比特位置0
  • sigismember: 判断signum信号是否存在set指向的信号集中(本质是信号判断对应位是否为1)

注意: 在实现这些函数之前,需要使用 sigemptysetsigfillset对信号集进行初始化。前四个函数的返回值是成功返回0,失败返回-1。最后一个函数的返回值是真返回1,假返回-1

阻塞信号集操作函数——sigprocmask:

#include<signal.h>
int sigprocmask(int how&#xFF0C;const sigset_t *set,sigset_t *oldset&#xFF09;&#xFF1B;
&#x529F;&#x80FD;&#xFF1A;
    &#x68C0;&#x67E5;&#x6216;&#x4FEE;&#x6539;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#xFF0C;&#x6839;&#x636E;how&#x6307;&#x5B9A;&#x7684;&#x65B9;&#x6CD5;&#x5BF9;&#x8FDB;&#x7A0B;&#x7684;&#x963B;&#x585E;&#x96C6;&#x5408;&#x8FDB;&#x884C;&#x4FEE;&#x6539;&#xFF0C;&#x65B0;&#x7684;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x7531;set&#x6307;&#x5B9A;&#xFF0C;&#x800C;&#x539F;&#x5148;&#x7684;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#x7531;oldset&#x4FDD;&#x5B58;&#x3002;
&#x53C2;&#x6570;:
    how&#xFF1A;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#x7684;&#x4FEE;&#x6539;&#x65B9;&#x6CD5;&#xFF0C;&#x6709;3&#x79CD;&#x60C5;&#x51B5;&#xFF1A;
    SIG_BLOCK&#xFF1A;&#x5411;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#x4E2D;&#x6DFB;&#x52A0;set&#x4FE1;&#x53F7;&#x96C6;&#xFF0C;&#x65B0;&#x7684;&#x4FE1;&#x53F7;&#x63A9;&#x7801;&#x662F;set&#x548C;&#x65E7;&#x4FE1;&#x53F7;&#x63A9;&#x7801;&#x7684;&#x5E76;&#x96C6;&#x3002;&#x76F8;&#x5F53;&#x4E8E;mask=mask&#x4E28;set&#x3002;
    SIG_UNBLOCK&#xFF1A;&#x4ECE;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#x4E2D;&#x5220;&#x9664;set&#x4FE1;&#x53F7;&#x96C6;&#xFF0C;&#x4ECE;&#x5F53;&#x524D;&#x4FE1;&#x53F7;&#x63A9;&#x7801;&#x4E2D;&#x53BB;&#x9664;set&#x4E2D;&#x7684;&#x4FE1;&#x53F7;&#x3002;&#x76F8;&#x5F53;&#x4E8E;mask=mask&~set&#x3002;
    SIG_SETMASK&#xFF1A;&#x5C06;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#x8BBE;&#x4E3A;set&#x4FE1;&#x53F7;&#x96C6;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x539F;&#x6765;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x7684;&#x5185;&#x5BB9;&#x6E05;&#x7A7A;&#xFF0C;&#x7136;&#x540E;&#x6309;&#x7167;set&#x4E2D;&#x7684;&#x4FE1;&#x53F7;&#x91CD;&#x65B0;&#x8BBE;&#x7F6E;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x3002;&#x76F8;&#x5F53;&#x4E8E;mask=set&#x3002;
    set&#xFF1A;&#x8981;&#x64CD;&#x4F5C;&#x7684;&#x4FE1;&#x53F7;&#x96C6;&#x5730;&#x5740;&#x3002;
         &#x82E5;set&#x4E3A;NULL,&#x5219;&#x4E0D;&#x6539;&#x53D8;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#xFF0C;&#x51FD;&#x6570;&#x53EA;&#x628A;&#x5F53;&#x524D;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5408;&#x4FDD;&#x5B58;&#x5230;oldset&#x4E2D;&#x3002;
    oldset: &#x4FDD;&#x5B58;&#x539F;&#x5148;&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;&#x5730;&#x5740;&#x3002;

&#x8FD4;&#x56DE;&#x503C;&#xFF1A;&#x6210;&#x529F;&#xFF1A;0  &#x5931;&#x8D25;&#xFF1A;-1
</signal.h>

未决信号集操作函数——sigpending:

#include<signal.h>

int sigpending(sigset_t *set);
&#x529F;&#x80FD;&#xFF1A;&#x8BFB;&#x53D6;&#x5F53;&#x524D;&#x8FDB;&#x7A0B;&#x7684;&#x672A;&#x51B3;&#x4FE1;&#x53F7;&#x96C6;
&#x53C2;&#x6570;&#xFF1A;
    set&#xFF1A;&#x672A;&#x51B3;&#x4FE1;&#x53F7;&#x96C6;
&#x8FD4;&#x56DE;&#x503C;&#xFF1A;
    &#x6210;&#x529F;&#xFF1A;0  &#x5931;&#x8D25;&#xFF1A;-1
</signal.h>

代码示例:

实验一:把进程中信号屏蔽字2号信号进行阻塞,然后每隔1s对未决信号集进行打印,观察现象。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void PrintPending(sigset_t* pend)
{
  int i = 0;
  for (i = 1; i < 32; ++i)
  {
    if (sigismember(pend, i)){
      printf("1");
    }
    else{
      printf("0");
    }
  }
  printf("\n");
}
int main()
{
  sigset_t set, oset;
  sigset_t pending;
  // &#x4F7F;&#x7528;&#x7CFB;&#x7EDF;&#x51FD;&#x6570;&#x5BF9;&#x4FE1;&#x53F7;&#x96C6;&#x8FDB;&#x884C;&#x521D;&#x59CB;&#x5316;
  sigemptyset(&set);
  sigemptyset(&oset);
  sigemptyset(&pending);

  // &#x963B;&#x585E;2&#x53F7;&#x4FE1;&#x53F7;
  // &#x5148;&#x7528;&#x7CFB;&#x7EDF;&#x51FD;&#x6570;&#x5BF9;set&#x4FE1;&#x53F7;&#x96C6;&#x8FDB;&#x884C;&#x8BBE;&#x7F6E;
  sigaddset(&set, 2);
  // &#x4F7F;&#x7528;sigprocmask&#x51FD;&#x6570;&#x66F4;&#x6539;&#x8FDB;&#x7A0B;&#x7684;&#x4FE1;&#x53F7;&#x5C4F;&#x853D;&#x5B57;
  // &#x7B2C;&#x4E00;&#x4E2A;&#x53C2;&#x6570;&#xFF0C;&#x4E09;&#x4E2A;&#x9009;&#x9879;&#xFF1A;SIG_BLOCK(mask |= set) SIG_UNBLOCK(mask &= ~set) SIG_SETMASK(mask = set)
  sigprocmask(SIG_BLOCK, &set, &oset);

  int flag = 1; // &#x8868;&#x793A;&#x5DF2;&#x7ECF;&#x963B;&#x585E;2&#x53F7;&#x4FE1;&#x53F7;
  int count = 0;
  while (1){
    // &#x4F7F;&#x7528;sigpending&#x51FD;&#x6570;&#x83B7;&#x53D6;pending&#x4FE1;&#x53F7;&#x96C6;
    sigpending(&pending);
    // &#x6253;&#x5370;pending&#x4F4D;&#x56FE;
    PrintPending(&pending);
    sleep(1);
  }
  return 0;
}

</signal.h></unistd.h></stdio.h>

运行结果如下:

可以看到,进程收到2号信号时,且该信号被阻塞,处于未决状态,未决信号集中2号信号对应的比特位由0置1

Linux进程间通信(二)

实例2: 将上面的代码进行修改,进行运行10s后,我们将信号屏蔽字中2号信号解除屏蔽

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void PrintPending(sigset_t* pend)
{
  int i = 0;
  for (i = 1; i < 32; ++i)
  {
    if (sigismember(pend, i)){
      printf("1");
    }
    else{
      printf("0");
    }
  }
  printf("\n");
}
int main()
{
  sigset_t set, oset;
  sigset_t pending;
  // &#x4F7F;&#x7528;&#x7CFB;&#x7EDF;&#x51FD;&#x6570;&#x5BF9;&#x4FE1;&#x53F7;&#x96C6;&#x8FDB;&#x884C;&#x521D;&#x59CB;&#x5316;
  sigemptyset(&set);
  sigemptyset(&oset);
  sigemptyset(&pending);

  // &#x963B;&#x585E;2&#x53F7;&#x4FE1;&#x53F7;
  // &#x5148;&#x7528;&#x7CFB;&#x7EDF;&#x51FD;&#x6570;&#x5BF9;set&#x4FE1;&#x53F7;&#x96C6;&#x8FDB;&#x884C;&#x8BBE;&#x7F6E;
  sigaddset(&set, 2);
  // &#x4F7F;&#x7528;sigprocmask&#x51FD;&#x6570;&#x66F4;&#x6539;&#x8FDB;&#x7A0B;&#x7684;&#x4FE1;&#x53F7;&#x5C4F;&#x853D;&#x5B57;
  // &#x7B2C;&#x4E00;&#x4E2A;&#x53C2;&#x6570;&#xFF0C;&#x4E09;&#x4E2A;&#x9009;&#x9879;&#xFF1A;SIG_BLOCK(mask |= set) SIG_UNBLOCK(mask &= ~set) SIG_SETMASK(mask = set)
  sigprocmask(SIG_BLOCK, &set, &oset);

  int flag = 1; // &#x8868;&#x793A;&#x5DF2;&#x7ECF;&#x963B;&#x585E;2&#x53F7;&#x4FE1;&#x53F7;
  int count = 0;
  while (1){
    // &#x4F7F;&#x7528;sigpending&#x51FD;&#x6570;&#x83B7;&#x53D6;pending&#x4FE1;&#x53F7;&#x96C6;
    sigpending(&pending);
    // &#x6253;&#x5370;pending&#x4F4D;&#x56FE;
    PrintPending(&pending);
    if (++count == 10){
        // &#x4E24;&#x79CD;&#x65B9;&#x6CD5;&#x90FD;&#x53EF;&#x4EE5;
        sigprocmask(SIG_UNBLOCK, &set, &oset);
        //sigprocmask(SIG_SETMASK, &oset, NULL);
    }
sleep(1);
  }
  return 0;
}
</signal.h></unistd.h></stdio.h>

运行结果如下:

Linux进程间通信(二)

信号2被阻塞之后就变成了未决状态,当该信号从阻塞集合中解除的时候,该信号就会被处理,该信号被处理后,该信号的未决信号集的标志位将从1置为0。

信号的捕获

一个进程收到一个信号的时候,可以用以下的方法进行处理:

(1)执行系统默认动作:对大多数信号来说,系统默认动作就是来终止进行。

(2)忽略此信号(丢弃):接收到此信号后没有任何动作

(3)执行自定义信号处理函数(捕获):用户定义的信号处理函数处理该信号

注意:SIGKILL和SIGSTOP不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

信号捕捉的过程

先思考一个问题:信号是什么时候被进程处理的?

首先,不是立即被处理的。而是在合适的时候,这个合适的时候,具体指的是 进程从用户态切换回内核态时进行处理

这句话如何理解,什么是用户态?什么是内核态?

  • 用户态: 处于⽤户态的 CPU 只能受限的访问内存,用户的代码,并且不允许访问外围设备,权限比较低
  • 内核态: 处于内核态的 CPU 可以访问任意的数据,包括外围设备,⽐如⽹卡、硬盘等,权限比较高

注意: 操作系统中有一个cr寄存器来记录当前进程处于何种状态

进程空间分为用户空间和内核空间。此前我们介绍的页表都是用户级页表,其实还有内核级页表。进程的用户空间是通过用户级页表映射到物理内存上,内核空间是通过内核级页表映射到物理内存上,如下面简图所示:

Linux进程间通信(二)
  • 当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。
  • *最高 1G 的内核空间是被所有进程共享的!

进程有不同的用户空间,但是只有一个内核空间,不同进程的用户空间的代码和数据是不一样的,但是内核空间的代码和数据是一样的。
上面这些主要是想说:进程处于用户态访问的是用户空间的代码和数据,进程处于内核态,访问的是内核空间的代码和数据。

信号捕捉的整个过程:

Linux进程间通信(二)

从上面的图可以看出,进程是在返回用户态之前对信号进行检测,检测pending位图,根据信号处理动作,来对信号进行处理。这个处理动作是在内核态返回用户态后进行执行的,所以这里也就回答了开始提出的那一个问题了。

sigaction函数

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
&#x529F;&#x80FD;&#xFF1A;
   &#x68C0;&#x67E5;&#x6216;&#x4FEE;&#x6539;&#x6307;&#x5B9A;&#x4FE1;&#x53F7;&#x7684;&#x8BBE;&#x7F6E;&#xFF08;&#x6216;&#x540C;&#x65F6;&#x6267;&#x884C;&#x4E24;&#x79CD;&#x64CD;&#x4F5C;&#xFF09;
&#x53C2;&#x6570;&#xFF1A;
   signum&#xFF1A;&#x8981;&#x64CD;&#x4F5C;&#x7684;&#x51FD;&#x6570;
   act&#xFF1A;&#x8981;&#x8BBE;&#x7F6E;&#x7684;&#x5BF9;&#x4FE1;&#x53F7;&#x7684;&#x65B0;&#x5904;&#x7406;&#x65B9;&#x5F0F;&#xFF08;&#x4F20;&#x5165;&#x65B9;&#x5F0F;&#xFF09;
   oldact&#xFF1A;&#x539F;&#x6765;&#x5BF9;&#x4FE1;&#x53F7;&#x7684;&#x5904;&#x7406;&#x65B9;&#x5F0F;&#xFF08;&#x4F20;&#x51FA;&#x53C2;&#x6570;&#xFF09;

   &#x5982;&#x679C;act&#x6307;&#x9488;&#x975E;&#x7A7A;&#xFF0C;&#x5219;&#x8981;&#x6539;&#x53D8;&#x6307;&#x5B9A;&#x4FE1;&#x53F7;&#x7684;&#x5904;&#x7406;&#xFF08;&#x8BBE;&#x7F6E;&#xFF09;&#xFF0C;&#x5982;&#x679C;oldact&#x6307;&#x9488;&#x975E;&#x7A7A;&#xFF0C;&#x5219;&#x7CFB;&#x7EDF;&#x5C06;&#x6B64;&#x524D;&#x6307;&#x5B9A;&#x4FE1;&#x53F7;&#x7684;&#x5904;&#x7406;&#x65B9;&#x5F0F;&#x5165;oldact
&#x8FD4;&#x56DE;&#x503C;&#xFF1A;
     &#x6210;&#x529F;&#xFF1A;0
     &#x5931;&#x8D25;&#xFF1A;-1
</signal.h>

struct sigaction结构体:

struct sigaction {
    void     (*sa_handler)(int);//&#x65E7;&#x7684;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#x6307;&#x9488;
    void     (*sa_sigaction)(int, siginfo_t *, void *);//&#x65B0;&#x7684;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#x6307;&#x9488;
    sigset_t   sa_mask;//&#x4FE1;&#x53F7;&#x963B;&#x585E;&#x96C6;
    int        sa_flags;//&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x7684;&#x65B9;&#x5F0F;
    void     (*sa_restorer)(void);//&#x5DF2;&#x7ECF;&#x5F03;&#x7528;
};

(1)sa_handler、sa_sigaction:信号处理函数指针,和signal里面的函数指针用法是一样的,根据情况给两个指针赋值。

​ a)SIG_IGN:忽略该信号

​ b)SIG_DFL:执行系统默认的动作

​ c)处理函数名:自定义信号处理函数

(2)sa_mask:信号阻塞集,在执行信号处理函数的时候,用来临时的屏蔽信号

(3)sa_flags:用于指定信号处理的行为, 通常设置为0,表示使用默认的属性。它可以是一下值的”按位”或”组合”:

  • SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到SIGCHLD信号。
  • SA_NOCLDWAIT:使父进程在它的子进程退出的时候不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵尸进程。
  • SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出整个信号。
  • SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
  • SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数。

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
  printf("catch a signal: %d\n", signo);
}
int main()
{
  struct sigaction act, oact;

  act.sa_flags = 0;// &#x9009;&#x9879; &#x8BBE;&#x7F6E;&#x4E3A;0
  sigfillset(&act.sa_mask);
  act.sa_handler = handler;
  // &#x5BF9;2&#x53F7;&#x4FE1;&#x53F7;&#x4FEE;&#x6539;&#x5904;&#x7406;&#x52A8;&#x4F5C;
  sigaction(2, &act, &oact);
  while (1){
    raise(2);
    sleep(1);
  }
  return 0;
}
</signal.h></unistd.h></stdio.h>

运行结果如下:

Linux进程间通信(二)

代码示例:旧的信号处理函数

sa_flags标志为0代表使用的是旧的信号处理函数

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
    printf("catch a signal: %d\n", signo);
}
int main()
{
    int ret = -1;
    struct sigaction act;
    //&#x6807;&#x5FD7;&#x4E3A;0&#xFF0C;&#x4EE3;&#x8868;&#x4F7F;&#x7528;&#x7684;&#x662F;&#x65E7;&#x7684;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#x6307;&#x9488;
    act.sa_flags = 0;
    //&#x7ED9;&#x963B;&#x585E;&#x96C6;&#x521D;&#x59CB;&#x5316;
    sigfillset(&act.sa_mask);
    act.sa_handler = handler;
    // &#x4FE1;&#x53F7;&#x6CE8;&#x518C;
    ret =  sigaction(SIGINT, &act, NULL);
    if(ret == -1)
    {
      perror("sigaction");
      return 1;
    }
    printf("&#x6309;&#x4E0B;&#x4EFB;&#x610F;&#x952E;&#x9000;&#x51FA;.....\n");
    getchar();
    return 0;
}
</signal.h></unistd.h></stdio.h>

运行结果如下:

Linux进程间通信(二)

代码示例:新的信号处理函数

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo&#xFF0C;siginfo_t *info,void *context)
{
     printf("catch a signal: %d\n", signo);
}
int main()
  {
    int ret = -1;
    struct sigaction act;
    //&#x4F7F;&#x7528;&#x65B0;&#x7684;&#x4FE1;&#x53F7;&#x5904;&#x7406;&#x51FD;&#x6570;&#x6307;&#x9488;
    act.sa_flags = 0;
    //&#x7ED9;&#x963B;&#x585E;&#x96C6;&#x521D;&#x59CB;&#x5316;
    sigfillset(&act.sa_mask);
    act.sa_handler = handler;
    // &#x4FE1;&#x53F7;&#x6CE8;&#x518C;
    ret =  sigaction(SIGINT, &act, NULL);
    if(ret == -1)
    {
      perror("sigaction");
      return 1;
    }
    printf("&#x6309;&#x4E0B;&#x4EFB;&#x610F;&#x952E;&#x9000;&#x51FA;.....\n");
    getchar();
    return 0;
 }
</signal.h></unistd.h></stdio.h>

Linux进程间通信(二)

不可重入函数和可重入函数

先看下面一段代码:

#include <stdio.h>
#include <signal.h>

int a = 10;

void SelfAdd(int n)
{
    a = a + n;
    a = a + n;
}

void handler(int signo)
{
    SelfAdd(signo);
}
int main()
{
    signal(2, handler);
    SlefAdd(2);
    printf("%d\n", a);
    return 0;
}
</signal.h></stdio.h>

上面我写了一个比较简单的代码,我们慢慢分析,当我们在主函数中执行调用SelfAdd时,进入该函数,执行完函数中int a = a + n这句代码后,a变成了12,此时收到2号信号,发生中断

Linux进程间通信(二)

最后打印a结果是16,其实正常调用该函数的话,打印的应该是18。
像上面这样的因为重入导致结果错乱的函数就叫做不可重入函数。其中a是一个全局变量。如果一个函数值访问自己的局部变量或参数,那么这样的函数就叫做可重入函数。

说的通俗点,不可重入的意思是,如果你定义了一个全局变量,在函数1里面这个变量应该是10,但是有一个函数2改变了这个变量的值,此时本来函数1用的是10 ,你把他改变了,这就是不安全的,这就是不可重入函数。

思考一个问题:为什么两个不同的控制流程调用同一个函数,访问同一个局部变量或参数不会造成错乱?

在多线程中,每个线程虽然是资源共享,但是他们的栈却是独有的,所以说局部变量不会造成错乱。

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
  • 函数体内使用了静态的数据结构。

保证函数可重入性的方法:

  • 在写函数的时候尽量使用局部变量。(例如寄存器和栈中的变量)
  • 对于要使用的全局变量要加以保护(采取中断、信号量互斥的方法),这样构成的函数就一定是可重入函数。

使用信号避免僵尸进程

SIGCHLD信号

产生条件:1)子进程终止时

​ 2)子进程接收到SIGSTOP信号停止的时候

​ 3)子进程处于停止态,接收到SIGCONT后唤醒时

如何避免僵尸进程:

1)最简单的方法,父进程通过wait和waitpid等待子进程函数结束,但是,这会导致父进程挂起。

2)如果父进程要处理的事情很多,不能挂起,通过signal()函数人为处理信号SIGCHLD,只有在子进程退出自动调用制定好的回调函数,因为子进程结束后,父进程会收到信号SIGCHLD,可以在回调函数里面用wait或waitpid回收资源。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include<sys types.h>
#include<sys wait.h>
void sig_child(int signo)
{
    pid_t pid;
    //&#x5904;&#x7406;&#x50F5;&#x5C38;&#x8FDB;&#x7A0B;&#xFF0C;-1&#x4EE3;&#x8868;&#x7B49;&#x5F85;&#x4EFB;&#x610F;&#x4E00;&#x4E2A;&#x5B50;&#x8FDB;&#x7A0B;&#xFF0C;WNOHANG&#x4EE3;&#x8868;&#x4E0D;&#x963B;&#x585E;
    while((pid=waitpid(-1,NULL,WNOHANG))>0)
    {
        printf("&#x5B69;&#x5B50;&#x8FDB;&#x7A0B;&#x88AB;&#x6740;&#x6B7B; %d\n",pid);
    }
}
int main()
{
     pid_t pid;
     //&#x521B;&#x5EFA;&#x6355;&#x6349;&#x5B50;&#x8FDB;&#x7A0B;&#x9000;&#x51FA;&#x4FE1;&#x53F7;
     //&#x53EA;&#x8981;&#x5B50;&#x8FDB;&#x7A0B;&#x9000;&#x51FA;&#xFF0C;&#x89E6;&#x53D1;SIGSIGCHLD,&#x81EA;&#x52A8;&#x8C03;&#x7528;sig_child()
     signal(SIGCHLD,sig_child());
     //&#x521B;&#x5EFA;&#x8FDB;&#x7A0B;
     pid = fork();
     if(pid<0) { perror("fork"); exit(1); } else if(pid="=" 0) 子进程 printf("我是子进程,pid id :%d.我正在退出\n",getpid()); exit(0);>0)
     {
        //&#x7236;&#x8FDB;&#x7A0B;
        sleep(2);//&#x4FDD;&#x8BC1;&#x5B50;&#x8FDB;&#x7A0B;&#x5148;&#x8FD0;&#x884C;
        printf("&#x6211;&#x662F;&#x7236;&#x4EB2;&#xFF0C;&#x6211;&#x6B63;&#x5728;&#x9000;&#x51FA;\n");
        system("ps -ef|grep defunct");//&#x67E5;&#x770B;&#x6709;&#x6CA1;&#x6709;&#x50F5;&#x5C38;&#x8FDB;&#x7A0B;
     }
    return 0;
}
</0)></sys></sys></signal.h></unistd.h></stdio.h>

运行结果:

Linux进程间通信(二)

3)如果父进程不关心子进程时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核,自己对子进程的结束不感兴趣,父进程忽略此信号,那么子进程结束后,内核会回收,并不再给父进程发送信号。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include<sys types.h>
#include<sys wait.h>
int main()
{
     pid_t pid;
     //&#x5FFD;&#x7565;&#x5B50;&#x8FDB;&#x7A0B;&#x9000;&#x51FA;&#x4FE1;&#x53F7;&#x7684;&#x4FE1;&#x53F7;
     //&#x90A3;&#x4E48;&#x5B50;&#x8FDB;&#x7A0B;&#x7ED3;&#x675F;&#x4E4B;&#x540E;&#xFF0C;&#x5185;&#x6838;&#x4F1A;&#x56DE;&#x6536;&#xFF0C;&#x5E76;&#x4E0D;&#x518D;&#x7ED9;&#x7236;&#x8FDB;&#x7A0B;&#x53D1;&#x9001;&#x4FE1;&#x53F7;
     signal(SIGCHLD,SIG_IGN);
     //&#x521B;&#x5EFA;&#x8FDB;&#x7A0B;
     pid = fork();
     if(pid<0) { perror("fork"); exit(1); } else if(pid="=" 0) 子进程 printf("我是子进程,pid id :%d.我正在退出\n",getpid()); exit(0);>0)
     {
        //&#x7236;&#x8FDB;&#x7A0B;
        sleep(2);//&#x4FDD;&#x8BC1;&#x5B50;&#x8FDB;&#x7A0B;&#x5148;&#x8FD0;&#x884C;
        printf("&#x6211;&#x662F;&#x7236;&#x4EB2;&#xFF0C;&#x6211;&#x6B63;&#x5728;&#x9000;&#x51FA;\n");
        system("ps -ef|grep defunct");//&#x67E5;&#x770B;&#x6709;&#x6CA1;&#x6709;&#x50F5;&#x5C38;&#x8FDB;&#x7A0B;
     }
    return 0;
}
</0)></sys></sys></signal.h></unistd.h></stdio.h>

运行结果:

Linux进程间通信(二)

Original: https://www.cnblogs.com/yzsn12138/p/16830175.html
Author: 一只少年AAA
Title: Linux进程间通信(二)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/800063/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球