Lazy loaded image
🥳嵌入式 Linux开发
🚥Signal 信号处理与 IO 复用
Words 2995Read Time 8 min
2024-11-27
2025-2-5
type
date
slug
category
icon
password
 

一、 Linux 信号是什么?

  • 事件发生时对进程的通知机制,提供了异步处理事件的方法,信号是异步事件的经典实例;
  • 软件中断,与硬件中断的相似之处在于能够打断程序当前执行的正常流程;
  • 信号本质上是 int 类型数字编号,下表给出常见信号和默认行为:
信号名
信号值
默认处理动作
发出信号的原因
SIGHUP
1
A
终端挂起或者控制进程终止
SIGINT
2
A
键盘中断Ctrl+c
SIGQUIT
3
C
键盘的退出键被按下
SIGILL
4
C
非法指令
SIGABRT
6
C
由abort(3)发出的退出指令
SIGFPE
8
C
浮点异常
SIGKILL
9
AEF
采用kill -9 进程编号 强制杀死程序。
SIGSEGV
11
C
无效的内存引用
SIGPIPE
13
A
管道破裂,写一个没有读端口的管道。
SIGALRM
14
A
由alarm(2)发出的信号
SIGTERM
15
A
采用“kill 进程编号”或“killall 程序名”通知程序。
SIGUSR1
10
A
用户自定义信号1
SIGUSR2
12
A
用户自定义信号2
SIGCHLD
17
B
子进程结束信号
SIGCONT
18
进程继续(曾被停止的进程)
SIGSTOP
19
DEF
终止进程
SIGTSTP
20
D
控制终端(tty)上按下停止键
SIGTTIN
21
D
后台进程企图从控制终端读
SIGTTOU
22
D
后台进程企图从控制终端写
处理动作一项中的字母含义如下:
A 缺省的动作是终止进程。
B 缺省的动作是忽略此信号,将该信号丢弃,不做处理。
C 缺省的动作是终止进程并进行内核映像转储(core dump),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员 提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
D 缺省的动作是停止进程,进入停止状态的程序还能重新继续,一般是在调试的过程中。
E 信号不能被捕获。
F 信号不能被忽略。

1.1 信号来源

阻塞(进入休眠状态,交出了 CPU 控制权)和非阻塞 IO
  • 阻塞式 I/O 表现:某些文件类型(读管道文件、网络设备文件和字符设备文件),当对文件进行读操作时,如果数据未准备好、文件当前无数据可读,那么读操作可能会使调用者阻塞,直到有数据可读时才会被唤醒。

1.2 信号分类

  • 可靠信号与不可靠信号
    • 早期 UNIX 信号出现两种不可靠情况,第一种信号错误处理,第二种信号丢失
    • Linux改进第一个问题,主要指信号丢失情况。信号值小于SIGRTMIN(34)的信号都是不可靠信号。
    • 34)SIGRTMIN~ 64)SIGRTMAX 之间为可靠信号。可靠信号支持排队,信号发送函数 sigqueue()及信号绑定函数 sigaction()。
  • 实时信号与非实时信号
    • 实时信号与非实时信号其实是从时间关系上进行的分类,与可靠信号与不可靠信号是相互对应的。
    • 实时信号保证了发送的多个信号都能被接收,实时信号是 POSIX 标准的一部分,可用于应用进程。

二、信号有什么用?

  1. 服务程序运行在后台,如果想让中止它,杀掉不是个好办法,因为程序被杀的时候,程序突然死亡,没有安排善后工作。可以通过向服务程序发送一个信号,服务程序收到这个信号后,调用一个函数,在函数中编写善后的代码,程序就可以有计划的退出。
  1. 向服务程序发送0的信号,可以检测程序是否存活。

三、Linux 信号怎么用?

  • 发出信号以及响应信号,信号由“谁”发送、由“谁”处理以及如何处理;
  • 进程在默认情况下对信号的响应方式;
  • 使用进程信号掩码来阻塞信号、以及等待信号等相关概念;

3.1 处理信号

信号由接收进程处理,处理方法有如下几种:
Linux 系统提供了系统调用 signal()和 sigaction()两个函数用于设置信号的处理方式。

3.1.1 signal()函数

sig_t signal(int signum, sig_t handler);
使用该函数需要包含头文件<signal.h>。
参数
描述
signum
指定需设置的信号,建议使用信号名,取代数字编号
handler
1. SIG_IGN 设置忽略信号 2. SIG_DFL 设置系统默认操作 3. 自定义函数
返回值
成功:sig_t 类型函数指针 typedef void (*sig_t)(int); 失败:SIG_ERR, 设置errno
Tip1: SIG_IGN、 SIG_DFL 分别取值如下:
此函数的返回值也是一个 sig_t 类型的函数指针,成功情况下的返回值则是指向在此之前的信号处理函数;如果出错则返回 SIG_ERR,并会设置 errno。
Tiip2:sig_t 函数指针的 int 类型参数指的是,当前触发该函数的信号,可将多个信号绑定到同一个信号处理函数上,此时就可通过此参数来判断当前触发的是哪个信号。
示例:
进程收到 SIGINT 信号后会执行该函数然后运行 printf 打印语句,而非终止程序
 
  • 在终端按下中断符 CTRL + C,系统便会给前台进程组中的每一个进程发送 SIGINT 信号,若在后台程序,则接收不到 SIGINT 信号。
