为什么子进程在接收到信号后不能继续运行?

3
以下是我的代码。父进程fork出子进程。子进程暂停等待父进程发送信号给它,然后继续运行。我的问题是为什么子进程在父进程发送信号给它后没有继续运行。我错过或误解了什么吗?
#include<stdio.h>
#include<unistd.h>
#include<signal.h>


void 
sigusr1( int pidno )
{
  printf("Catched\n");
}

int 
main()
{
  pid_t pid;

  signal( SIGUSR1, sigusr1 );
  if( (pid = fork()) == 0 ){
    pause();
  printf("Child\n"); 
  }

  kill( pid , SIGUSR1 ); //parent sends signal to child 
  pause();
}
2个回答

3
以下是父进程的操作:
  1. 创建一个子进程。
  2. 向子进程发送SIGUSR1信号。
  3. 等待一个信号。
以下是子进程的操作:
  1. 等待一个信号。
  2. 打印Child
  3. 调用kill(0, SIGUSR1)(0是子进程中pid的值)。使用进程ID为0调用kill会将信号发送给调用kill的进程所在的进程组中的每个进程。
  4. 等待一个信号。
您的程序有多种可能的行为,这取决于父进程和子进程系统调用执行的顺序。根据您的操作系统版本、各种内核参数的微调、系统负载情况以及随机事件的影响,如果您多次运行程序或在调试器下运行程序,则可能观察到不同的行为。
如果父进程启动比子进程快,您可能会看到以下内容:
  1. 父进程向子进程发送SIGUSR1信号。
  2. 子进程接收到SIGUSR1信号并打印“Catched”。
  3. 子进程调用“pause”函数。
  4. 父进程调用“pause”函数。

按照这个执行顺序,父进程和子进程都会无限等待(这是死锁)。

如果子进程比父进程启动得更快,你可能会看到以下结果:

  1. 子进程调用“pause”函数。
  2. 父进程向子进程发送SIGUSR1信号。
  3. 父进程调用“pause”函数。
  4. 子进程被解除阻塞并打印“Catched”。
  5. 子进程打印“Child”。
  6. 子进程向进程组发送SIGUSR1信号。
  7. 子进程打印“Catched”。
  8. 子进程调用“pause”函数。
  9. 父进程被解除阻塞并打印“Catched”。
  10. 父进程退出。
我认为孩子没有退出的方法:它调用pause两次,虽然它可以接收多达两个信号,但其中一个是从它自己发送的(来自kill(0,SIGUSR1)),而这个信号是同步发送的,不是在执行pause期间发送的。
这个程序可能不是你想写的,但由于你没有描述期望的行为,所以无法确定你想写什么。我注意到你没有遵循fork程序的通常结构。
pid = fork();
if (pid < 0) {
    /*error handling*/
} else if (pid == 0) {
    /*child code*/
    exit(...); /*Usually, what follows the if is specific to the parent.*/
}

1
“kill”(即“发送信号”)被意外地调用了两次(一次在父进程中,一次在子进程中),这正是我一开始想说的。更糟糕的是,子进程(再次无意中)发送了一个“kill 0”。这是主要问题。第二个问题可能是行缓冲(影响显示的顺序和时间线)。 - paulsm4
当输出到终端时,stdout是行缓冲或无缓冲的;这在POSIX中有明确规定,在C语言中更为普遍(例如参见N1256 §7.19.3.7)。 - Gilles 'SO- stop being evil'
您的报价:“stdout是行缓冲或无缓冲的”。默认情况下,在几乎所有实现(X 终端,putty窗口,文本模式帧缓冲器等)中,对于几乎所有的*nix(包括Linux),stdio默认为“缓冲”。那么C99标准与“终端I/O”有什么关系? - paulsm4
@14moose 当输出到终端时,Stdio默认使用缓冲。在所有C实现中都是如此。这与C标准有关(我引用了C99,但C89有类似的语言),因为这是一个C程序(而POSIX通过引用包含了C标准)。 - Gilles 'SO- stop being evil'

1

试试这个:

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


void
sigusr1( int pidno )
{
  fprintf(stderr, "Caught\n");
}

int
main()
{
  pid_t pid;

  signal( SIGINT, sigusr1 );
  if( (pid = fork()) == 0 ){
    pause();
    fprintf(stderr, "Child\n");
  }
  else
  {
    fprintf(stderr, "Parent\n");
    kill( pid , SIGINT ); //parent sends signal to child
  }
  pause();
  return 0;
}
  1. printf缓冲输入:也许正在调用它,但在您期望的时候没有显示。一个解决方法是使用fflush()。更好的解决方法是使用fprintf(stderr),它首先不进行缓冲。

  2. 您在父进程和子进程中都调用了kill()。我添加了一个else来消除这个问题。

  3. 以下是示例输出:

gcc -Wall -g -o z z.c

./z
Parent
Caught
Child

谢谢。我复制了你的代码,但似乎在我的Mac上无法运行。 - Janus.Le
1
我认为原因是在暂停运行之前发送了信号。 - Janus.Le
#1 是错误的:如果程序在终端上打印,它将使用行缓冲。#2 是正确的,但我认为这不是整个问题的全部。 - Gilles 'SO- stop being evil'
@Gilles - 不,打印是有缓冲的。通常情况下缓冲是好的,但在多进程或多线程环境下可能会产生意外结果。而且,是的,原始代码中kill所在的位置就是主要问题所在。 - paulsm4
@Janus Le - "signal()"设置信号处理程序。"kill()"向进程发送信号。在“fork()”之前和随后的“pause()”之前调用signal是很好的。 - paulsm4
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接