-
Notifications
You must be signed in to change notification settings - Fork 0
/
第 12.00.01.02 章、os_signal 包
204 lines (141 loc) · 11.3 KB
/
第 12.00.01.02 章、os_signal 包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
总述
signal 包实现了处理输入信号的能力
信号主要用于 Unix-like 的系统上
对于在 Windows 与 Plan9 的用户,见下方
信号的种类
SIGKILL 和 SIGSTOP 并不能被程序捕获,所以也不能被这个包处理
同步信号(Synchronous signal)由在程序运行执行中的产生的错误所触发:SIGBUS/SIGFPE/SIGSEGV
这些仅是进过考虑的,在程序执行中的同步信号,与使用 os.Process.Kill 或 kill 程序或其它类似机制的不同
大体来说,除了下面所讨论的,Go 程序将一个同步信号转换为一个 运行时 panic
剩余信号就是异步信号(asynchronous signal)了
它们并非由程序错误所触发,而是从内核或其它程序所送出
在异步信号之中,
SIGHUP 信号在失去它的控制终端时发出
SIGINT 信号在控制终端的用户按下了中断字符(interrupt character)—— 默认为 ^C ——时发出
SIGQUIT 信号在控制端的用户按下了退出字符(quit character)—— 默认为 ^\ ——时触发
大体来说,你可以简单按下 ^C 来导致程序退出,也可以按下 ^\ 来导致带栈 dump 的程序退出
Go 程序中的信号的默认行为
默认来说,同步信号被转换为运行时 panic
SIGHUP SIGINT SIGTERM 信号导致程序退出
SIGQUIT SIGILL SIGTRAP SIGABRT SIGSTKFLT SIGEMT SIGSYS 信号导致程序带栈 dump 退出
SIGSTP SIGTIN SIGTOU 信号获得系统默认的行为(这些信号被 shell 用来做工作控制(job control))
SIGPROF 信号直接被 Go 的运行时所处理,来实现 runtime.CPUProfile
其它的信号将被捕捉,但并不会执行动作
若 Go 程序在 忽略 SIGHUP 或 忽略 SIGINT 的情况下启动(信号处理器设置为 SIG_IGN),它们会保持忽视
若 Go 程序在非空信号遮罩(non-empty signal mask)的情况下启动,它们通常会被兑现
但是,有些信号是明确地不会被阻挡的:同步信号、SIGILL、SIGTRAP、SIGSTKFLT、SIGCHLD、SIGPROF,以及
在 GUN/Linux 上,信号 32(SIGCANCEL)以及信号 33(SIGSETXID)(SIGCANCEL 和 SIGSETXID 被 glibc 内部使用)
被 os.Exec 或者被 os/exec 启动的子进程,将继承修改后的信号遮罩
修改 Go 程序中信号的行为
这个包中的函数允许一个程序修改 Go 程序处理信号的方式
Notify 函数禁用了给定的非同步信号集的默认行为,并将它们分发至一个或多个注册了的 channel 中
准确地说,它作用于 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
它也作用于工作控制信号 SIGTSTP SIGTTIN SIGTTOU,这种情况下系统的默认行为不会发生
它也作用于一些本不会产生动作的信号:
SIGUSR1 SIGUSR2 SIGPIPE SIGALRM SIGCHLD SIGCONT SIGURG SIGXCPU SIGXFSZ
SIGVTALRM SIGWINCH SIGIO SIGPWR SIGSYS SIGINFO SIGTHR SIGWAITING SIGLWP
SIGFREEZE SIGTHAW SIGLOST SIGXRES SIGJVM1 SIGJVM2
以及任何系统使用的实时信号
注意,并非所有的信号在任何一个系统上都会有
若程序在 忽略 SIGHUP 或 忽略 SIGINT 的情况下启动,
同时 Notify 函数被其中任何一个调用了,
那么一个对应的信号处理器会被建立,它将不会再会被忽略
若在其后, Reset 函数或者 Ignore 函数在那个信号上调用,
或 Stop 函数在所有与被 Notify 传递的该信号的 channel 上被调用,
该信号将再一次被忽略
Reset 函数会将信号重置为系统默认行为,而 Ignore 函数会导致系统完全忽略该信号
若程序在非空信号遮罩下启动,一些信号将被明确的不被阻塞,正如上文所述
若 Notify 在被阻塞的的信号上调用,它将会变为非阻塞的
若在其后, Reset 在该信号上调用,
Stop 函数在所有与被 Notify 传递的该信号的 channel 上被调用,
这个信号会再一次被阻塞
SIGPIPE
当 Go 程序写入一个破碎的管道时,内核会产生一个 SIGPIPE 信号
若程序并没有调用 Notify 来接受 SIGPIPE 信号,那么其行为与文件描述符的编号相关
在文件描述符 1 或 2(标准输出和标准错误)上,向破碎的管道写入时,会导致程序以 SIGPIPE 信号退出
在其它的文件描述符上,向破碎的管道写入时,不会对 SIGPIPE 产生动作,写入将失败,并产生一个 EPIPE 错误
若程序已经调用 Notify 来接受 SIGPIPE 信号,那么文件描述符的编号将不再有用
SIGPIPE 将被发送至 Notify 的 channel 中,同时写入将失败,并产生一个 EPIPE 错误
这意味着,默认情况下,命令行程序的行为会像典型的 Unix 命令行程序,
但其它程序不会因为 写入关闭的网络通讯而产生的 SIGPIPE 而崩溃
使用了 Go 程序的 cgo 和 SWIG
在 包含非 Go 代码的 Go 程序中,典型的是通过 cgo 或 SWIG 调用的 C/C++ 代码,Go 的启动代码通常先运行
它会先于非 Go 启动代码运行,配置 Go 运行时所期待的信号处理器
若 非 Go 启动的代码希望安装其自有的信号处理,它必须执行特定的步骤来保证 Go 工作正常
这部分的文档极了了这些步骤,以及使用非 Go 代码能够使 Go 程序的信号处理的整体影响
在极少数情况下,非 Go 代码可能会在 Go 代码前运行,这些情况将下,下面的段落也会使用
若被 Go 代码调用的非 Go 代码,并未修改任何信号处理器或者遮罩,那么行为和纯 Go 程序一样
若 非 Go 代码安装了任何的信号处理器,它在使用 sigaction 函数时,必须添加 SA_ONSTACK 标示
若并未做此有可能会导致程序在系统接收信号时崩溃
Go 程序例行会运行一个有限的栈,于是也会设置一个另外的信号栈
同时,Go 的标准库也希望任何信号处理器都使用 SA_RESTART 标示
若并未做此有可能会导致一些库调用返回 “interrupt system call” 错误
若非 Go 代码为任何同步信号(SIGBUS/SIGFPE/SIGSEGV)安装了一个信号处理器,那么它应该记录现存的 Go 信号处理器
若这些信号在执行 Go 代码时产生,它应该调用 Go 的信号处理器(执行 Go 代码时,是否出现了信号,可以通过查看传入信号处理器的程序计数器(PC)来判断)
若非如此,则一些 Go 的运行时 panic 肯不会如期触发
若非 Go 代码为任何非同步信号暗黄了一个信号处理器,则它可以调用 Go 的信号处理器,或者也可以不使用
自然地,若它不调用 Go 的信号处理器,那么上述描述的 Go 的行为就不会出现
在实际中,这可能会与 SIGPROF 信号产生问题
非 Go 代码不应该改变任何由 Go 运行时创建的线程的信号遮罩
若非 Go 代码启动了自己的线程,则它可以设置自己想要的信号遮罩
若非 Go 代码启动了一个新线程,修改了信号遮罩,并在该线程中调用了 Go 的函数
那么 Go 运行时将自动解阻塞一些特定的信号:
异步信号 SIGILL/SIGTRAP/SIGSTKFLT/SIGCHLD/SIGPROF/SIGCANCEL/SIGSETXID
当 Go 函数返回时,非 Go 信号遮罩将被还原
若 Go 的信号处理器在一个非 Go 线程,且并未运行 Go 代码的线程上被调用,
处理器一般会将信号传递给非 Go 代码,如下所示
若信号是 SIGPROF,Go 处理器不会执行任何操作
其它情况下,Go 处理器都会移除它自己,解阻塞这个信号,重新发出它,并调用任何非 Go 处理器,或者默认的系统处理器
若程序并非退出,Go 处理器将会重新安装自身,并接续执行程序
非 Go 程序调用 Go 代码
当 Go 代码在构建时,使用了如 -buildmode=c-shared 这样的选项,它将作为现有的非 Go 程序的一部分运行
在 Go 代码启动时,非 Go 代码可能已经安装了信号处理器(这同样会由于使用 cgo 或 SWIG 时非正常情况导致;这种情况也在此描述)
对于 -buildmode=c-shared 的 Go 运行时,将会在共享库装载时,初始化信号
若 Go 运行时查看到了一个已经存在的 SIGCANCEL 或者 SIGSETXID 信号处理器(它们仅在 GNU/Linux 上使用)
它会打开 SA_ONSTACK 标示,其他情况会保持信号处理器
对于同步信号以及 SIGPIPE,Go 运行时将安装一个信号处理器
它将保存任何先存的信号处理器
若执行非 Go 代码时,一个信号到达,Go 的运行时将会调用已经存在的信号处理器,而非 Go 的信号处理器
对于使用 -buildmode=c-achive 或 -buildmode=c-shared 构建的 Go 代码,默认将不会安装任何其它的信号处理器
若已经存在信号处理器,Go 的运行时将打开 SA_ONSTACK 标示,否则保持信号处理器
若 Notify 函数在一个非同步信号上被调用,一个与该信号对应的 Go 信号处理器将被安装
若在此之后,Reset 在该信号上被调用,那么该信号的原始处理将被重安装,若有,则恢复任何非 Go 信号处理器
对于并未使用 -buildmode=c-achive 或 -buildmode=c-shared 构建的 Go 代码,将会对上述非同步信号安装信号处理器,并保存任何现存的信号处理器
若一个信号发送至非 Go 线程,它将会如上文描述一样行动,除非有已经存在的非 Go 信号处理器,将在发出信号前,先安装那个处理器
Windows
在 Windows 上,一个 ^C 或者 ^BREAK 通常会导致程序退出
若 Notify 在 os.Interrupt 上被调用, ^C 或 ^BREAK 将导致 os.Interrupt 在 channel 上发送,而程序并不会退出
若 Reset 被调用,或者 Stop 在所有传入 Notify 的 channel 上被调用,那么默认的行为将恢复
Plan9
在 Plan 9,信号有类型 syscall.Note,是一个字符串
与 syscall.Note 调用 Notify 将会导致该值在 channel 上发送,且该字符串作为 note 被发表
Ignore(sig ...os.Signal)
本函数导致提供的信号被忽略
若它们被程序接收,什么也不会发生
本函数会取消任何先前在指定的信号上调用 Notify 所产生的任何影响
若没有指定信号,所有传入信号都将被忽略
Notify(c chan<- os.Signal, sig ...os.Signal)
本函数导致本包将传入信号重导向至 c 中
若没有指定信号,则所有的传入信号将全部被传入 c 中
否则只有指定的信号才会
本包将不会阻塞信号传入 c:调用者必须保证 c 有充足的缓存空间来保证期望的信号频率
对于用于仅用于提醒一个信号值的 channel c,缓冲值为 1 就够了
对同一个 channel 多次调用 Notify 是允许的:每次调用都扩展了传入该 channel 的信号集
仅有的从集合中移除该信号的方法就是调用 Stop 函数
对同一个信号多次在不同 channel 上调用 Notify 也是允许的:每个 channel 都独立获得传入信号的拷贝
EG.
// 建立一个发送信号通知的 channel
// 我们必须使用一个带缓冲的 channel
// 否则我们就要冒在信号发送时,我们没有准备好接受它而导致信号丢失的危险
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// 阻塞直到信号被接收
s := <-c
fmt.Println("Got signal:", s)
Reset(sig ...os.Signal)
本函数取消任何指定的,先前调用 Notify 的信号所产生的影响
若没有指定信号,则所有信号处理器将被重置
Stop(c chan<- os.Signal)
本函数导致本包停止重导向传入信号至 c
它取消任何使用 c 来调用 Notify 的效果
当 Stop 返回时,它保证 c 将不再会接收任何多的信号