Calling Qt Functions From Unix Signal Handlers

你无法从 Unix 信号处理程序调用 Qt 函数. 标准 POSIX 规则适用: 你只能从信号处理程序调用异步信号安全函数. 有关可以从 Unix 信号处理程序调用的函数的完整列表, 参见 Signal Actions.

但不要绝望, 有一种方法可以在 Qt 中使用 Unix 信号处理程序. 策略是让你的 Unix 信号处理程序执行一些操作, 最终导致发出 Qt 信号, 然后你只需从 Unix 信号处理程序返回即可. 回到 Qt 程序中, Qt 信号被发出, 然后由 Qt 槽函数接收, 你可以安全地执行 Unix 信号处理程序中不允许执行的任何 Qt 操作.

实现这一点的一个简单方法是在类中为要处理的每个 Unix 信号声明一个套接字对. 套接字对被声明为静态数据成员. 你还创建一个 QSocketNotifier 监视每个套接字对的读取端, 将你的 Unix 信号处理程序声明为静态类方法, 并声明与每个 Unix 信号处理程序相对应的槽函数. 在此示例中, 我们打算处理 SIGHUP 和 SIGTERM 信号. 注意: 在仔细阅读以下代码片段之前, 你应该阅读 socketpair(2) 和 sigaction(2) 手册页.


  class MyDaemon : public QObject
  {
      Q_OBJECT

    public:
      MyDaemon(QObject *parent = 0);
      ~MyDaemon();

      // Unix signal handlers.
      static void hupSignalHandler(int unused);
      static void termSignalHandler(int unused);

    public slots:
      // Qt signal handlers.
      void handleSigHup();
      void handleSigTerm();

    private:
      static int sighupFd[2];
      static int sigtermFd[2];

      QSocketNotifier *snHup;
      QSocketNotifier *snTerm;
  };

在 MyDaemon 的构造函数中, 使用 socketpair(2) 函数初始化每个文件描述符对, 然后创建 QSocketNotifier 监视每个文件描述符对的读取. 每个 QSocketNotifier 的 activated() 信号连接到适当的槽函数, 这个类有效地将 Unix 信号转换为 QSocketNotifier::activated() 信号.


  MyDaemon::MyDaemon(QObject *parent)
          : QObject(parent)
  {
      if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sighupFd))
         qFatal("Couldn't create HUP socketpair");

      if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermFd))
         qFatal("Couldn't create TERM socketpair");
      snHup = new QSocketNotifier(sighupFd[1], QSocketNotifier::Read, this);
      connect(snHup, SIGNAL(activated(int)), this, SLOT(handleSigHup()));
      snTerm = new QSocketNotifier(sigtermFd[1], QSocketNotifier::Read, this);
      connect(snTerm, SIGNAL(activated(int)), this, SLOT(handleSigTerm()));

      ...
  }

在你的代码其他开始位置, 使用 sigaction(2) 初始化你的 Unix 信号处理程序.


  static int setup_unix_signal_handlers()
  {
      struct sigaction hup, term;

      hup.sa_handler = MyDaemon::hupSignalHandler;
      sigemptyset(&hup.sa_mask);
      hup.sa_flags = 0;
      hup.sa_flags |= SA_RESTART;

      if (sigaction(SIGHUP, &hup, 0))
         return 1;

      term.sa_handler = MyDaemon::termSignalHandler;
      sigemptyset(&term.sa_mask);
      term.sa_flags |= SA_RESTART;

      if (sigaction(SIGTERM, &term, 0))
         return 2;

      return 0;
  }

在你的 Unix 信号处理程序, 使用 write 函数向套接字对写入一字节, 并返回. 这将造成对应的 QSocketNotifier 发出 activated() 信号, 然后允许相应的 Qt 槽函数.


  void MyDaemon::hupSignalHandler(int)
  {
      char a = 1;
      ::write(sighupFd[0], &a, sizeof(a));
  }

  void MyDaemon::termSignalHandler(int)
  {
      char a = 1;
      ::write(sigtermFd[0], &a, sizeof(a));
  }

在连接到 QSocketNotifier::activated() 信号的槽函数中, 你使用 read 读取这个字节. 现在你可以安全的使用 Qt 的信号, 并且执行 Unix 信号处理程序中不允许执行的所有 Qt 操作.


  void MyDaemon::handleSigTerm()
  {
      snTerm->setEnabled(false);
      char tmp;
      ::read(sigtermFd[1], &tmp, sizeof(tmp));

      // do Qt stuff

      snTerm->setEnabled(true);
  }

  void MyDaemon::handleSigHup()
  {
      snHup->setEnabled(false);
      char tmp;
      ::read(sighupFd[1], &tmp, sizeof(tmp));

      // do Qt stuff

      snHup->setEnabled(true);
  }