目前,我遇到了用户抱怨我的应用程序终止的问题。在某些(看似任意)条件和桌面环境下,应用程序不会终止,并且重新启动时不会保存设置。我在相关的 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`
但最坏的情况是,这些只会停止您的进程,而不会破坏您的状态。
除非您知道需要处理CHLD
、CONT
、 或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+非常有名)。也是一个好主意,因为它仍然是基本的终止信号,并且由内核使用(例如,在系统关闭时)。可能是前两个版本的“更严厉”版本,因为它会导致核心转储。当您收到该消息时,您的程序可能会立即崩溃。在收到任何其他信息后,您的程序将被期望“清理混乱并离开”(保存设置,正确终止)。CSIGTERM
SIGQUIT
SIGILL
、SIGFPE
、SIGSEGV
和SIGPIPE
是与程序不当行为相关的信号。分段故障可能是最常见的情况,但最终,所有这些情况都可以被视为错误,并且我不认为捕获信号是从这些情况中恢复的最佳方法。
- 大多数时候,编译器不会
SIGILL
轻易让这种情况发生(更多这里)所以我想你可以把这个放在一边。 - 确保你永远不会除以零,这样你就可以避免大多数
SIGFPE
s。除此之外,只需确保您的程序擅长数学并且不会对数字感到疯狂。 SIGSEGV
可能是由多种原因引起的(非法内存访问、非法取消引用),对于这一点,我建议在程序执行时使用它来监视程序,并使用堆栈gdb
追踪讨厌的指令。gdb
- 最后,
SIGPIPE
主要与数据流相关(读取文件、连接……)。如果您的任何程序 I/O 流在执行时可能会终止,您应该确保您的程序在使用它们之前检查其描述符,并处理其上发生的所有 I/O 事件(例如,在使用select
或时poll
)。gdb
在这里可能也有用。
SIGABRT
、SIGALRM
、SIGUSR1
和SIGUSR2
基本上是用户信号。如果你收到了它们,很可能你真的做到了。
这给我们留下了SIGHUP
和SIGKILL
,它可能不会被捕获。
SIGHUP
例如,如果您从终端启动进程,然后关闭该终端而不分离进程(disown
或使用nohup
),就会发生这种情况。如果您的程序从终端启动并作为守护进程运行,请不要忘记使其与 shell 分离,例如通过双分叉。如果您的程序可以从 GUI 启动,或者在启动时控制终端不太可能“关闭”,则此特定信号的相关性较小。SIGKILL
如您所知,这是万能的信号。这是内核说“滚蛋!”的方式。如果您收到了该信号,那么您应该将注意力集中在发出信号的任何内容上,而不是如何在程序中处理它。
最后,分析信号并使用调试工具可能是解决这些问题的最简单方法。如果当你的程序出现问题时内核触发了一个信号,gdb
应该可以帮助你定位问题。在这种情况下,请忘记该信号,而专注于它告诉您的错误。
如果信号来自“外部”,找到发射器可能是最好的做法(如果实在找不到的话追踪这kill(2)
系统调用可能是最后的选择)。然后您可以决定是否要忽略它 ( SIG_IGN
),或考虑它。沉默发射程序也可以是一个不错的选择......