Linux 应用程序开发和信号处理

Linux 应用程序开发和信号处理

目前,我遇到了用户抱怨我的应用程序终止的问题。在某些(看似任意)条件和桌面环境下,应用程序不会终止,并且重新启动时不会保存设置。我在相关的 irc 频道中询问,大多数时候我被告知要正确处理信号。我知道终端中的 Ctrl-C 的 SIGINT 和“正常”终止的 SIGTERM 。但有人告诉我 SIGHUP 也很重要。所以我的问题是:

我必须处理哪些信号才能构建行为良好的应用程序?

答案1

https://en.wikipedia.org/wiki/Unix_signal

有一个标准信号及其默认操作的列表。任何有足够特权的进程总是可以向您发送任何信号,但进程(或您,通过进程)不应该这样做。

您应该只kill使用并期望您的流程kill使用学期以及一堆可以由您的环境(shell、终端驱动程序)生成的信号,除非您知道目标处理您想要发送的特殊信号。

您可以按操作对基本信号进行排序。

核心转储信号

ABRT TRAP SYS BUS FPE ILL SEGV   XFSZ XCPU QUIT

要么是由您(TRAP、ABRT、SYS)使用某些专用函数生成的,要么是由您进入硬错误状态(BUS、FPE、ILL、SEGV)生成的。 QUIT由用户在想要核心转储的终端上生成 ( C+\)。

您可能希望将它们保留为默认配置。

出于终止信号:

HUP INT PIPE TERM ALRM POLL PROF USR1 USR2

你应该期待

HUP INT PIPE TERM

根据您的环境在不同情况下的情况:

HUP -- when the terminal hungs up or you become a stopped orphaned process
INT -- when the user at the terminal interrupts you with C-c
PIPE -- when a PIPE or socket you write into closes, e.g. by
        exiting before you finished writing to it 
       (`yourprogram | (exit)` will give you a `PIPE` 
        if yourprogram attempts to write to its STDOUT) 
TERM -- when a process ends you a normal termination request

仅当您自己设置时,您才应该收到其余的终止信号。

停止你无能为力。

您可能希望拦截终端生成的停止信号:

TTIN -- you read from the terminal and you're a backgrounded process
TTOU -- you write the terminal when you're a backgrounded process
        and the terminal is set to put you to sleep when you do
TSTP -- you're being put to sleep with `C-Z`

但最坏的情况是,这些只会停止您的进程,而不会破坏您的状态。

除非您知道需要处理CHLDCONT、 或URG,否则您不需要。

长话短说:

基本上,我认为如果您通常想要进行一些退出前清理,则应该处理(或忽略)HUP、INT、PIPE 和 TERM。其余的可以不用管,除非你的程序使用这些信号,或者除非你在所有情况下都绝对需要一些清理。

在后一种情况下,您可以空白地阻止所有未处理的信号,但要注意信号阻止掩码会在 fork 和 execve 调用之间继承,并忽略或阻止像 ILL 这样的信号,如果它们是在进程运行时产生的并且没有发送给您通过kill 或sigqueue 会给你未定义的行为。

如果您想了解更多信息,请浏览联机帮助页和标准。信号在 Unix 上是一个相当大的话题,处理它们可能会变得非常棘手。

答案2

如果您这样做,您可能最终会忽略大量信号,仅仅因为其中一个信号正在终止您的程序。正如 sblingx 在评论中所建议的,最好的做法是分析这些信号并了解它们来自哪里。毕竟,你的程序被杀死可能是有充分理由的。

话虽如此,你可以看看signals(7)获取可能导致进程终止的信号列表:

SIGHUP、SIGINT、SIGQUIT、SIGILL、SIGABRT、SIGFPE、SIGKILL、SIGSEGV、SIGPIPE、SIGALRM、SIGTERM、SIGUSR1 和 SIGUSR2。

如果您的应用程序花费一些时间连接到终端,那么处理SIGINT是一个好主意(毕竟Ctrl+非常有名)。也是一个好主意,因为它仍然是基本的终止信号,并且由内核使用(例如,在系统关闭时)。可能是前两个版本的“更严厉”版本,因为它会导致核心转储。当您收到该消息时,您的程序可能会立即崩溃。在收到任何其他信息后,您的程序将被期望“清理混乱并离开”(保存设置,正确终止)。CSIGTERMSIGQUIT

SIGILLSIGFPESIGSEGVSIGPIPE是与程序不当行为相关的信号。分段故障可能是最常见的情况,但最终,所有这些情况都可以被视为错误,并且我不认为捕获信号是从这些情况中恢复的最佳方法。

  • 大多数时候,编译器不会SIGILL轻易让这种情况发生(更多这里)所以我想你可以把这个放在一边。
  • 确保你永远不会除以零,这样你就可以避免大多数SIGFPEs。除此之外,只需确保您的程序擅长数学并且不会对数字感到疯狂。
  • SIGSEGV可能是由多种原因引起的(非法内存访问、非法取消引用),对于这一点,我建议在程序执行时使用它来监视程序,并使用堆栈gdb追踪讨厌的指令。gdb
  • 最后,SIGPIPE主要与数据流相关(读取文件、连接……)。如果您的任何程序 I/O 流在执行时可能会终止,您应该确保您的程序在使用它们之前检查其描述符,并处理其上发生的所有 I/O 事件(例如,在使用select或时poll)。gdb在这里可能也有用。

SIGABRTSIGALRMSIGUSR1SIGUSR2基本上是用户信号。如果你收到了它们,很可能你真的做到了。

这给我们留下了SIGHUPSIGKILL,它可能不会被捕获。

  • SIGHUP例如,如果您从终端启动进程,然后关闭该终端而不分离进程(disown或使用nohup),就会发生这种情况。如果您的程序从终端启动并作为守护进程运行,请不要忘记使其与 shell 分离,例如通过双分叉。如果您的程序可以从 GUI 启动,或者在启动时控制终端不太可能“关闭”,则此特定信号的相关性较小。

  • SIGKILL如您所知,这是万能的信号。这是内核说“滚蛋!”的方式。如果您收到了该信号,那么您应该将注意力集中在发出信号的任何内容上,而不是如何在程序中处理它。

最后,分析信号并使用调试工具可能是解决这些问题的最简单方法。如果当你的程序出现问题时内核触发了一个信号,gdb应该可以帮助你定位问题。在这种情况下,请忘记该信号,而专注于它告诉您的错误。

如果信号来自“外部”,找到发射器可能是最好的做法(如果实在找不到的话追踪kill(2)系统调用可能是最后的选择)。然后您可以决定是否要忽略它 ( SIG_IGN),或考虑它。沉默发射程序也可以是一个不错的选择......

相关内容