学习之Linux学习

linux手册翻译——signal(7)

2021-06-11  本文已影响0人  蟹蟹宁

\color{#A00000}{NAME}
signal - linux 信号机制概览

\color{#A00000}{DESCRIPTION}
linux支持两种信号:

信号处理(disposition)

对于当前进程,每一个信号都拥于一个处理(disposition,注意区别于信号处理函数),决定了在信号被传递(delivered)到进程时的行为方式。
下表描述了对于不同信号的默认处理(disposition)方式种类:

disposition 描述
Term 默认行为(action)是终止(terminate)进程
Ign 默认行为是忽略此信号
Term 默认行为是终止进程,并执行dump core(将进程的内存信息写入名为core的磁盘文件中,见core(5))
Stop 默认行为是停止(stop)进程
Cont 默认行为是如果进程当前是停止的(stopped),那么继续(continue)进程

可以使用sigaction(2)signal(2)来修改信号的处理(disposition),前者的可移植性更强。使用这些系统调用,信号传递(delivered)给进程时,可以进行一下操作:

默认情况下,用户自定义的信号处理函数使用的是进程堆栈,也可以使用sigaltstack(2) 切换到自定义的堆栈中。
信号的处置(disposition)是每个进程的属性,在多线程应用中,特定信号的处置(disposition)对于所有线程都是相同的。
通过fork创建的子进程将会继承父进程处置(disposition)的副本,在执行execve时,所有信号的处置将会重置为默认值,但是对于是否阻塞信号不会修改,因为在执行execve时将保留信号掩码

发送一个信号

下列系统调用和库函数将允许调用者发送一个信号:

function 描述
raise(3) 向调用线程(注意是线程)发送一个信号
kill(2) 向指定进程(PID>0)、指定进程组的所有成员(PID<-1或PID=0)或系统上的所有进程(PID = -1)发送信号
pidfd_send_signal(2) 向由 PID 文件描述符标识的进程发送信号
killpg(3) 向指定进程组的所有成员进程发送信号
pthread_kill(3) 向与caller位于同一进程中的指定 POSIX 线程发送信号。
tgkill(2) 向特定进程内的指定线程发送信号 (这是用于实现 pthread_kill(3) 的系统调用。)
sigqueue(3) 向指定进程发送带有伴随数据的实时信号,也可以发送普通信号(标准信号、可靠信号)
等待信号被捕获

以下系统调用会暂停调用线程的执行,直到捕获到信号(或未处理的信号终止进程):

function 描述
pause(2) 暂停(Suspend)执行,直到捕获到任何信号,被掩码的信号是无法捕获的
sigsuspend(2) 临时更改信号掩码(见下文)并暂停(Suspends)执行,直到捕获到未掩码的信号之一
同步接受信号

一般来说信号都是异步接收的,或者说被动接收,也就是程序正常执行,只有信号被传递(delivered)到进程时,才会捕获并执行信号处理函数。但是信号也可以同步接收,也就是阻塞(Block)等待信号,这里类似于前面的“等待信号被捕获”,有两种方式可以实现:

Signal mask and pending signals

信号可能被blocked,这意味着他将不会被delivered到进程,直到信号被解除阻塞(unblocked).从信号产生到被传递的过程称之为信号待处理(pending).(注意,信号首先由一个进程发出,将会首先在内核中生成相关的数据结构,之后信号会被内核添加到接受者进程的pending列表中等待被派发(deliver),信号被派发的时机是接受者进程由内核态返回用户态时检查自己的pending列表,如果存在信号,将检查自己的信号掩码,如果信号掩码屏蔽此信号,那么将阻塞此信号,否则将根据是否配置了信号处理函数来选择信号的处理方式:执行默认处置(disposition)或忽略此信号或被捕获执行信号处理函数)。
进程中的每个线程都有一个独立的信号掩码,它指示线程当前正在阻塞的信号集。 线程可以使用 pthread_sigmask(3) 操作其信号掩码。 在传统的单线程应用程序中,可以使用 sigprocmask(2) 来操作信号掩码。
通过 fork(2) 创建的子级继承其父级信号掩码的副本; 信号掩码在 execve(2) 中保留。
信号可以是针对进程的也可以是针对线程的(process-directed or thread-directed).由kill(2)、sigqueue(3)发出的信号、或者是内核出于硬件异常以外的原因生成的信号是针对进程的;而由tgkill(2)或pthread_kill(3)发出的信号或由于执行触发硬件异常的特定机器语言指令(例如,SIGSEGV 表示无效内存访问,或 SIGFPE 表示数学错误)而生成的信号则是针对线程的。
对于针对进程的信号,可以被传递到进程中的任意一个不阻塞此信号(信号掩码没有阻塞信号)的线程,内核将随机挑选任意一个可用线程来派发。
线程可以通过调用sigpending(2)来获取当前被pending的信号(即被掩码阻塞的信号)集合,集合由进程的pending信号和调用线程的pending信号组成。(注意,进程的pending列表是应该就是主线程的pending列表)
线程之间是共享信号处理函数,但是有自己的独立的pending列表和信号掩码:

共享信号处理函数
私有pending列表和信号掩码
通过 fork(2) 创建的子进程最初有一个空的pending信号集; pending信号将在执行execve(2) 后保留。
信号处理程序的执行

每当从内核模式转换到用户模式执行时(例如,从系统调用返回或线程被调度到 CPU 上执行时),内核将检查此进程/线程是否存在未阻塞的(信号掩码没有阻塞信号)处于pending状态的信号(且进程为此信号建立了信号处理函数,如果没有,那么就执行默认的处置,默认的处置并不会执行用户代码)。如果存在这样的pending信号,将会:

  1. 内核为执行信号处理程序执行必要的准备步骤:
    • 将信号从pending信号集中移除。
    • 若使用sigaction(2)建立信号处理函数时,指定了 SA_ONSTACK flag,并且线程定义了信号堆栈(使用sigaltstack(2)),则将安装(install)堆栈。
    • 各种与信号相关的上下文被保存到在堆栈上创建的特殊帧中。 保存的信息包括:
      • 程序计数器寄存器(即信号处理程序返回时应执行的主程序中的下一条指令的地址);
      • 恢复被打断的程序所需的特定于体系结构的寄存器状态;
      • 线程的当前信号掩码;
      • 线程的备用信号堆栈设置。
        (如果信号处理程序是使用 sigaction(2) SA_SIGINFO 标志安装的,则可以通过信号处理程序的第三个参数指向的 ucontext_t 对象访问上述信息。)
    • 当使用 sigaction(2) 注册处理程序时(手册这里写错了),在 act->sa_mask 中指定的任何信号都被添加到线程的信号掩码中。 被传递的信号也被添加到信号掩码中,除非在注册处理程序时指定了 SA_NODEFER。 这些信号在执行信号的处理程序时候都会被阻塞,这是为了防止类似于信号重入等问题。(*sa_mask 指定在信号处理程序执行期间应该被阻塞的信号掩码(即,添加到调用信号处理程序的线程的信号掩码中)。 此外,触发处理程序的信号将被阻塞,除非使用 SA_NODEFER 标志。见sigaction(2) *)。
  2. 内核为堆栈上的信号处理程序构造一个帧。 内核将线程的程序计数器设置为指向信号处理函数的第一条指令,并将该函数的返回地址配置为指向一段用户空间代码(这段代码被称之为signal trampoline,见sigreturn(2 ))。
  3. 内核将控制权传递回用户空间,在信号处理函数的开始处开始执行。
  4. 当信号处理程序返回时,控制权传递给前面提到的signal trampoline代码。
  5. signal trampoline代码调用 sigreturn(2),这是一个系统调用,它使用在步骤 1 中创建的堆栈帧中的信息将线程恢复到调用信号处理程序之前的状态,包括恢复线程的信号掩码和信号堆栈设置(这里信号掩码是在第一步的第4部分被修改,堆栈是在后面的执行过程被修改,恢复利用的是第一步的第三部分保存的信息)。 完成对 sigreturn(2) 的调用后,内核将控制权转移回用户空间,线程在被信号处理程序中断的地方重新开始执行。

请注意,如果信号处理程序没有返回(例如,使用 siglongjmp(3) 将控制权转移出处理程序,或者处理程序使用 execve(2) 执行新程序),则不会执行最后一步。 特别是,在这种情况下,如果希望解除阻止进入信号处理程序时被阻止的信号,则程序员有责任恢复信号掩码的状态(使用 sigprocmask(2))。 (请注意,siglongjmp(3) 可能会也可能不会恢复信号掩码,具体取决于在对 sigsetjmp(3) 的相应调用中指定的 savesigs 值。)
从内核的角度来看,信号处理程序代码的执行与任何其他用户空间代码的执行完全相同。 也就是说,内核不会记录任何指示线程当前正在信号处理程序内部执行的特殊状态信息。 所有必要的状态信息都保存在用户空间寄存器和用户空间堆栈中。 因此可以调用嵌套信号处理程序的深度仅受用户空间堆栈(和合理的软件设计)的限制。

Standard signals(标准信号,即非实时信号)
Signal Standard Action Comment
SIGABRT P1990 Core abort(3)触发的Abort信号
SIGALRM P1990 Term alarm(2)触发的计时器信号
SIGBUS P2001 Core 总线错误(内存访问错误)
SIGCHLD P1990 Ign 子进程停止或终止
SIGCLD - Ign 同SIGCHLD
SIGCONT P1990 Cont 如果是stoped状态则继续执行
SIGEMT - Term 模拟trap
SIGFPE P1990 Core 浮点异常
SIGHUP P1990 Term 检测到控制终端挂断或控制进程死亡
SIGILL P1990 Core 非法指令
SIGINFO - Term 同SIGPWR
SIGINT P1990 Term 键盘中断
SIGIO - Term 现在可以进行 I/O (4.2BSD)
SIGIOT - Core IOT trap. 同SIGABRT
SIGKILL P1990 Term Kill signal
SIGLOST - Term 文件锁丢失(未使用)
SIGPIPE P1990 Term Broken pipe:在没有读取器的情况下写入管道;见pipe(7)
SIGPOLL P2001 Term 可轮询事件(Sys V);同 SIGIO
SIGPROF P2001 Term 分析计时器已过期
SIGPWR - Term 电源故障(系统 V)
SIGQUIT P1990 Core 退出键盘
SIGSEGV P1990 Core 无效的内存引用
SIGSTKFLT - Term 协处理器上的堆栈错误(未使用)
SIGSTOP P1990 Stop Stop process
SIGTSTP P1990 Stop 在终端输入停止
SIGSYS P2001 Core 错误的系统调用(SVr4); 另见seccomp(2)
SIGTERM P1990 Term 终止信号
SIGTRAP P2001 Core Trace/breakpoint trap
SIGTTIN P1990 Stop 后台进程的终端输入
SIGTTOU P1990 Stop 后台进程的终端输出
SIGUNUSED - Core 同SIGSYS
SIGURG P2001 Ign 紧急情况 on socket (4.2BSD)
SIGUSR1 P1990 Term User-defined signal 1
SIGUSR2 P1990 Term User-defined signal 2
SIGVTALRM P2001 Term 虚拟闹钟 (4.2BSD)
SIGXCPU P2001 Core 超出 CPU 时间限制 (4.2BSD); 见setrlimit(2)
SIGXFSZ P2001 Core 超出文件大小限制(4.2BSD); 见setrlimit(2)
SIGWINCH - Ign 窗口大小调整信号(4.3BSD, Sun)

信号 SIGKILL 和 SIGSTOP 不能被捕获(caught)、阻塞(blocked)或忽略(ignored)。
直到并包括 Linux 2.2,SIGSYS、SIGXCPU、SIGXFSZ 和(在 SPARC 和 MIPS 以外的体系结构上)SIGBUS 的默认行为是终止进程(没有核心转储)。 (在某些其他 UNIX 系统上,SIGXCPU 和 SIGXFSZ 的默认操作是在没有核心转储的情况下终止进程。)Linux 2.4 符合 POSIX.1-2001 对这些信号的要求,使用核心转储终止进程。
SIGEMT 未在 POSIX.1-2001 中指定,但仍然出现在大多数其他 UNIX 系统上,其默认操作通常是使用核心转储终止进程。
SIGPWR(在 POSIX.1-2001 中未指定)通常在出现它的其他 UNIX 系统上默认被忽略。
SIGIO(在 POSIX.1-2001 中未指定)在其他几个 UNIX 系统上默认被忽略。

标准信号的排队和传递语义

如果一个进程有多个标准信号挂起,则信号的传递顺序是未指定的。
标准信号不会排队,即如果一个标准信号在阻塞的情况下产生了多个,那么仅仅只有一个信号被标记为pending,且在unblocked后仅会deliver一次。同时, 在标准信号已经挂起的情况下,后续到达的同一种信号不会覆盖已经挂起的信号的 siginfo_t 结构(见 sigaction(2)。也就是说进程只会接收到第一个到达的信号。

标准信号的信号编号

下表给出了每个信号的数值。 如表所示,许多信号在不同架构上具有不同的数值。 每行中的第一列为 x86、ARM 和大多数其他架构上的信号编号; 第二列为 Alpha 和 SPARC; 第三列是MIPS; 最后一列是 PARISC。 破折号 (-) 表示相应架构上不存在信号。

Signal x86/ARM/most others Alpha/SPARC MIPS PARISC Notes
SIGHUP 1 1 1 1
SIGINT 2 2 2 2
SIGQUIT 3 3 3 3
SIGILL 4 4 4 4
SIGTRAP 5 5 5 5
SIGABRT 6 6 6 6
SIGIOT 6 6 6 6
SIGBUS 7 10 10 10
SIGEMT - 7 7 -
SIGFPE 8 8 8 8
SIGKILL 9 9 9 9
SIGUSR1 10 30 16 16
SIGSEGV 11 11 11 11
SIGUSR2 12 31 17 17
SIGPIPE 13 13 13 13
SIGALRM 14 14 14 14
SIGTERM 15 15 15 15
SIGSTKFLT 16 - - 7
SIGCHLD 17 20 18 18
SIGCLD - - 18 -
SIGCONT 18 19 25 26
SIGSTOP 19 17 23 24
SIGTSTP 20 18 24 25
SIGTTIN 21 21 26 27
SIGTTOU 22 22 27 28
SIGURG 23 16 21 29
SIGXCPU 24 24 30 12
SIGXFSZ 25 25 31 30
SIGVTALRM 26 26 28 20
SIGPROF 27 27 29 21
SIGWINCH 28 28 20 23
SIGIO 29 23 22 22
SIGPOLL Same as SIGIO
SIGPWR 30 29/- 19 19
SIGINFO - 29/- - -
SIGLOST - -/29 - -
SIGSYS 31 12 12 31
SIGUNUSED 31 - - 31

请注意以下事项:

实时信号

从 2.2 版开始,Linux 支持最初在 POSIX.1b 实时扩展中定义的实时信号(现在包含在 POSIX.1-2001 中)。 支持的实时信号范围由宏 SIGRTMIN 和 SIGRTMAX 定义。 POSIX.1-2001 要求一个实现至少支持 _POSIX_RTSIG_MAX (8) 个实时信号。
Linux 内核支持 33 种不同的实时信号,编号为 32 到 64。 但是,glibc POSIX 线程实现在内部使用两个(对于 NPTL)或三个(对于 LinuxThreads)实时信号(请参阅 pthreads(7)) , 并适当调整 SIGRTMIN 的值(到 34 或 35)。 因为可用实时信号的范围根据 glibc 线程实现而变化(并且这种变化可能发生在运行时根据可用的内核和 glibc),实际上实时信号的范围因 UNIX 系统而异,程序应该 永远不要使用硬编码数字引用实时信号,而应始终使用符号 SIGRTMIN+n 引用实时信号,并包括适当的(运行时)检查以确保 SIGRTMIN+n 不超过 SIGRTMAX。
与标准信号不同,实时信号没有预定义的含义:整个实时信号集可用于应用程序定义的目的。
未处理的实时信号的默认操作是终止接收过程。
实时信号的区别如下:

  1. 实时信号的多个实例可以排队。 相比之下,如果一个标准信号的多个实例在该信号当前被阻塞时被传递,那么只有一个实例(首先到达的信号)被排队。
  2. 如果使用 sigqueue(3) 发送信号,则可以随信号一起发送伴随值(整数或指针)。 如果接收进程使用 sigaction(2) 的 SA_SIGINFO 标志为这个信号建立一个处理程序,那么它可以通过作为第二个参数传递给处理程序的 siginfo_t 结构的 si_value 字段获得这个数据。 此外,该结构体的 si_pid 和 si_uid 字段可用于获取发送信号的进程的 PID 和真实用户 ID。
  3. 实时信号以有保证的顺序传送。 多个相同类型的实时信号按照发送顺序进行传递。 如果不同的实时信号被发送到一个进程,它们会从编号最小的信号开始传递。 (即,编号低的信号具有最高优先级。)相比之下,如果多个标准信号等待一个进程,则它们的传递顺序是未指定的。

如果一个进程的标准信号和实时信号都挂起,POSIX 将不指定哪个先传递。 在这种情况下,Linux 与许多其他实现一样,优先考虑标准信号。
根据 POSIX,一个实现应该允许至少 _POSIX_SIGQUEUE_MAX (32) 个实时信号排队到一个进程。 但是,Linux 的处理方式不同。 在内核 2.6.7(包括 2.6.7)中,Linux 对所有进程的排队实时信号的数量施加了系统范围的限制。 可以通过 /proc/sys/kernel/rtsig-max 文件查看和(使用特权)更改此限制。 相关文件 /proc/sys/kernel/rtsig-nr 可用于查明当前排队的实时信号数量。 在 Linux 2.6.8 中,这些 /proc 接口被 RLIMIT_SIGPENDING 资源限制所取代,它为排队信号指定了每个用户的限制; 有关更多详细信息,请参阅 setrlimit(2)。
添加实时信号需要将信号集结构 (sigset_t) 从 32 位扩展到 64 位。 因此,各种系统调用被支持更大信号集的新系统调用所取代。 新旧系统调用如下:

Linux 2.0 and earlier Linux 2.2 and later
sigaction(2) rt_sigaction(2)
sigpending(2) rt_sigpending(2)
sigprocmask(2) rt_sigprocmask(2)
sigreturn(2) rt_sigreturn(2)
sigsuspend(2) rt_sigsuspend(2)
sigtimedwait(2) rt_sigtimedwait(2)
信号处理程序中断系统调用和库函数

如果在系统调用或库函数调用被阻塞时调用信号处理程序,则:

发生这两种行为中的哪一种取决于接口以及是否使用 SA_RESTART 标志建立信号处理程序(请参阅 sigaction(2))。 详细信息因 UNIX 系统而异; 下面是 Linux 的详细信息.
如果对以下接口之一的阻塞调用被信号处理程序中断,则判断是否使用了SA_RESTART 标志:

  1. read(2)、readv(2)、write(2)、writev(2) 和 ioctl(2) 在“慢”设备上调用。 “慢”设备是指 I/O 调用可能会无限期阻塞的设备,例如终端、管道或套接字。 如果慢速设备上的 I/O 调用在被信号处理程序中断时已经传输了一些数据,则该调用将返回成功状态(通常为传输的字节数)。 请注意,根据此定义,(本地)磁盘不是慢速设备; 磁盘设备上的 I/O 操作不会被信号中断。

  2. open(2),如果它可以阻塞(例如,在打开 FIFO 时;请参阅 fifo(7))

  3. wait(2), wait3(2), wait4(2), waitid(2), and waitpid(2).

  4. 套接字接口:accept(2)、connect(2)、recv(2)、recvfrom(2)、recvmmsg(2)、recvmsg(2)、send(2)、sendto(2) 和 sendmsg(2),除非 已在套接字上设置超时(见下文)

  5. 文件锁定接口:flock(2) 和 fcntl(2) 的 F_SETLKW 和 F_OFD_SETLKW 操作

  6. POSIX 消息队列接口:mq_receive(3)、mq_timedreceive(3)、mq_send(3) 和 mq_timedsend(3)

  7. futex(2) FUTEX_WAIT (since Linux 2.6.22; beforehand, always failed with EINTR).

  8. getrandom(2).

  9. pthread_mutex_lock(3), pthread_cond_wait(3), and related APIs.

  10. futex(2) FUTEX_WAIT_BITSET.

  11. POSIX semaphore interfaces: sem_wait(3) and sem_timedwait(3)(since Linux 2.6.22; beforehand, always failed with EINTR).

  12. read(2) from an inotify(7) file descriptor (since Linux 3.8; beforehand, always failed with EINTR).

以下接口在被信号处理程序中断后永远不会重新启动,无论是否使用 SA_RESTART; 当被信号处理程序中断时,它们总是以错误 EINTR 失败:

  1. "Input" socket interfaces, when a timeout (SO_RCVTIMEO) has
    been set on the socket using setsockopt(2): accept(2), recv(2),
    recvfrom(2), recvmmsg(2) (also with a non-NULL timeout
    argument), and recvmsg(2).

  2. "Output" socket interfaces, when a timeout (SO_RCVTIMEO) has
    been set on the socket using setsockopt(2): connect(2),
    send(2), sendto(2), and sendmsg(2).

  3. Interfaces used to wait for signals: pause(2), sigsuspend(2),
    sigtimedwait(2), and sigwaitinfo(2).

  4. File descriptor multiplexing interfaces: epoll_wait(2),
    epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).

  5. System V IPC interfaces: msgrcv(2), msgsnd(2), semop(2), and
    semtimedop(2).

  6. Sleep interfaces: clock_nanosleep(2), nanosleep(2), and usleep(3).

  7. io_getevents(2). The sleep(3) function is also never restarted if interrupted by a handler but gives a success return: the number of seconds remaining to sleep.

如果被处理程序中断, sleep(3) 函数也永远不会重新启动,但会返回成功:睡眠剩余的秒数。

通过停止信号中断系统调用和库函数

在 Linux 上,即使没有信号处理程序,在进程被停止信号之一停止然后通过 SIGCONT 恢复,某些阻塞接口可能会失败并显示错误 EINTR。 这种行为不受 POSIX.1 的认可,也不会发生在其他系统上。
存在上述行为的Linux 接口包括:

  1. “Input”套接字接口,当使用 setockopt(2) 在套接字上设置超时 (SO_RCVTIMEO) 时:accept(2)、recv(2)、recvfrom(2)、recvmmsg(2)(也带有非 NULL timeout 参数)和 recvmsg(2)。

  2. “Output”套接字接口,当使用 setockopt(2) 在套接字上设置超时 (SO_RCVTIMEO) 时:connect(2)、send(2)、sendto(2) 和 sendmsg(2),如果发送超时( SO_SNDTIMEO) 已设置。

  3. epoll_pwait(2).

  4. semop(2), semtimedop(2).

  5. sigtimedwait(2), sigwaitinfo(2).

  6. Linux 3.7 and earlier: read(2) from an inotify(7) file descriptor

  7. Linux 2.6.21 and earlier: futex(2) FUTEX_WAIT, sem_timedwait(3), sem_wait(3).

  8. Linux 2.6.8 and earlier: msgrcv(2), msgsnd(2).

  9. Linux 2.4 and earlier: nanosleep(2).

\color{#A00000}{NOTES}
有关异步信号安全函数的讨论,请参阅signal-safety(7).
/proc/[pid]/task/[tid]/status 文件包含显示线程正在阻塞 (SigBlk)、捕获 (SigCgt) 或忽略 (SigIgn) 的信号的各种字段。 (被捕获或忽略的信号集在进程中的所有线程中都是相同的。)其他字段显示指向线程 (SigPnd) 的挂起信号集以及指向的挂起信号集 整个过程(ShdPnd)。 /proc/[pid]/status 中的相应字段显示了主线程的信息。 有关更多详细信息,请参阅 proc(5)。

上一篇下一篇

猜你喜欢

热点阅读