【问题标题】:Simple Linux Signal Handling简单的 Linux 信号处理
【发布时间】:2013-07-30 07:19:41
【问题描述】:

我有一个程序可以创建许多线程并运行直到嵌入式计算机关闭电源,或者用户使用 killctrlc 来终止过程。

这是一些代码以及 main() 的外观。

static int terminate = 0;  // does this need to be volatile?

static void sighandler(int signum) { terminate = 1; }

int main() {
  signal(SIGINT, sighandler);
  // ...
  // create objects, spawn threads + allocate dynamic memory
  // ...
  while (!terminate) sleep(2);
  // ...
  // clean up memory, close threads, etc.
  // ...
  signal(SIGINT, SIG_DFL);  // is this necessary?
}

我想知道一些事情:

  1. 是否需要任何信号处理?
    我在这个线程"Linux C catching kill signal for graceful termination" 中读到,显然操作系统将为我处理清理工作。 因此,我可以只用一个无限循环替换信号处理程序,让操作系统优雅地退出线程、取消分配内存等吗?

  2. 关于干净终止,我是否需要关注其他任何信号?这个线程"How does SIGINT relate to the other termination signals?",对于列出我可能关心的所有信号很有用,但实际需要处理的信号有多少?

  3. 我的示例中的终止变量是否必须是可变的?我见过很多例子,这个变量是 volatile 的,还有一些不是。

  4. 我了解到 signal() 现在已弃用,可以使用 sigaction()。有没有很好的例子来展示如何从之前的signal() 调用转换?我对必须创建/传递的新结构以及它们如何组合在一起遇到了问题。

  5. 是否需要第二次致电signal()
    对于sigaction(),我需要关注类似的事情吗?

明确地说,我要完成的所有工作就是让我的主循环运行,直到 ctrlc 或电源断开或发生非常糟糕的事情。

【问题讨论】:

  • 关于清理,系统无法进行一些清理。一个例子是,如果您通过串行端口进行通信,那么您可能需要发送一个特殊的“再见”消息来告诉另一端您要离开了。
  • 是的,但这不是告诉对方我已经离开的一种不可靠的方法吗?例如,如果 SIGKILL 命中进程,则我无能为力,或者没有可以放置的处理程序,因为没有处理程序可以映射到 SIGKILL... 对于某些应用程序,尽管我可以看到为什么这是有用的。对于我来说,我只需要进行内存释放并关闭一些线程。
  • 您没有义务进行信号处理,但是如果您有一个 5 步每步 10 分钟的计算程序,这可能会很烦人。用户可能会意外杀死该程序。当它被杀死时,适当的信号处理可以保存步骤 1 到 4 的中间结果。
  • @It'sPete 谁建议你在 main() 中调用 signal(SIGINT, SIG_DFL);?参考?

标签: c++ c linux signals


【解决方案1】:

[Q-3] 我的示例中的terminate 变量是否必须是volatile?我有 看到了许多这个变量是 volatile 的例子,还有一些例子 不是。

标志terminate 应该是volatile sig_atomic_t

因为处理函数可以异步调用。也就是说,处理程序可能会在程序中的任何位置被调用,这是不可预测的。如果两个信号在很短的时间间隔内到达,一个处理程序可以在另一个处理程序内运行。声明volatile sig_atomic_t 被认为是更好的做法,这种类型总是以原子方式访问,避免中断访问变量的不确定性。 volatile 告诉编译器不要优化并将其放入寄存器。 (阅读:Atomic Data Access and Signal Handling 了解详情)。
另一个参考:24.4.7 Atomic Data Access and Signal Handling。 此外,7.14.1.1-5 中的 C11 标准表明,只有 volatile sig_atomic_t 的对象可以从信号处理程序中访问(访问其他对象具有未定义的行为)。

[Q-4] 我了解到 signal() 现在已弃用,可以使用 sigaction()。是 有任何非常好的例子来展示如何从 以前的signal() 电话?我在使用新结构时遇到了问题 我必须创建/传递以及如何将它们组合在一起。