notion image
注意事项
  1. 程序启动时,signal()未运行,执行系统默认操作;
  1. 创建的子进程,继承父进程信号处理方式;
  1. 普通用户只能杀死该用户自己的进程,无权限杀死其它用户的进程;
  1. 后台进程,按下中断符时系统并不会给后台进程发送 SIGINT 信号。

3.1.2 sigaction()函数

设置信号处理方式:
参数
描述
signum
指定需设置的信号,建议使用信号名,取代数字编号
act
struct sigaction 类型指针,描述了信号的处理方式 如果参数 act 为 NULL,则表示无需改变信号当前的处理方式
oldact
如果参数 oldact 不为 NULL,则会将信号之前的处理方式等信息通过参数 oldact 返回出来;如果无意获取此类信息,那么可将该参数设置为 NULL。
返回值
成功返回 0;失败将返回-1,并设置 errno。
sigaction结构体如下:
结构体成员介绍:
  • sa_handler:指定信号处理函数,与 signal()函数的 handler 参数相同。
  • sa_sigaction:也用于指定信号处理函数,这是一个替代的信号处理函数,他提供了更多的参数,可以通过该函数获取到更多信息。
  • sa_mask:信号掩码,避免信号竞态,解决类似中断嵌套问题,下面示例2作进一步说明。
  • sa_flags:控制信号的处理过程
    • SA_NOCLDSTOP:子进程停止和恢复不会收到 SIGCHLD 信号;
    • SA_NOCLDWAIT:子进程终止时不要将其转变为僵尸进程;
    • SA_NODEFER:处理一个信号时不阻塞同种信号,建议不设置,防止引起竞态条件;
    • SA_RESETHAND:信号的处理方式设置为系统默认操作;
    • SA_RESTART:信号处理完成之后将自动重新发起;
    • SA_SIGINFO:如果设置了该标志,则表示使用 sa_sigaction 作为信号处理函数、而不是 sa_handler。
siginfo_t结构体如下:
示例1:
 
示例2:处理某个信号时屏蔽其他特定信号
这个例子中,当处理 SIGUSR1 时,SIGINT 会被屏蔽,确保 SIGINT 不会中断 SIGUSR1 的处理。这样可以有效地解决信号处理中的竞态问题。

3.2 发送信号

3.2.1 kill() 函数

将信号发送给指定的进程或进程组中的每一个进程。
参数
描述
pid
如果 pid 为正,则信号 sig 将发送到 pid 指定的进程。 如果 pid 等于 0,则将 sig 发送到当前进程的进程组中的每个进程。 如果 pid 等于-1,则将 sig 发送到当前进程有权发送信号的每个进程,但进程 1(init)除外。 如果 pid 小于-1,则将 sig 发送到 ID 为-pid 的进程组中的每个进程
sig
参数 sig 指定需要发送的信号,也可设置为 0,如果参数 sig 设置为 0 则表示不发送信号,但任执行错误检查,这通常可用于检查参数 pid 指定的进程是否存在
返回值
成功返回 0;失败将返回-1,并设置 errno。
一个终端运行 02_sigaction
另一个终端运行执行./signal/03_testkill
  • 🔺向一个不存在的进程发送信号,kill()将会返回-1,errno 将被设置为 ESRCH

3.2.2 raise() 函数

进程向自身发送信号,等价于:
kill(getpid(), sig);
Tips:getpid()函数用于获取进程自身的 pid。
参数
描述
sig
参数 sig 指定需要发送的信号,也可设置为 0,如果参数 sig 设置为 0 则表示不发送信号,但任执行错误检查,这通常可用于检查参数 pid 指定的进程是否存在
返回值
成功返回 0;失败将返回非零值
示例:

3.2.3 alarm() 函数

  • 设置一个定时器(闹钟),当定时器定时时间到时,内核会向进程发送 SIGALRM信号。
  • 设置的闹钟为单次闹钟,若想要实现循环触发,可以在 SIGALRM 信号处理函数中再次调用 alarm()函数设置定时器。
参数
描述
seconds
设置定时时间,以秒为单位;如果参数 seconds 等于 0,则表示取消之前设置的 alarm 闹钟。
返回值
返回上一个闹钟剩余值,闹钟重新设置,否则返回0
示例:

3.2.4 pause() 函数

pause()系统调用可以使得进程暂停运行、进入休眠状态,直到进程捕获到一个信号为止,只有执行了信号处理函数并从其返回时,pause()才返回,在这种情况下,pause()返回-1,并且将 errno 设置为 EINTR
 
上一篇
烧写整个系统或更新部分系统
下一篇
进程和进程间通信

Comments
Loading...