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

时间:2014-02-11 16:35:14

标签: c linux signals

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函数(显示“接收到的信号”)。但我不确定,请随时纠正或提供更多细节。谢谢你的帮助!

3 个答案:

答案 0 :(得分:6)

您的代码中存在一些错误:

  1. 避免在信号处理程序中调用printf( )函数。 SIGNAL(7)手册提供了一个授权函数列表,它们在信号处理程序中是安全的。阅读:

      

    Async-signal-safe functions

         

    自处理以来,信号处理函数必须非常小心      其他地方可能会在执行中的某个任意点中断      该计划。 POSIX具有“安全功能”的概念。 如果是      信号中断执行不安全的功能 和处理程序      调用一个不安全的函数,然后程序的行为是      未定义。

  2. 使用main()int的返回类型;阅读"What should main() return in C?"

  3. x应为pid_t。 (Process Identification)。

  4. 现在让我们假设您的程序编译并运行(在处理程序执行时不会被任何其他信号中断): 我只是缩进你的代码并在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()函数中,您为f()信号注册了SIGUSR1函数,之后调用fork()来创建新进程。在运行时,fork()函数返回子进程在父进程的 parallel 中开始执行。
    正如我可以看到您的代码,我认为您理解子进程是父进程的副本,除了变量的值可能与点fork()返回不同,因此x在子进程中是不同的父进程。我们可以使用fork的返回值来判断程序是在父进程还是子进程中运行。但请注意,它不是父级,而是接收信号SIGUSR1的子进程。对于任何流程,自我进程ID的值始终为0。您检查作为新创建的子进程的pid的返回值x = fork(),在x的子进程值为0且在父x != 0中。因此,信号从父进程发送到子进程。

    您的意见:

      

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

    我的印象是你不认为两个进程同时执行,并且“它可能在fork()创建子进程后很快就会发生,子进程开始执行并在父进程之前立即终止向孩子发送信号(或儿童过程可以接收信号)“。在这种情况下,函数f()永远不会有机会执行,而信号处理程序中的printf永远不会打印。

    但是我上面描述的可能性非常低,因为fork需要时间来创建一个新进程。即使您一次又一次地执行代码,大多数时候从父进程发送的信号也会执行signal-handler。

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

    代码是 xc :正确的方法是设置一个标志,指示信号处理程序已执行,然后在信号处理程序外的标志值的基础上调用printf函数,如我在答案中所述:{ {3}} How to avoid using printf in a signal handler?在其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调度)它向父进程发回信号并在父进程之前有机会执行子接收信号并打印消息“。但有时在儿童印刷品印刷之前也会发生这种情况;父接收并打印消息(即使用箭头标记)。

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

    一些好的资源学习信号(1)GNU C库:answer (2)CERT C编码标准Signal Handling

答案 1 :(得分:0)

一个问题是子进程没有做任何事情,但会立即从main函数返回,可能在父进程发送信号之前。

您可能想要致电,例如孩子pause

答案 2 :(得分:0)

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

#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()不安全?