【发布时间】:2014-02-17 03:11:00
【问题描述】:
我知道当内核使用SIGSEGV 报告内存访问冲突时,不能忽略它。但是,如果我为SIGSEGV 安装一个什么都不做的信号处理程序,然后另一个进程使用kill 向我发送该信号,这是否会与我使用“正常”信号一样(如SIGUSR1)代替?
【问题讨论】:
标签: c++ segmentation-fault signals posix kill
我知道当内核使用SIGSEGV 报告内存访问冲突时,不能忽略它。但是,如果我为SIGSEGV 安装一个什么都不做的信号处理程序,然后另一个进程使用kill 向我发送该信号,这是否会与我使用“正常”信号一样(如SIGUSR1)代替?
【问题讨论】:
标签: c++ segmentation-fault signals posix kill
Grijesh Chauhan 的回答在技术上是正确的,但很难理解,所以我将写出我自己对基本相同点的阐述。带脚注。0
Dima 询问当一个线程为 SIGSEGV 安装无操作处理程序,然后另一个线程使用 kill1 在该线程上生成 SIGSEGV 时会发生什么。一句话的答案是处理程序运行,什么都不做,然后控制返回到被中断线程内的正常流程。与内核生成SIGSEGV 作为对实际内存访问冲突的响应不同,这种情况不会 触发undefined behavior2。但是,SIGSEGV 和它的朋友(SIGBUS、SIGFPE 和 SIGILL)的预期目的是让内核告诉你的程序它做了一些令人发指的事情必须 是一个bug,无法继续正常执行,你想在被杀之前清理一下吗?因此,将它们用于其他任何事情都是不明智的。为每个应用程序保留了几个信号(SIGUSR1、SIGUSR2 和 SIGRTMIN 到 SIGRTMAX)以供使用;您应该改用其中之一。
对于更长的答案,我将参考 POSIX 标准,3 小节Signal Actions。首先,SIGFPE、SIGILL、SIGSEGV 和 SIGBUS 这四个信号与任何其他信号一样,可以“异步”传递——在没有特定时间——因为某些代码使用了系统调用(例如作为kill) 来生成它们。当这种情况发生时,它们被视为与默认“动作”恰好是“异常终止程序”的任何其他信号完全相同;请注意,许多其他信号都具有此属性,包括保留供应用程序使用的信号。如果你的程序只需要担心接收SIGFPE、SIGILL、SIGSEGV和SIGBUS,当它们由kill和朋友生成时,它可以用它们做所有正常的事情:阻止它们,忽略它们,建立对异步信号处理程序执行任何有效操作的信号处理程序,4通过sigwait 或signalfd 而不是正常的asynchronous system trap-like 传递机制接收它们。
但是。 SIGFPE、SIGILL、SIGSEGV 和 SIGBUS 也由内核“同步”生成,以响应触发硬件异常的不同类型的错误程序行为,例如尝试访问未映射的内存。同步意味着信号在执行有问题的 CPU 指令时立即传递,并且在同一个线程上而不是在进程中碰巧解除阻塞的任何线程上传递。我们真的不是在开玩笑“立即”部分:如果有一个信号处理程序,当它执行时,保存的程序计数器将指向导致任何类型的硬件异常的确切指令。内核不允许通过导致 CPU 发出硬件异常的指令继续执行,因此 POSIX 表示尝试丢弃这些信号而不是终止进程或采取一些剧烈的恢复操作:
在忽略不是由
kill()、sigqueue()或raise()生成的SIGFPE、SIGILL、SIGSEGV或SIGBUS信号后,进程的行为未定义。
("After it ignores" 在上下文中的意思是"如果内核尝试在操作设置为SIG_IGN 时同步生成这些信号之一。)
进程从信号捕获函数正常返回后的行为是不确定的/p>
(“正常返回”的意思是“不是通过调用(sig)longjmp”。它是有效的,至少就内核而言,展开堆栈并在其他地方恢复执行;你但是,如果您没有充分修复导致故障的损坏数据结构,则可能会遇到麻烦。正如 Basile 所提到的,弄乱保存的处理器状态也是有效的,以便“正常”返回不会'不仅仅是尝试再次运行相同的错误指令;但这样做往往涉及手动解释机器指令和其他类似的黑魔法。)
如果任何 SIGFPE、SIGILL、SIGSEGV 或 SIGBUS 信号在它们被阻塞时生成,则结果是不确定的,除非该信号是由另一个进程的操作或由函数 kill() 之一生成的, pthread_kill()、raise() 或 sigqueue()。
(我不知道为什么这个措辞与其他两个有点不同;可能只是因为写specific documentation for sigprocmask的人没有与写general documentation for signal actions的人协调。)
0 Dima 将他们的问题标记为“Linux”,但无论如何我还是要提出这个警告,以使未来的读者受益:我在这里写的所有内容都应该假设仅适用于 到符合 POSIX 的操作系统;首先,这意味着“您可能会遇到的所有操作系统,Windows 除外。”例外很重要。与 POSIX 相比,Windows 对线程和进程之间的关系有完全不同的概念,并且有一个完全不同的(优越的!)用于报告 CPU 生成的错误程序异常的基本机制。 Windows 上的信号由 C 库模拟,并且可能不 像我描述的那样运行。
1 大概实际上是pthread_kill。
2请阅读整个系列博文
3具体来说,The Open Group Base Specifications Issue 7, 2013 edition 的在线副本,这也是“同时”2013 年版 IEEE标准 1003.1,POSIX。
4 不是很多,但比没有更重要; “Signal Actions”文档中有一个列表,但没有片段 ID 允许我指出它。
【讨论】:
我有接收信号
SIGSEGV的函数,但是什么都不做,这是否意味着信号被忽略了?
是的,你捕捉到了信号 SIGSEGV 并且在处理程序中什么都不做,控制返回。
一般而言,忽略真正的 SIGSEGV 并不意味着错误不会使程序能够继续有意义地执行。注意 SIGSEGV 不是通过用户请求(某些组合键)发送的信号,当程序尝试在为其分配的内存之外读取或写入时,SIGSEGV 会引发。该名称是“分段违规”的缩写。
SIG35-C. Do not return from a computational exception signal handler
根据 C 标准,子条款 7.14.1.1 ISO/IEC 9899:2011,如果作为计算结果输入的信号处理程序 异常(即,其参数值为 SIGFPE 、 SIGILL 、 或 SIGSEGV 或任何其他实现定义的值对应于 这样的异常)返回,行为未定义。
忽略
SIGFPE后,进程的行为未定义,SIGILL、SIGSEGV或SIGBUS不是由kill()、sigqueue()或raise()。
第二个
信号是否传递给默认实现?
不,根据标准引用行为,或者您的代码不是未定义,因为您通过kill() 生成信号。默认实现将立即终止您的进程执行并生成代码转储(参考:Standard Signals)。
您的代码具有明确定义的行为 - 信号将被接收, 处理程序将运行,它什么也不做,控制将恢复正常 flow - 但您仍应将其更改为使用其他信号。
SIGUSR1和SIGUSR2旨在按照您的使用方式使用SIGSEGV现在,作为内部应用程序向自身发送消息。
GNU C 库是学习Signal Handling 的好资源。
【讨论】:
SIGUSR1 和 SIGUSR2 旨在以您现在使用 SIGSEGV 的方式使用,作为内部应用程序向自身发送消息。
SIGFPE、SIGILL、SIGSEGV 或 SIGBUS 信号后未定义b>不是由kill()、sigqueue() 或raise() 生成的”。 (强调我的。)
SIGFPE、SIGILL、SIGSEGV 或 SIGBUS 的处理程序处返回”对于绝大多数情况下是很好的建议,其中有人不聪明事情例如与raise,他们认为添加警告会分散人们的注意力。不幸的是,几乎所有非平凡的 C 程序最终都做了一些聪明的事情,因此 CERT 指南最终没有什么帮助。
我有接收信号 SIGSEGV 但什么都不做的函数, 是否意味着该信号被忽略?
从技术上讲,它不会被忽视。您定义了信号处理程序,它确实处理了信号,尽管该函数没有做任何事情。 请注意,就信号而言,IGNORE 表示内核不处理信号,即不采取任何行动。
每个信号都有一个默认操作。它是 SEGV 的核心转储(以核心终止)并终止 SEGKILL 等......
用户可以使用 signal() 或 sigaction() API 更改默认操作,该操作可以设置为以下之一:
所以在你的情况下,它是第三个。如果您使用第二个操作,我会说忽略信号。
信号是否传递给默认实现?
没有。
【讨论】: