繁体   English   中英

在此信号处理程序中会发生什么?

[英]What happens during this signal handling program?

void main ( )
{   int x;
    signal (SIGUSR1, f);
    x= fork ( );
    if (x == -1) exit (1);
    if (x != 0) 
    {   kill (x, SIGUSR1) ;
        sleep (2);
        exit (0);
    }
}
void f ( )
{
    printf ("signal received");
    exit (0);
}

我认为上面的程序要求系统在父进程接收到SIGUSR1信号时启动f函数(显示“信号已接收”)。 但我不确定,请随时进行更正或提供更多详细信息。 感谢您的帮助!

您的代码中有一些错误:

  1. 避免在信号处理程序中调用printf( )函数。 SIGNAL(7)手册提供了授权函数的列表,称这些函数在信号处理程序内部是安全的。 读:

    异步信号安全功能

    信号处理程序的功能必须非常小心,因为在程序执行过程中的任意位置可能会中断其他地方的处理。 POSIX具有“安全功能”的概念。 如果信号中断了不安全函数的执行 ,并且处理程序调用了不安全函数,则程序的行为是不确定的。

  2. 使用main()的返回类型int ; 阅读main()应该在C中返回什么?”

  3. x应该是pid_t 过程标识 )。

现在,假设您的程序可以编译并运行(在执行处理程序时不会被任何其他信号打断):我只是缩进代码,并在main之前将f()函数定义转移,因为缺少函数声明,还添加了一些您应该阅读的注释:

#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void f( )
{
    printf ("signal received\n");
    exit (0);//if child receives signal then child exits from here
}            // ******* mostly happens
int main ( )
{   int x;
    signal (SIGUSR1, f);// First: handler registered
    x = fork ( );       // Second: Child created, now child will execute in
    if (x == -1)        // parallel with parent 
      exit (1);
    if (x != 0) {// parent           
        kill(x, SIGUSR1) ; // Third: sends signal to child
        sleep(2);          // Forth: parent sleep
    }
    return 0; // parent always exits  
    // Child can exits from here **** 
    // if signal sent from parent delayed
}

main()函数中,为SIGUSR1信号注册f()函数,然后调用fork()创建一个新进程。 在运行时, fork()函数返回一个子进程,并开始与父进程并行执行。
正如我看到的代码所示,我认为您理解子进程是父进程的副本,只是变量的值可以与fork()返回的点不同,因此x在子进程和父进程中是不同的。 我们可以使用fork的返回值来判断程序是在父进程中运行还是在子进程中运行。 但是请注意,接收信号SIGUSR1不是父进程,而是子进程。 对于任何进程,自身进程ID的值始终为0 您检查返回值x = fork()是新创建的子进程的pid,在x子进程值是0且在父x != 0 因此,信号从父进程发送到子进程。

你的评论:

我认为上面的程序要求系统在父进程接收到SIGUSR1信号时启动f( )函数(显示"signal received" )。

我给您的印象是您不认为两个进程同时执行,并且“有可能发生在fork()创建子进程之后不久,子进程开始执行,并在父进程可以向子进程发送信号之前立即终止(或子进程可以接收信号)”。 在那种情况下,函数f()将永远不会执行,并且信号处理程序中的printf永远不会打印。

但是我上面所描述的可能性很小,因为fork需要花费时间来创建一个新的过程。 即使您一次又一次地执行代码,大多数时候从父进程发送的信号也会执行信号处理程序。

编写此代码的正确方法是什么?

代码是xc :正确的方法是设置一个标志,该标志指示信号处理程序已执行,然后根据信号处理程序外部的标志值调用printf函数,正如我在我的答案中所述: 如何避免在信号处理程序中使用printf? 乔纳森·莱弗勒Jonathan Leffler)回答中也解释了背后的原因。

#define _POSIX_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
volatile sig_atomic_t flag = 0; //initially flag == 0
void f(){
    flag = 1; // flag is one if handler executes
}
int main(void){   
  pid_t x;
  (void) signal (SIGUSR1, f);
  x = fork();
  if(x == -1) 
    exit(EXIT_FAILURE);
  if (x != 0){// parent 
    kill(x, SIGUSR1);
    sleep(1);
  }
  if(flag)//print only if signal caught and flag == 1
    printf("%s signal received \n", x == 0 ? "Child:" : "Parent:");
  return EXIT_SUCCESS;
}

现在编译并执行:

@:~$ gcc -Wall  -pedantic -std=c99  x.c  -o x
@:~$ ./x
Child: signal received 
@:~$ 

请注意,子进程会打印,因为父进程会向子进程发送信号(但父进程不会打印,因为父进程中没有信号捕获)。 因此,上述代码的行为仍然与您获取代码时的行为类似。 下面,我添加了另一个示例,其中我试图证明“进程的并行执行在不同的执行实例上导致不同的结果”(请阅读注释)。

// include header files...
volatile sig_atomic_t flag = 0;
void f(){
    flag = 1;
}
int main(void){   
  pid_t x;
  (void) signal (SIGUSR1, f);
  (void) signal (SIGUSR2, f); // added one more signal
  x= fork ( );
  if(x == -1) 
    exit(EXIT_FAILURE);
  if (x != 0){// parent 
    kill(x, SIGUSR1);
    while(!flag); // in loop until flag == 0
  }
  if (x == 0){//child 
    kill(getppid(), SIGUSR2); // send signal to parent 
    while(!flag); // in loop until flag == 0
  }//  ^^^^ loop terminates just after signal-handler sets `flag` 
  if(flag)
    printf("%s signal received \n", x == 0 ? "Child:" : "Parent:"); 
  return EXIT_SUCCESS;
}

在上面的代码中,在父进程和子进程中都注册了两个信号。 父进程不会休眠,而是在一个while循环中忙碌,直到信号置位标志为止。 类似地,子进程具有一个循环,该循环在信号处理程序中的标志变为1时中断。 现在编译此代码并重复运行。 我经常尝试在系统中进行以下跟踪输出。

@:~$ gcc -Wall  -pedantic -std=c99  x.c  -o x
@:~$ ./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Child: signal received  
Parent: signal received 
@:~$ ./x
Parent: signal received   // <------
@:~$ Child: signal received 
./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Parent: signal received   // <------
@:~$ Child: signal received 

@:~$ 

注意输出,一种情况是:“直到子进程创建了父进程发送的信号并进入while循环,当子进程有机会执行(取决于CPU调度)时,它会向父进程发回一个信号,并且在父进程获得机会之前执行子程序接收信号并打印消息”。 但是有时也会在子printf打印之前发生; 家长收到并打印消息(即我用箭头标记)。

在上一个示例中,我试图显示子进程与父进程并行执行,如果不应用并发控制机制,则输出可能会有所不同。

一些学习信号的好资源(1)GNU C库: 信号处理 (2)CERT C编码标准11.信号(SIG)

一个问题是子进程不执行任何操作,但是会立即从main函数返回,可能在父进程发送信号之前。

您可能想打电话给孩子pause

为了便于练习,这里是将编译并运行的原始代码的更正版本。

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <bits/signum.h>
void f ( );

int main ( )
{   int x;
    signal (SIGUSR1, f);
    x= fork ( );
    if (x == -1) exit (1);
    if (x != 0)
{   kill (x, SIGUSR1) ;
        sleep (2);
        exit (0);
    }
}
void f ( )
{
    printf ("signal received\n");
    exit (0);
}

这恰好符合程序建议的原始问题。 尝试一下,看看如果您不相信我会发生什么。

顺便说一句:我对C的经验不是很丰富。有很多评论断言在子进程中使用printf()是不安全的。 为什么?? 子进程是父进程的副本,包括虚拟地址空间。 那么为什么printf()不安全?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM