文章目录
1. 前言
The GNU C Library Reference Manual for version 2.35
2. 信号处理
Signal Handling
信号是传递给进程的软件中断。操作系统使用信号将异常情况报告给正在执行的程序。一些信号报告错误,例如引用无效的内存地址;其他人报告异步事件,例如电话线断开。
GNU C 库定义了多种信号类型,每种信号类型都对应一种特定的事件。某些类型的事件使程序不宜或不可能照常进行,相应的信号通常会中止程序。默认情况下会忽略报告无害事件的其他类型的信号。
如果您预期会导致信号的事件,您可以定义一个处理函数并告诉操作系统在该特定类型的信号到达时运行它。
最后,一个进程可以向另一个进程发送信号;这允许父进程中止子进程,或两个相关进程进行通信和同步。
2.1. 信号的基本概念
Basic Concepts of Signals
本节解释信号是如何产生的、信号传递后会发生什么以及程序如何处理信号的基本概念。
2.1.1. 几种信号
Some Kinds of Signals
信号报告异常事件的发生。以下是一些可能导致(或生成或引发)信号的事件:
- 程序错误,例如除以零或发出超出有效范围的地址。
- 用户请求中断或终止程序。大多数环境都设置为允许用户通过键入 C-z 来暂停程序,或使用 C-c 终止程序。无论使用什么键序列,操作系统都会发送适当的信号来中断进程。
- 子进程的终止。
- 计时器或警报到期。
- 由同一进程调用 kill 或 raise。
- 来自另一个进程的终止调用。信号是一种有限但有用的进程间通信形式。
- 尝试执行无法完成的 I/O 操作。示例是从没有写入器的管道读取(请参阅管道和 FIFO),以及在某些情况下读取或写入终端(请参阅作业控制)。
每种类型的事件(除了显式调用 kill 和 raise 之外)都会产生自己特定类型的信号。标准信号中列出并详细描述了各种信号。
2.1.2. 信号生成的概念
Concepts of Signal Generation
一般来说,产生信号的事件分为三大类:错误、外部事件和显式请求。
错误意味着程序做了一些无效的事情并且不能继续执行。但并非所有类型的错误都会产生信号——事实上,大多数都不会。例如,打开一个不存在的文件是一个错误,但它不会引发信号;相反,open 返回 -1。通常,通过返回指示错误的值来报告与某些库函数必然相关的错误。引发信号的错误可能发生在程序的任何地方,而不仅仅是在库调用中。这些包括除以零和无效的内存地址。
外部事件通常与 I/O 或其他进程有关。这些包括输入的到达、计时器的到期和子进程的终止。
显式请求意味着使用诸如 kill 之类的库函数,其目的是专门生成信号。
信号可以同步或异步生成。同步信号与程序中的特定动作有关,并在该动作期间传递(除非被阻塞)。大多数错误会同步生成信号,进程的显式请求也会为同一进程生成信号。在某些机器上,某些类型的硬件错误(通常是浮点异常)不会完全同步报告,但可能会在几条指令之后到达。
异步信号由接收它们的进程控制之外的事件生成。这些信号在执行期间到达不可预测的时间。外部事件异步生成信号,适用于其他进程的显式请求也是如此。
给定类型的信号通常是同步的或通常是异步的。例如,错误信号通常是同步的,因为错误会同步生成信号。但是任何类型的信号都可以通过显式请求同步或异步生成。
2.1.3. 信号如何传递
How Signals Are Delivered
当一个信号产生时,它变成待处理的。通常它会在很短的时间内保持未决状态,然后被传递到发出信号的进程。但是,如果这种信号当前被阻塞,它可能会无限期地保持等待状态——直到这种信号被解除阻塞。解封后,即刻送达。请参阅阻塞信号。
当信号被传递时,无论是立即还是经过长时间的延迟,都会对该信号采取指定的操作。对于某些信号,例如 SIGKILL 和 SIGSTOP,动作是固定的,但对于大多数信号,程序可以选择:忽略信号、指定处理函数或接受该信号的默认动作。该程序使用信号或 sigaction 等函数指定其选择(请参阅指定信号操作)。我们有时会说处理程序捕获了信号。在处理程序运行时,该特定信号通常被阻塞。
如果对某种信号的指定动作是忽略它,则生成的任何此类信号都将立即丢弃。即使当时信号也被阻塞,也会发生这种情况。以这种方式丢弃的信号将永远不会被传递,即使程序随后为这种信号指定了不同的操作然后解除阻塞也不会传递。
如果程序既没有处理也没有忽略的信号到达,它的默认动作就会发生。每种信号都有自己的默认操作,如下所述(参见标准信号)。对于大多数类型的信号,默认操作是终止进程。对于代表“无害”事件的某些类型的信号,默认操作是什么都不做。
当一个信号终止一个进程时,它的父进程可以通过检查由 wait 或 waitpid 函数报告的终止状态代码来确定终止的原因。(这在 进程完成 中有更详细的讨论。)它可以获得的信息包括终止是由于信号和所涉及的信号类型的事实。如果从 shell 运行的程序被信号终止,shell 通常会打印某种错误消息。
通常代表程序错误的信号有一个特殊的属性:当这些信号之一终止进程时,它还会写入一个核心转储文件,该文件记录终止时进程的状态。您可以使用调试器检查核心转储以调查导致错误的原因。
如果您通过显式请求发出“程序错误”信号,并且这会终止进程,它会生成一个核心转储文件,就好像该信号是直接由错误引起的一样。
2.2. 标准信号
Standard Signals
本节列出了各种标准信号的名称并描述了它们所代表的事件类型。每个信号名称都是一个宏,它代表一个正整数——那种信号的信号编号。您的程序不应该对特定类型的信号的数字代码做出假设,而应该始终使用此处定义的名称来引用它们。这是因为给定类型信号的编号可能因系统而异,但名称的含义是标准化的且相当统一。
信号名称在头文件 signal.h 中定义。
宏:int NSIG
这个符号常数的值是定义的信号总数。由于信号编号是连续分配的,因此 NSIG 也比定义的最大信号编号大一。
2.2.1. 程序错误信号
Program Error Signals
当操作系统或计算机本身检测到严重的程序错误时,会产生以下信号。一般来说,所有这些信号都表明您的程序在某些方面严重损坏,并且通常无法继续遇到错误的计算。
有些程序会处理程序错误信号,以便在终止前进行整理;例如,关闭终端输入回显的程序应处理程序错误信号以重新打开回显。处理程序应该通过为发生的信号指定默认操作然后重新引发它来结束;这将导致程序以该信号终止,就好像它没有处理程序一样。(请参阅终止进程的处理程序。)
终止是大多数程序中程序错误的合理最终结果。然而,像 Lisp 这样可以加载已编译用户程序的编程系统可能需要继续执行,即使用户程序发生错误也是如此。这些程序具有使用 longjmp 将控制返回到命令级别的处理程序。
所有这些信号的默认操作是导致进程终止。如果您阻止或忽略这些信号或为它们建立正常返回的处理程序,那么当这些信号发生时您的程序可能会严重中断,除非它们是由 raise 或 kill 而不是真正的错误生成的。
当这些程序错误信号之一终止进程时,它还会写入一个核心转储文件,该文件记录终止时进程的状态。核心转储文件被命名为 core 并且被写入当时进程中的当前目录。(在 GNU/Hurd 系统上,您可以使用环境变量 COREFILE 指定核心转储的文件名。)核心转储文件的目的是让您可以使用调试器检查它们以调查导致错误的原因。
宏:int SIGFPE
SIGFPE 信号报告致命的算术错误。虽然名称来源于“浮点异常”,但这个信号实际上涵盖了所有算术错误,包括被零除和溢出。如果程序将整数数据存储在一个位置,然后用于浮点运算,这通常会导致“无效运算”异常,因为处理器无法将数据识别为浮点数。
实际的浮点异常是一个复杂的主题,因为有许多类型的异常具有微妙的不同含义,而 SIGFPE 信号无法区分它们。二进制浮点算术的 IEEE 标准(ANSI/IEEE 标准 754-1985 和 ANSI/IEEE 标准 854-1987)定义了各种浮点异常,并要求符合标准的计算机系统报告它们的出现。但是,该标准没有规定如何报告异常,或者操作系统可以为程序员提供什么样的处理和控制。
BSD 系统为 SIGFPE 处理程序提供了一个额外的参数,用于区分各种异常原因。为了访问此参数,您必须定义处理程序以接受两个参数,这意味着您必须将其转换为单参数函数类型才能建立处理程序。GNU C 库确实提供了这个额外的参数,但该值仅对提供信息的操作系统(BSD 系统和 GNU 系统)有意义。
FPE_INTOVF_TRAP
整数溢出(在 C 程序中不可能,除非您以特定于硬件的方式启用溢出捕获)。
FPE_INTDIV_TRAP
整数除以零。
FPE_SUBRNG_TRAP
下标范围(C 程序从不检查的东西)。
FPE_FLTOVF_TRAP
浮动溢出陷阱。
FPE_FLTDIV_TRAP
浮点数/十进制除以零。
FPE_FLTUND_TRAP
浮动底溢陷阱。(捕获浮动下溢通常不会启用。)
FPE_DECOVF_TRAP
十进制溢出陷阱。(只有少数机器有十进制算术,而 C 从不使用它。)
宏:int SIGILL
该信号的名称来源于“非法指令”;这通常意味着您的程序正在尝试执行垃圾或特权指令。由于 C 编译器只生成有效指令,SIGILL 通常表明可执行文件已损坏,或者您正在尝试执行数据。进入后一种情况的一些常见方法是传递一个无效的对象,其中需要指向函数的指针,或者通过写入超过自动数组的末尾(或指向自动变量的指针的类似问题)并破坏其他数据堆栈,例如堆栈帧的返回地址。
SIGILL 也可以在堆栈溢出或系统无法运行信号处理程序时生成。
宏:int SIGSEGV
当程序试图在为其分配的内存之外读取或写入,或写入只能读取的内存时,会生成此信号。(实际上,信号只有在程序跑到足够远的地方被系统的内存保护机制检测到时才会出现。)这个名字是“segmentation violation”的缩写。
获取 SIGSEGV 条件的常用方法包括取消引用 null 或未初始化的指针,或者使用指针单步执行数组,但无法检查数组的结尾。取消引用空指针会生成 SIGSEGV 还是 SIGBUS,因系统而异。
宏:int SIGBUS
当取消引用无效指针时会生成此信号。与 SIGSEGV 一样,此信号通常是取消引用未初始化指针的结果。两者的区别在于 SIGSEGV 表示对有效内存的无效访问,而 SIGBUS 表示对无效地址的访问。特别是,SIGBUS 信号通常是由于取消引用未对齐的指针而产生的,例如在不能被四整除的地址处引用四字整数。(每种计算机对地址对齐都有自己的要求。)
该信号的名称是“总线错误”的缩写。
宏:int SIGABRT
该信号表示程序本身检测到的错误,并通过调用 abort 报告。请参阅中止程序。
宏:int SIGIOT
由 PDP-11 “iot”指令生成。在大多数机器上,这只是 SIGABRT 的另一个名称。
宏:int SIGTRAP
由机器的断点指令和可能的其他陷阱指令生成。该信号由调试器使用。如果你的程序以某种方式执行了错误的指令,它可能只会看到 SIGTRAP。
宏:int SIGEMT
模拟器陷阱;这是由于某些可能在软件中模拟的未实现指令,或者操作系统未能正确模拟它们造成的。
宏:int SIGSYS
错误的系统调用;也就是说,执行到操作系统的trap指令,但是执行的系统调用的code号无效。
2.2.2. 终止信号
Termination Signals
这些信号都用于告诉进程以一种或另一种方式终止。它们有不同的名称,因为它们的用途略有不同,程序可能希望以不同的方式处理它们。
处理这些信号的原因通常是为了让您的程序可以在实际终止之前进行适当的整理。例如,您可能想要保存状态信息、删除临时文件或恢复以前的终端模式。这样的处理程序应该通过为发生的信号指定默认操作然后重新引发它来结束;这将导致程序以该信号终止,就好像它没有处理程序一样。(请参阅终止进程的处理程序。)
所有这些信号的(显而易见的)默认操作是导致进程终止。
宏:int SIGTERM
SIGTERM 信号是用于导致程序终止的通用信号。与 SIGKILL 不同,此信号可以被阻止、处理和忽略。这是礼貌地要求程序终止的正常方式。
shell 命令 kill 默认生成 SIGTERM。
宏:int SIGINT
当用户键入 INTR 字符(通常是 C-c)时,会发送 SIGINT(“程序中断”)信号。有关 C-c 的终端驱动程序支持的信息,请参阅特殊字符。
宏:int SIGQUIT
SIGQUIT 信号与 SIGINT 类似,不同之处在于它由不同的键(QUIT 字符,通常为 C-\)控制,并在终止进程时产生核心转储,就像程序错误信号一样。您可以将此视为用户“检测到”的程序错误情况。
有关核心转储的信息,请参阅程序错误信号。有关终端驱动程序支持的信息,请参阅特殊字符。
在处理 SIGQUIT 时最好省略某些类型的清理。例如,如果程序创建了临时文件,它应该通过删除临时文件来处理其他终止请求。但 SIGQUIT 最好不要删除它们,以便用户可以结合核心转储检查它们。
宏:int SIGKILL
SIGKILL 信号用于立即终止程序。它不能被处理或忽略,因此总是致命的。也无法阻止此信号。
该信号通常仅由显式请求生成。由于它无法处理,因此您应该仅在最后尝试使用 C-c 或 SIGTERM 等不太激烈的方法后才生成它。如果一个进程没有响应任何其他终止信号,向它发送一个 SIGKILL 信号几乎总是会导致它消失。
事实上,如果 SIGKILL 未能终止一个进程,这本身就构成了一个操作系统错误,您应该报告它。
在程序可能无法继续运行(甚至运行信号处理程序)的某些异常情况下,系统将为进程本身生成 SIGKILL。
宏:int SIGHUP
SIGHUP(“挂断”)信号用于报告用户终端已断开连接,可能是因为网络或电话连接中断。有关这方面的更多信息,请参阅控制模式。
该信号还用于向与该会话关联的作业报告终端上控制进程的终止;此终止有效地将会话中的所有进程与控制终端断开连接。有关详细信息,请参阅终止内部结构。
2.2.3. 报警信号
Alarm Signals
这些信号用于指示计时器到期。有关导致发送这些信号的功能的信息,请参阅设置警报。
这些信号的默认行为是导致程序终止。这个默认值很少有用,但没有其他默认值有用;在任何情况下,大多数使用这些信号的方法都需要处理函数。
宏:int SIGALRM
该信号通常表示测量实际时间或时钟时间的计时器到期。例如,它由警报功能使用。
宏:int SIGVTALRM
该信号通常表示测量当前进程使用的 CPU 时间的计时器到期。该名称是“虚拟时间警报”的缩写。
宏:int SIGPROF
该信号通常表示计时器到期,该计时器测量当前进程使用的 CPU 时间和系统代表该进程花费的 CPU 时间。这样的计时器用于实现代码分析工具,因此该信号的名称。
2.2.4. 异步 I/O 信号
Asynchronous I/O Signals
本节中列出的信号与异步 I/O 设施一起使用。您必须通过调用 fcntl 来启用特定文件描述符来生成这些信号(请参阅中断驱动输入)来采取显式操作。这些信号的默认操作是忽略它们。
宏:int SIGIO
当文件描述符准备好执行输入或输出时发送此信号。
在大多数操作系统上,终端和套接字是唯一可以生成 SIGIO 的文件类型;其他类型的文件,包括普通文件,即使您要求它们也永远不会生成 SIGIO。
在 GNU 系统上,如果您使用 fcntl 成功设置异步模式,则始终会正确生成 SIGIO。
宏:int SIGURG
当“紧急”或带外数据到达套接字时发送此信号。请参阅带外数据。
宏:int SIGPOLL
这是一个 System V 信号名称,或多或少类似于 SIGIO。它只是为了兼容性而定义的。
2.2.5. 作业控制信号
Job Control Signals
这些信号用于支持作业控制。如果您的系统不支持作业控制,则定义了这些宏,但无法引发或处理信号本身。
除非您真正了解作业控制的工作原理,否则您通常应该不理会这些信号。请参阅作业控制。
宏:int SIGCHLD
每当其子进程之一终止或停止时,此信号就会发送到父进程。
此信号的默认操作是忽略它。如果您在有子进程已终止但未通过 wait 或 waitpid 报告其状态(请参阅 进程完成)时为此信号建立处理程序,那么您的新处理程序是否适用于这些进程取决于特定的操作系统。
宏:int SIGCLD
这是 SIGCHLD 的过时名称。
宏:int SIGCONT
您可以向进程发送 SIGCONT 信号以使其继续。这个信号是特殊的——如果它在信号被传递之前停止,它总是让进程继续。默认行为是什么都不做。您不能阻止此信号。你可以设置一个处理程序,但 SIGCONT 总是让进程继续进行。
大多数程序没有理由处理 SIGCONT;他们只是恢复执行,却没有意识到他们曾经被阻止过。您可以使用 SIGCONT 的处理程序使程序在停止和继续时执行一些特殊操作,例如,在等待输入时暂停时重新打印提示。
宏:int SIGSTOP
SIGSTOP 信号停止进程。它不能被处理、忽略或阻止。
宏:int SIGTSTP
SIGTSTP 信号是一个交互式停止信号。与 SIGSTOP 不同,该信号可以被处理和忽略。
如果您有特殊需要在进程停止时将文件或系统表保持在安全状态,您的程序应该处理此信号。例如,关闭回显的程序应该处理 SIGTSTP,以便它们可以在停止之前重新打开回显。
当用户键入 SUSP 字符(通常为 C-z)时,会生成此信号。有关终端驱动程序支持的更多信息,请参阅特殊字符。
宏:int SIGTTIN
进程在作为后台作业运行时无法从用户终端读取。当后台作业中的任何进程尝试从终端读取时,作业中的所有进程都会收到 SIGTTIN 信号。此信号的默认操作是停止进程。有关它如何与终端驱动程序交互的更多信息,请参阅访问控制终端。
宏:int SIGTTOU
这类似于 SIGTTIN,但在后台作业中的进程尝试写入终端或设置其模式时生成。同样,默认操作是停止进程。如果设置了 TOSTOP 输出模式,则仅会为尝试写入终端而生成 SIGTTOU;请参阅输出模式。
当一个进程停止时,除了 SIGKILL 信号和(显然)SIGCONT 信号之外,在它继续之前不能再向它传递任何信号。信号被标记为待处理,但在进程继续之前不会被传递。SIGKILL 信号总是会导致进程终止,并且不能被阻止、处理或忽略。您可以忽略 SIGCONT,但如果进程停止,它总是会导致进程继续进行。向进程发送 SIGCONT 信号会导致该进程的任何未决停止信号被丢弃。同样,当进程接收到停止信号时,进程的任何未决 SIGCONT 信号都会被丢弃。
当孤立进程组(请参阅孤立进程组)中的进程接收到 SIGTSTP、SIGTTIN 或 SIGTTOU 信号并且不处理它时,该进程不会停止。停止进程可能不是很有用,因为没有 shell 程序会注意到它停止并允许用户继续它。相反,会发生什么取决于您使用的操作系统。有些系统可能什么都不做;其他人可能会传递另一个信号,例如 SIGKILL 或 SIGHUP。在 GNU/Hurd 系统上,进程因 SIGKILL 而死;这避免了系统周围许多停止的孤立进程的问题。
2.2.6. 操作错误信号
Operation Error Signals
这些信号用于报告由程序执行的操作产生的各种错误。它们不一定表示程序中的编程错误,而是阻止操作系统调用完成的错误。所有这些的默认操作是导致进程终止。
宏:int SIGPIPE
破损的管道。如果您使用管道或 FIFO,您必须设计您的应用程序,以便一个进程在另一个进程开始写入之前打开管道进行读取。如果读取过程从未启动或意外终止,则写入管道或 FIFO 会引发 SIGPIPE 信号。如果 SIGPIPE 被阻止、处理或忽略,则违规调用将失败并使用 EPIPE 代替。
管道和 FIFO 特殊文件在管道和 FIFO 中有更详细的讨论。
SIGPIPE 的另一个原因是当您尝试输出到未连接的套接字时。请参阅发送数据。
宏:int SIGLOST
资源丢失。当您在 NFS 文件上具有咨询锁定并且 NFS 服务器重新启动并忘记您的锁定时,将生成此信号。
在 GNU/Hurd 系统上,当任何服务器程序意外终止时会生成 SIGLOST。忽略信号通常很好;对死掉的服务器进行的任何调用都只会返回错误。
宏:int SIGXCPU
超出 CPU 时间限制。当进程超过其对 CPU 时间的软资源限制时,将生成此信号。请参阅限制资源使用。
宏:int SIGXFSZ
超出文件大小限制。当进程尝试扩展文件以使其超出进程对文件大小的软资源限制时,将生成此信号。请参阅限制资源使用。
2.2.7. 杂项信号
Miscellaneous Signals
这些信号用于各种其他目的。一般来说,它们不会影响您的程序,除非它明确地将它们用于某些事情。
宏:int SIGUSR1
宏:int SIGUSR2
SIGUSR1 和 SIGUSR2 信号被放在一边,供您以任何方式使用。如果您在接收信号的程序中为它们编写信号处理程序,它们对于简单的进程间通信很有用。
有一个例子展示了 SIGUSR1 和 SIGUSR2 在 Signaling another Process 中的使用。
默认操作是终止进程。
宏:int SIGWINCH
窗口大小变化。在某些系统(包括 GNU)上,当终端驱动程序对屏幕上的行数和列数的记录发生更改时,会产生这种情况。默认操作是忽略它。
如果一个程序进行全屏显示,它应该处理 SIGWINCH。当信号到达时,它应该获取新的屏幕尺寸并相应地重新格式化其显示。
宏:int SIGINFO
信息请求。在 4.4 BSD 和 GNU/Hurd 系统上,当用户在规范模式下键入 STATUS 字符时,该信号被发送到控制终端的前台进程组中的所有进程;请参阅引起信号的字符。
如果进程是进程组的leader,默认的动作是打印一些关于系统的状态信息以及进程在做什么。否则默认是什么都不做。
2.2.8. 信号消息
Signal Messages
我们在上面提到,shell 会打印一条消息,描述终止子进程的信号。打印描述信号的消息的简洁方法是使用函数 strsignal 和 psignal。这些函数使用信号编号来指定要描述的信号类型。信号编号可能来自子进程的终止状态(请参阅进程完成),也可能来自同一进程中的信号处理程序。
函数:char * strsignal (int signum)
Preliminary: | MT-Unsafe race:strsignal locale | AS-Unsafe init i18n corrupt heap | AC-Unsafe init corrupt mem | See POSIX Safety Concepts.
此函数返回一个指向静态分配字符串的指针,该字符串包含描述信号符号的消息。你不应该修改这个字符串的内容;而且,由于它可以在后续调用中重写,因此如果您以后需要引用它,则应该保存它的副本。
这个函数是一个 GNU 扩展,在头文件 string.h 中声明。
函数:void psignal (int signum, const char *message)
Preliminary: | MT-Safe locale | AS-Unsafe corrupt i18n heap | AC-Unsafe lock corrupt mem | See POSIX Safety Concepts.
此函数将描述信号符号的消息打印到标准错误输出流 stderr;请参阅标准流。
如果您使用空指针或空字符串的消息调用 psignal,则 psignal 只会打印与 signum 对应的消息,并添加尾随换行符。
如果您提供非空消息参数,则 psignal 会在其输出前加上此字符串。它添加了一个冒号和一个空格字符,以将消息与对应于 signum 的字符串分开。
这个函数是一个 BSD 特性,在头文件 signal.h 中声明。
函数:const char * sigdescr_np (int signum)
| MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
此函数返回描述信号符号的消息或无效信号编号的 NULL(例如,SIGHUP 的“挂断”)。与 strsignal 不同,返回的描述不被翻译。该消息指向一个静态存储,其生命周期是程序的整个生命周期。
这个函数是一个 GNU 扩展,在头文件 string.h 中声明。
函数:const char * sigabbrev_np (int signum)
| MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
此函数返回描述信号符号的缩写或无效信号编号的 NULL。该消息指向一个静态存储,其生命周期是程序的整个生命周期。
这个函数是一个 GNU 扩展,在头文件 string.h 中声明。
2.3. 指定信号动作
Specifying Signal Actions
更改信号动作的最简单方法是使用信号函数。您可以指定一个内置操作(例如忽略信号),也可以建立一个处理程序。
GNU C 库还实现了更通用的 sigaction 工具。本节介绍了这两种设施,并就何时使用提供了建议。
2.3.1. 基本信号处理
Basic Signal Handling
信号函数提供了一个简单的接口,用于为特定信号建立动作。函数和相关的宏在头文件 signal.h 中声明。
数据类型:sighandler_t
这是信号处理函数的类型。信号处理程序采用一个整数参数指定信号编号,并具有返回类型 void。所以,你应该像这样定义处理函数:
void handler (int signum) {
… }
此数据类型的名称 sighandler_t 是 GNU 扩展。
函数:sighandler_t signal (int signum, sighandler_t action)
Preliminary: | MT-Safe sigintr | AS-Safe | AC-Safe | See POSIX Safety Concepts.
信号函数将动作建立为信号符号的动作。
第一个参数 signum 标识您要控制其行为的信号,并且应该是信号编号。指定信号编号的正确方法是使用符号信号名称之一(请参阅标准信号)——不要使用明确的数字,因为给定类型信号的数字代码可能因操作系统而异。
第二个参数 action 指定用于信号符号的操作。这可以是以下之一:
SIG_DFL
SIG_DFL 指定特定信号的默认操作。标准信号中说明了各种信号的默认操作。
SIG_IGN
SIG_IGN 指定应忽略该信号。
您的程序通常不应忽略代表严重事件或通常用于请求终止的信号。您根本不能忽略 SIGKILL 或 SIGSTOP 信号。您可以忽略 SIGSEGV 之类的程序错误信号,但忽略错误不会使程序继续有意义地执行。忽略 SIGINT、SIGQUIT 和 SIGTSTP 等用户请求是不友好的。
当您不希望在程序的某个部分传递信号时,要做的就是阻止它们,而不是忽略它们。请参阅阻塞信号。
handler
提供程序中处理程序函数的地址,以指定运行此处理程序作为传递信号的方式。
有关定义信号处理程序函数的更多信息,请参阅定义信号处理程序。
如果将信号的操作设置为 SIG_IGN,或者将其设置为 SIG_DFL 并且默认操作是忽略该信号,则该类型的任何未决信号都将被丢弃(即使它们被阻止)。丢弃挂起的信号意味着它们永远不会被传递,即使您随后指定另一个操作并取消阻止这种信号也是如此。
信号函数返回先前对指定符号有效的操作。您可以保存此值并稍后通过再次调用信号来恢复它。
如果信号不能满足请求,它会返回 SIG_ERR。为此函数定义了以下 errno 错误条件:
EINVAL
您指定了无效的签名;或者您试图忽略或提供 SIGKILL 或 SIGSTOP 的处理程序。
兼容性说明:使用信号函数时遇到的一个问题是它在 BSD 和 SVID 系统上具有不同的语义。不同之处在于,在 SVID 系统上,信号处理程序在信号传递后被卸载。在 BSD 系统上,必须显式卸载处理程序。在 GNU C 库中,我们默认使用 BSD 版本。要使用 SVID 版本,您可以使用函数 sysv_signal(见下文)或使用 _XOPEN_SOURCE 功能选择宏(见功能测试宏)。通常,由于兼容性问题,应避免使用这些函数。如果 sigaction 可用,最好使用它,因为结果更可靠。
这是一个设置处理程序以在某些致命信号发生时删除临时文件的简单示例:
#include <signal.h>
void
termination_handler (int signum)
{
struct temp_file *p;
for (p = temp_file_list; p; p = p->next)
unlink (p->name);
}
int
main (void)
{
…
if (signal (SIGINT, termination_handler) == SIG_IGN)
signal (SIGINT, SIG_IGN);
if (signal (SIGHUP, termination_handler) == SIG_IGN)
signal (SIGHUP, SIG_IGN);
if (signal (SIGTERM, termination_handler) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
…
}
请注意,如果先前将给定信号设置为忽略,则此代码会避免更改该设置。这是因为非作业控制 shell 在启动子进程时经常会忽略某些信号,而对子进程而言,尊重这一点很重要。
在本例中,我们不处理 SIGQUIT 或程序错误信号,因为它们旨在为调试提供信息(核心转储),而临时文件可能会提供有用的信息。
函数:sighandler_t sysv_signal (int signum, sighandler_t action)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
sysv_signal 实现了 SVID 系统上的标准信号函数的行为。与 BSD 系统的不同之处在于,处理程序在传递信号后被卸载。
兼容性说明:如上所述,对于信号,应尽可能避免使用此功能。sigaction 是首选方法。
函数:sighandler_t ssignal (int signum, sighandler_t action)
Preliminary: | MT-Safe sigintr | AS-Safe | AC-Safe | See POSIX Safety Concepts.
信号功能与信号做同样的事情;提供它只是为了与 SVID 兼容。
宏:sighandler_t SIG_ERR
该宏的值用作信号的返回值以指示错误。
2.3.2. 高级信号处理
Advanced Signal Handling
sigaction 函数具有与信号相同的基本作用:指定进程应如何处理信号。然而,sigaction 提供了更多的控制权,但代价是更复杂。特别是,sigaction 允许您指定其他标志来控制何时生成信号以及如何调用处理程序。
sigaction 函数在 signal.h 中声明。
数据类型:struct sigaction
sigaction 函数中使用 struct sigaction 类型的结构来指定有关如何处理特定信号的所有信息。该结构至少包含以下成员:
sighandler_t sa_handler
这与信号函数的动作参数的使用方式相同。该值可以是 SIG_DFL、SIG_IGN 或函数指针。请参阅基本信号处理。
s

本文详细解读了信号处理的基础概念,如信号类型、生成原理及传递机制,涵盖了标准信号、信号处理函数和高级操作。重点介绍了如何定义信号处理器、处理程序设计原则,以及阻塞、等待和信号同步等技巧。适合深入理解信号在程序中的角色和优化实践。
最低0.47元/天 解锁文章
2633

被折叠的 条评论
为什么被折叠?