下面的示例(以及 cmets 中的链接)可能会有所帮助:

// 1. Prepare struct 
struct sigaction sa;
sa.sa_handler =  sighandler;

// 2. To restart functions if interrupted by handler (as handlers called asynchronously)
sa.sa_flags = SA_RESTART; 

// 3. Set zero 
sigemptyset(&sa.sa_mask);

/* 3b. 
 // uncomment if you wants to block 
 // some signals while one is executing. 
sigaddset( &sa.sa_mask, SIGINT );
*/ 

// 4. Register signals 
sigaction( SIGINT, &sa, NULL );

参考:

  1. Beginning Linux Programming, 4th Edition:在本书中,你的代码在“第 11 章:进程和信号”中用 sigaction() 很好地解释了。
  2. sigaction documentation,包括一个示例(快速学习)。
  3. GNU C 库:Signal Handling
    *我从 1 开始,目前我正在阅读 3 GNU-library

[Q-5] 是否需要第二次致电signal()?对于sigaction(),我需要关注类似的事情吗?

我不清楚为什么在程序终止之前将其设置为默认操作。我想下面这段话会给你答案:

Handling Signals

对信号的调用只为信号的一次 出现建立信号处理。在调用信号处理函数之前,库会重置信号,以便在再次出现相同信号时执行默认操作。例如,如果在信号处理程序中执行的操作再次引发相同的信号,则重置信号处理有助于防止无限循环。如果您希望处理程序在每次发生时都用于信号,则必须在处理程序中调用信号以恢复它。在恢复信号处理时应谨慎。例如,如果您不断恢复SIGINT 处理,您可能会失去中断和终止程序的能力。

signal() 函数仅定义下一个接收信号的处理程序,在此之后恢复默认处理程序。因此,如果程序需要继续使用非默认处理程序处理信号,则信号处理程序必须调用signal()

阅读讨论以供进一步参考:When to re-enable signal handlers

[Q-1a] 是否需要任何信号处理?

是的,Linux 会为您进行清理。例如,如果您不关闭文件或套接字,Linux 将在进程终止后进行清理。但是 Linux 可能不需要立即执行清理,并且可能需要一些时间(可能是为了保持系统性能较高或其他一些问题)。例如,如果您不关闭 tcp-socket 并且程序终止,内核不会立即关闭套接字以确保所有数据都已传输,如果可能,TCP 会保证交付。

[Q-1b]因此,我可以只用一个无限循环替换信号处理程序,让操作系统优雅地退出线程、释放内存等吗?

不,操作系统仅在程序终止后执行清理。在进程执行时,分配给该进程的资源不会被操作系统占用。 (操作系统无法知道您的进程是否处于无限循环中 - this is an unsolvable problem)。如果您希望在进程终止后操作系统为您执行清理操作,那么您不需要处理信号(即使您的进程被信号异常终止)。

[Q] 我想要完成的所有工作都是为了让 my: 主循环运行直到 ctrlc 或电源断开或者发生了非常糟糕的事情。

不,有限制!你无法捕捉到所有信号。有些信号是不可捕捉的,例如SIGKILLSIGSTOP 都是终止信号。引用一个:

— Macro: int SIGKILL

SIGKILL 信号用于立即终止程序。它不能被处理或忽略,因此总是致命的。它也不可能阻止这个信号。

所以你不能a program that cannot be interrupted (an uninterrupted program)

我不确定,但可能你可以在 Windows 系统中做这样的事情:通过编写 TSR(某种内核模式挂钩)。我记得在我的论文时间里,有些病毒甚至无法从任务管理器中终止,但我也相信它们会通过管理员权限欺骗用户。

希望这个回答对你有帮助。

【讨论】:

  • 您可能需要更新该链接以指向当前的 glibc 文档,gnu.org/software/libc/manual/html_node/…
  • @Hasturkun 我也想添加它,因为我从那里读到我在回答中给出的。谢谢!用新答案更新链接
  • 另外,您可能希望始终调用sigemptyset(&sa.sa_mask),否则其内容未初始化。 (另外,你不需要另一个变量)
  • "标志终止应该是 volatile sigatomic_t" - 好吧,对于这个简单的例子来说不一定。如果您愿意再等 2 秒来识别更改,那么您可以没有它(即使这种情况发生的概率也很低)
  • @KarolyHorvath 是的!是信号带有我们不需要的时间间隔。因为编写小型处理程序也是一种好习惯。
【解决方案2】:

1.是否需要任何信号处理?

  • 在您发布的链接的特定情况下,是的。涉及网络的软件的运行需要特定的操作,例如警告客户端关闭一个套接字,以免干扰其运行。
  • 在您的特定情况下,您无需处理任何信号即可优雅地清除进程:您的操作系统会为您完成。

2。关于彻底终止,我还需要关注其他任何信号吗?

  • 首先,看看他的页面:The GNU Library Signals 终止信号是您所关心的。但是看看 SIGUSR1 和 SIGUSR2,即使你永远不会在任何软件中找到它们,除了调试目的。

  • 如果您不希望您的软件突然终止,则需要处理所有这些终止信号。

3。我的示例中的终止变量是否必须是可变的?

  • 绝对不是。

4.我读到 signal() 现在已弃用,并使用 sigaction()

  • Sigaction() 是 POSIX,而信号是 C 标准。

  • Signal() 对我来说很好,但如果你想要任何例子:IBM Example

【讨论】:

    【解决方案3】:

    首先 - 如果您不知道是否应该处理任何信号,那么您可能不知道。信号需要在某些特定情况下进行处理,例如关闭套接字、在退出之前向其他连接的进程发送一些消息,或者处理来自 write() 的 SIGPIPE 信号(可能还有更多)。

    其次 - 这个while (!terminate) sleep(2); 不是很好 - 在最坏的情况下,它可能会让用户(甚至系统)迫不及待地不得不等待 2 秒并向您的程序发送您无法处理的 SIGKILL。

    恕我直言,这里最好的解决方案是using signalfd and select,因此您可以终止程序而无需等待 2 秒。

    【讨论】:

      【解决方案4】:

      要改用sigaction,您可以使用如下函数:

      /*
       * New implementation of signal(2), using sigaction(2).
       * Taken from the book ``Advanced Programming in the UNIX Environment''
       * (first edition) by W. Richard Stevens.
       */
      sighandler_t my_signal(int signo, sighandler_t func)
      {
          struct sigaction nact, oact;
      
          nact.sa_handler = func;
          nact.sa_flags = 0;
          # ifdef SA_INTERRUPT
          nact.sa_flags |= SA_INTERRUPT;
          # endif
          sigemptyset(&nact.sa_mask);
      
          if (sigaction(signo, &nact, &oact) < 0)
              return SIG_ERR;
      
          return oact.sa_handler;
      }
      

      【讨论】:

      • 如果您对作者表示感谢,您可能需要更正书名中的错字。
      【解决方案5】:

      没有必要为此使用信号。标准终止不需要 被捕获。你可能有理由抓住它,但这取决于你的应用程序,而不是操作系统要求的任何东西。

      就信号而言,现在您通常应该使用 sigaction 而不是信号,它解决了标准化问题。

      信号处理程序必须写成可重入的。这并不要求您的终止变量是可变的,但它可能取决于您如何使用它!

      W. Richard Stevens 的书 "Advanced Programming in the UNIX Environment" 有一个很好的例子,说明了为什么以及如何处理信号。

      不,您不必在应用程序终止之前放回默认处理程序,您的处理程序仅对您的应用程序有效,因此如果您只是关闭应用程序,则不需要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-19
        • 1970-01-01
        • 2014-04-29
        • 1970-01-01
        相关资源
        最近更新 更多