【问题标题】:Providing/passing argument to signal handler向信号处理程序提供/传递参数
【发布时间】:2011-10-21 15:55:50
【问题描述】:

我可以向信号处理程序提供/传递任何参数吗?

/* Signal handling */
struct sigaction act;
act.sa_handler = signal_handler;
/* some more settings */

现在,处理程序看起来像这样:

void signal_handler(int signo) {
    /* some code */
}

如果我想做一些特别的事情,即删除临时文件,我可以将这些文件作为参数提供给这个处理程序吗?

编辑 0:感谢您的回答。我们通常避免/不鼓励使用全局变量。在这种情况下,如果你有一个庞大的程序,不同的地方可能会出错,你可能需要做很多清理工作。为什么要这样设计 API?

【问题讨论】:

    标签: c signals signal-handling


    【解决方案1】:

    您不能将自己的数据作为参数传递给信号处理程序。相反,您必须将参数存储在全局变量中。 (如果您在安装信号处理程序后需要更改这些数据,请务必非常小心)。

    对编辑 0 的回应: 历史原因。信号是一种非常古老且非常低级的设计。基本上,您只是为内核提供了某个机器代码的单个地址,并在发生此类情况时要求它转到该特定地址。我们回到了“便携式汇编器”的思维模式,内核提供了简洁的基线服务,无论用户进程可以合理地期望自己做什么,它都必须自己做。

    另外,针对全局变量的常用论点在这里并不适用。信号处理程序本身是一个全局设置,因此不可能有几个不同的用户指定参数集。 (嗯,实际上它并不完全是全局的,而只是线程全局的。但是线程 API 将包含一些用于线程局部存储的机制,这正是您在这种情况下所需要的)。

    【讨论】:

    • 信号处理程序不是每个线程的。更改信号的处理程序适用于所有线程。
    • 对。但是希望信号处理程序对它正在处理的线程做出反应仍然是有意义的,这就是 TLS 可以实现的。
    • 实际上我不清楚从信号处理程序访问 TLS 是否有效。对于 glibc/NPTL,它几乎可以肯定一般来说是无效的,因为可以在创建线程之后立即传递信号,但在将控制权传递给 start 函数之前,而线程的 TLS 副本仍在初始化。如果你小心地在这个时候屏蔽信号,可能没问题,但它仍然是一种可疑的做法......
    • 顺便说一句,即使没有 TLS,也有一种方法可以以异步信号安全的方式识别信号处理程序从哪个线程运行:&errno 对于每个线程都是唯一的!但是,我相信这在 glibc/NPTL 中可能不安全,原因与我刚才描述的相同。如果不是,我很确定这是一个一致性错误,但它可能是一个非常罕见的种族,不会被发现......
    • @R.. 信号处理程序可以基于每个线程。 POSIX 提供了可用于向同一进程中的各个线程发送信号的功能。
    【解决方案2】:

    将文件名存储在全局变量中,然后从处理程序访问它。信号处理回调只会传递一个参数:导致问题的实际信号的 ID(例如 SIGINT、SIGTSTP)

    编辑 0:“不允许向处理程序提供参数必须有一个坚如磐石的理由。”

    【讨论】:

      【解决方案3】:

      信号处理程序注册已经是一个全局状态,等效于全局变量。因此,使用全局变量向它传递参数并没有什么大不了的。但是,无论如何从信号处理程序中执行任何操作都是一个巨大的错误(几乎可以肯定未定义的行为,除非您是专家!)。如果您只是阻止信号并从主程序循环中轮询它们,则可以避免所有这些问题。

      【讨论】:

      • 除非您是信号专家,否则从信号处理程序中执行任何操作都是非常危险的。相反,您可以只使用信号处理程序来保存信号发生的标志,并从信号处理程序之外的各个点检查它,或者您可以阻止信号并使用sigpendingsigwait 来轮询信号。跨度>
      • 哇,这对我来说是全新的。我已经看到代码在主程序终止之前进行清理,例如临时文件删除或子进程清理。此外,当您说检查标志时,如果您获得 SIGTERM,这将如何工作?如果您不在处理程序中处理它,您的程序不会立即终止吗?为什么dangerous 在处理程序内部做事?
      • 信号处理程序可以随时中断程序。这意味着,除非您注意确保不会发生这种情况,否则它可能会以不一致的状态与各种想要使用的数据对象一起运行。例如,FILE 内的缓冲区指针可能仅部分更新,或者打开的 FILEs 的链表可能有一个中途插入或中途删除的项目与其部分链接。或者内部的malloc 数据结构可能引用了中途释放的内存......如果信号处理程序调用依赖于该状态一致的函数,将会发生非常糟糕的事情!
      • 顺便说一句,使用信号处理程序的一种经典的“安全”方式是为自己打开一个管道,并将文件描述符存储在全局的某个地方。信号处理程序可以写入管道(这是安全的),您可以在主循环中等待来自管道的输入 select/poll 以及您正在等待输入的其他文件描述符。
      • @R.. Linux 上有 signalfd() 正是这样做的。还是个好主意。很少有人考虑使用管道在进程进行通信。使线程之间的通信更加容易和明确。
      【解决方案4】:

      您可以使用作为类方法的信号处理程序。然后该处理程序可以访问该类的成员数据。我不完全确定 Python 在 C signal() 调用的幕后做了什么,但它必须重新定义数据范围?

      我很惊讶这有效,但确实有效。运行它,然后从另一个终端终止该进程。

      import os, signal, time
      
      class someclass:
          def __init__(self):
              self.myvalue = "something initialized not globally defined"
              signal.signal(signal.SIGTERM, self.myHandler)
          def myHandler(self, s, f):
              # WTF u can do this?
              print "HEY I CAUGHT IT, AND CHECK THIS OUT", self.myvalue
      
      
      print "Making an object"
      a = someclass()
      
      while 1:
          print "sleeping.  Kill me now."
          time.sleep(60)
      

      【讨论】:

      • 问题是关于 C 的……尽管 Python 很可能会在某个地方记住信号以便稍后尽快处理它(在信号处理程序内部做事很危险)。可能使用全局变量。
      【解决方案5】:

      当然。您可以使用 sigqueue() 而不是通常的 kill() 将整数和指针传递给信号处理程序。

      http://man7.org/linux/man-pages/man2/sigqueue.2.html

      【讨论】:

        【解决方案6】:

        这是一个非常古老的问题,但我想我可以向您展示一个可以回答您的问题的好技巧。 不需要使用 sigqueue 什么的。

        我也不喜欢使用全局变量,所以我必须找到一个聪明的方法,在我的例子中,发送一个 void ptr(你可以稍后将它转换为任何适合你需要的东西)。

        其实你可以这样做:

        signal(SIGWHATEVER, (void (*)(int))sighandler); // Yes it works ! Even with -Wall -Wextra -Werror using gcc
        

        那么您的 sighandler 将如下所示:

        int sighandler(const int signal, void *ptr) // Actually void can be replaced with anything you want , MAGIC !
        

        你可能会问:那么如何获取 *ptr 呢?

        方法如下: 初始化时

        signal(SIGWHATEVER, (void (*)(int))sighandler)
        sighandler(FAKE_SIGNAL, your_ptr);
        

        在你的 sighandler 函数中

        int sighandler(const int signal, void *ptr)
        {
          static my_struct saved = NULL;
        
          if (saved == NULL)
             saved = ptr;
          if (signal == SIGNALWHATEVER)
             // DO YOUR STUFF OR FREE YOUR PTR
           return (0);
        }
        

        【讨论】:

        • 不错的技巧,但不幸的是它没有被标准定义。见 6.3.2.3(8):if a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
        【解决方案7】:

        我认为你最好在 sa_flags 中使用 SA_SIGINFO 以便处理程序得到 void signal_handler(int sig, siginfo_t *info, void *secret) 在 siginfo_t 你可以提供你的参数。 Ty:HAPPY代码

        【讨论】:

        • 您在 sigval 联合中设置一些指针或其他数据,然后将其传递给 sigqueue() 以将实时(用户定义)信号排入队列,然后您会返回您通过siginfo_t 参数中的sigval 联合在信号处理程序中指定的任何内容。但是,您不能为非实时信号指定用户定义的数据。而且,前面已经提到过使用sigqueue()...
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-04
        • 2022-01-05
        相关资源
        最近更新 更多