【问题标题】:Problems with signal handling信号处理问题
【发布时间】:2012-01-01 14:51:35
【问题描述】:

在 fork 调用之后,我有一个父亲必须将 sigusr1 或 sigusr2(基于“cod”变量的值)发送给他的孩子。孩子必须在收到 sigusr1 或 sigusr2 之前安装适当的处理程序。为此,我暂停了父亲,等待孩子向他发出信号,告诉他他已完成处理程序的安装。父亲由 sigusr1 发出信号,并且该信号的处理程序在 fork 调用之前安装。但是,似乎父亲无法从暂停中返回,这让我认为他实际上从未调用过 sigusr1 处理程序。

[...]

    typedef enum{FALSE, TRUE} boolean;

    boolean sigusr1setted = FALSE;
    boolean sigusr2setted = FALSE;


    void
    sigusr1_handler0(int signo){
             return;
    }

    void
    sigusr1_handler(int signo){
            sigusr1setted = TRUE;
    }

    void
    sigusr2_handler(int signo){
            sigusr2setted = TRUE;
    }  

    int main(int argc, char *argv[]){
         [...]

         if(signal(SIGUSR1, sigusr1_handler0) == SIG_ERR){
            perror("signal 0 error");
            exit(EXIT_FAILURE); 
         }

         pid = fork();
                    if (pid == 0){
                        if(signal(SIGUSR1, sigusr1_handler) == SIG_ERR){
                            perror("signal 1 error");
                            exit(EXIT_FAILURE);
                        }

                        if(signal(SIGUSR2, sigusr2_handler) == SIG_ERR){
                            perror("signal 2 error");
                            exit(EXIT_FAILURE);         
                        }

                        kill(SIGUSR1, getppid()); // wake up parent by signaling him with sigusr1

                        // Wait for the parent to send the signals...
                        pause();

                        if(sigusr1setted){
                            if(execl("Prog1", "Prog1", (char*)0) < 0){
                                perror("exec P1 error");
                                exit(EXIT_FAILURE);
                            }
                        }

                        if(sigusr2setted){
                            if(execl("Prog2", "Prog2", (char*)0) < 0){
                                perror("exec P2 error");
                                exit(EXIT_FAILURE);
                            }
                        }

                        // Should'nt reach this point : something went wrong...
                        exit(EXIT_FAILURE);

                    }else if (pid > 0){
                        // The father must wake only after the child has done with the handlers installation

                        pause(); 

                        // Never reaches this point ... 
                        if (cod == 1)
                            kill(SIGUSR1, pid);
                        else 
                            kill(SIGUSR2, pid);

                        // Wait for the child to complete..
                        if(wait(NULL) == -1){
                            perror("wait 2 error"); 
                            exit(EXIT_FAILURE);             
                        }

                             [...]

                    }else{
                        perror("fork 2 error");
                        exit(EXIT_FAILURE);
                    }
         [...]

         exit(EXIT_SUCCESS);
    }

【问题讨论】:

  • 我怀疑您已经产生了与您预期相反的竞争条件。子进程父进程到达pause之前向父进程发送了SIGUSR1。
  • 多放一些 printf()(并确保你刷新),这样你就可以看到孩子走了多远,以及是否调用了父母的信号处理程序。这会告诉你更多。
  • @OliCharlesworth:没看到……谢谢。
  • 信号处理程序和非处理程序代码(您的booleanobjects)之间共享的对象必须具有volatile sig_atomic_t 类型,否则代码未定义。
  • 顺便说一下,没有必要测试任何exec*() 系列函数的返回值。如果系统调用返回,则失败。

标签: c unix posix signals


【解决方案1】:

从 cmets 收集一个合理的答案 - 所以这从一开始就是 Community Wiki。 (如果 Oli 提供了答案,请投票而不是这个!)

Oli Charlesworth 给出了可能是问题的核心:

  • 我怀疑您产生的竞争条件与您预期的相反。在父级到达pause() 之前,子级将 SIGUSR1 发送给父级。

ouah 准确记录:

  • 信号处理程序和非处理程序代码(您的布尔对象)之间共享的对象必须具有volatile sig_atomic_t 类型,否则代码未定义。

也就是说,对于可以在信号处理程序中完成的操作,POSIX 比标准 C 允许更宽松一些。我们可能还会注意到 C99 提供了 &lt;stdbool.h&gt; 来定义 bool 类型。

原发帖人评论:

我不知道如何确保父母先进入pause() 呼叫而不在孩子中使用sleep()(这不能保证什么)。有什么想法吗?

建议:使用usleep()(微秒睡眠,或睡眠微秒),还是nanosleep()(纳秒睡眠)?

或者使用不同的同步机制,比如:

  1. 父进程创建FIFO;
  2. fork();
  3. child 打开 FIFO 进行写入(阻塞直到有读取器);
  4. 父级打开 FIFO 进行读取(阻塞直到有写入器);
  5. 当因为open() 调用返回而解除阻塞时,两个进程都会简单地关闭 FIFO;
  6. 父级删除 FIFO。

注意两个进程之间没有通过FIFO进行数据通信;代码只是依靠内核来阻塞进程,直到有一个读取器和一个写入器,所以两个进程都准备好了。

另一种可能性是父进程可以尝试if (siguser1setted == FALSE) pause(); 来减少竞争条件的窗口。但是,它只会缩小窗口;它不保证不会发生竞态条件。也就是说,墨菲定律适用,信号可能在测试完成和pause() 执行之间到达。

所有这些都表明信号不是一个很好的 IPC 机制。它们可用于 IPC,但实际上很少用于同步。

顺便说一句,没有必要测试任何exec*() 系列函数的返回值。如果系统调用返回,则失败。

提问者又问了一遍:

使用进程间共享的 POSIX 信号量不是更好吗?

信号量肯定是同步这两个进程的另一种有效机制。因为我当然必须查看信号量的手册页,而我可以不看就记住如何使用 FIFO,我不确定我是否真的会使用它们,但是创建和删除 FIFO 有其自身的一系列问题因此尚不清楚它是否以任何方式“更好”(或“更糟”);只是不同。它是mkfifo()open()close()unlink() 用于 FIFO 与 sem_open()(或 sem_init())、sem_post()sem_wait()sem_close(),可能还有 @9876543(或 @98876543) @) 用于信号量。您可能想考虑向atexit() 注册一个“FIFO 删除”或“信号量清理”功能,以确保在尽可能多的情况下销毁 FIFO 或信号量。但是,对于测试程序来说,这可能是 OTT。

【讨论】:

    猜你喜欢
    • 2011-04-07
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-07
    • 1970-01-01
    相关资源
    最近更新 更多