【问题标题】:Signal handler for SIGALRM does not work even if resetting in the handler即使在处理程序中重置,SIGALRM 的信号处理程序也不起作用
【发布时间】:2009-11-01 06:50:28
【问题描述】:

10.6节的示例代码,预期结果为:
经过几次迭代后,getpwnam 使用的静态结构将被破坏,程序将以 SIGSEGV 信号终止。

但是在我的平台,Fedora 11,gcc (GCC) 4.4.0,结果是

[浪子@Freedom apue]$ ./corrupt
在 sig_alarm 中

我只能看到来自sig_alarm 的输出一次,并且该程序似乎由于某种原因挂起,但它确实存在,并且仍在运行。
但是当我尝试使用 gdb 运行程序时,似乎还可以,我会定期看到来自sig_alarm 的输出。

在我的手册中,它说信号处理程序将在信号处理后设置为 SIG_DEF,系统不会阻塞信号。所以在我的信号处理程序开始时,我重置了信号处理程序。

也许我应该改用sigaction,但我只想知道正常运行和gdb运行之间差异的原因。

我们将不胜感激任何建议和帮助。

以下是我的代码:

#include "apue.h"
#include <pwd.h>

void sig_alarm(int signo);

int main()
{
  struct passwd *pwdptr;
  signal(SIGALRM, sig_alarm);

  alarm(1);
  for(;;) {
    if ((pwdptr = getpwnam("Zhijin")) == NULL)
      err_sys("getpwnam error");
    if (strcmp("Zhijin", pwdptr->pw_name) != 0) {
      printf("data corrupted, pw_name: %s\n", pwdptr->pw_name);
    }
  }
}

void sig_alarm(int signo)
{
  signal(SIGALRM, sig_alarm);
  struct passwd *rootptr;
  printf("in sig_alarm\n");

  if ((rootptr = getpwnam("root")) == NULL)
    err_sys("getpwnam error");
  alarm(1);
}

【问题讨论】:

    标签: c linux signals


    【解决方案1】:

    根据标准,您确实不能在信号处理程序中做太多事情。您可以在信号处理函数中保证能够在不引起未定义行为的情况下调用 signal,并将值分配给 sig_atomic_t 类型的 volatile 静态对象

    我在 Ubuntu Linux 上运行这个程序的前几次,看起来你在信号处理程序中对 alarm 的调用不起作用,所以 main 中的循环在第一次之后一直在运行警报。当我稍后尝试时,程序运行了几次信号处理程序,然后挂起。所有这些都与未定义的行为一致:程序有时会以各种或多或少有趣的方式失败。

    具有未定义行为的程序在调试器中以不同方式工作的情况并不少见。调试器是一个不同的环境,例如,您的程序和数据可能以不同的方式在内存中布局,因此错误可能以不同的方式表现出来,或者根本不表现出来。

    我通过添加一个变量让程序运行:

    volatile sig_atomic_t got_interrupt = 0;
    

    然后我将您的信号处理程序更改为这个非常简单的处理程序:

    void sig_alarm(int signo) {
        got_interrupt = 1;
    }
    

    然后我将实际工作插入到 main 中的无限循环中:

    if (got_interrupt) {
        got_interrupt = 0;
        signal(SIGALRM, sig_alarm);
        struct passwd *rootptr;
        printf("in sig_alarm\n");
    
        if ((rootptr = getpwnam("root")) == NULL)
            perror("getpwnam error");
        alarm(1);
    }
    

    我认为你提到的“apue”是“UNIX环境中的高级编程”这本书,我这里没有,所以我不知道这个例子的目的是不是为了表明你应该'不要乱用信号处理程序内部的东西,或者只是信号会通过中断程序的正常工作而导致问题。

    【讨论】:

    • 感谢您的回复。该示例的目的是说明如果我们从信号处理程序调用不可重入函数,结果是不可预测的。不可重入函数:a) 使用静态数据结构,b) 调用 malloc 或 free,c) 使用标准 I/O 库,因为库以不可重入的方式使用全局数据结构。我们应该保存并恢复errno。 getpwnam 函数使用静态结构,所以在主循环中,当信号处理程序调用相同的函数时,调用可能会发现内部数据指针已损坏,程序将崩溃。和
    • 按照您的建议,报警电话是可以的。但是无法出现示例的预期结果。这个例子是为了演示信号处理程序的错误方式,所以我只想知道它错误的原因。再次感谢。
    • 是的,这个例子表明你得到了未定义的行为。我假设意图是字符串 pw_name 有时应该被证明是错误的。但在我看来,它实际上展示的不止这些,即未定义的行为就是这样,未定义,如果您希望它做某件特定的事情,它可能会做一些完全不同的事情。只是庆幸它没有烧毁电脑,或者给你妈妈发粗鲁的电子邮件!
    【解决方案2】:

    根据规范,函数getpwnam 不可重入,不保证线程安全。由于您在两个不同的控制线程中访问结构(信号处理程序有效地在不同的线程上下文中运行),因此您遇到了这个问题。每当您有并发或并行执行时(如使用 pthread 或使用信号处理程序时),您必须确保不要修改共享状态(例如,'getpwnam' 拥有的结构),如果这样做,则适当的锁定/同步必须使用。

    此外,signal 函数已被弃用,取而代之的是 sigaction 函数。为了确保注册信号处理程序时的可移植行为,您应该始终使用sigaction 调用。

    使用 sigaction 函数,您可以使用 SA_RESETHAND 标志来重置默认处理程序。您还可以使用sigprocmask 函数来启用/禁用信号的传递,而无需修改其处理程序。

    【讨论】:

      【解决方案3】:
      #include <stdio.h>
      #include <stdlib.h>
      #include <signal.h>
      #include <unistd.h>
      
      void sigalrm_handler(int);
      
      
      
      
      int main()
      {   
          signal(SIGALRM, sigalrm_handler);
      
          alarm(3);   
      
      
          while(1)
          {
      
          }
      
          return 0;
      }
      
      
      void sigalrm_handler(int sign)
      {   
      
          printf("I am alive. Catch the sigalrm %d!\n",sign);
          alarm(3);
      }
      

      例如,我的代码在 main 中无所事事地运行,每 3 秒我的程序说我活着 x)

      我认为,如果您像我在处理函数警报中调用值 3 那样做,问题就解决了 :)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-06
        • 1970-01-01
        • 2017-08-05
        • 2018-02-04
        • 1970-01-01
        • 1970-01-01
        • 2012-03-21
        相关资源
        最近更新 更多