信号允许进程终端其他进程,他通知进程系统中发生了一种某种类型的事件,又称为软件中断。
信号传递的步骤
- 发送信号,内核通过更新进程上下文中的某个状态,发送一个信号给目的进程。发送信号有2个原因:1)内核检测到一个事件,比如除零错误(SIGFPE)或子进程终止;2)一个进程调用了kill函数,显示地要求内核发送一个信号给目的进程。一个进程可以发送信号给自己。
- 接收信号,当目的进程收到内核发送的信号,进程可以忽略该信号、终止或者通过执行一个信号处理程序的用户层函数捕获该信号。
常见的信号:
SIGINT
程序终止信号,用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。默认终止进程,不可捕获、阻塞或忽略。
SIGQUIT
与SIGINT类似,但由QUIT字符(通常是Ctrl-\)来控制,进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号。默认终止进程,不可捕获、阻塞或忽略。
SIGKILL
用来立即结束程序的运行,本信号不能被阻塞、处理或忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
默认导致进程退出,不可忽略或捕捉。
SIGUSER1/SIGUSER2
留给用户使用,默认终止,可捕获、阻塞或忽略
SIGALARM
时钟定时信号,计算的是实际的时间或时钟时间,alarm函数使用该信号。默认终止进程,可捕获、阻塞或忽略
SIGCHLD
子进程结束时,父进程会收到这个信号。,如果父进程没有处理这个信号,也没有等待子进程,子进程虽然终止,但是还是会在内核进程表中占有该信号,这是进程变为僵尸进程,应尽量避免。默认忽略,可捕获、阻塞或忽略
信号的发送
1. 从键盘发送
Ctrl+Z:发送SIGTSTP信号
Ctrl+C:发送SIGINT信号
2. 通过kill函数
进程可以通过调用kill
函数发送信号给其他进程(包括他自己)
#python3.6
#os.kill(pid, sig)
import signal
import os
import functools
import time
def main():
pid = os.fork()
if pid > 0: #parent process
_print = functools.partial(print, 'parent:')
_print("I'm parent process!")
time.sleep(5)
_print("Notify SIGKILL to my child!")
os.kill(pid, signal.SIGKILL)
elif pid == 0: #child process
_print = functools.partial(print, 'child:')
_print("I'm child process!")
cnt = 1
while True:
_print(cnt, "time")
time.sleep(1)
cnt += 1
if __name__ == "__main__":
main()
"""result:
parent: I'm parent process!
child: I'm child process!
child: 1 time
child: 2 time
child: 3 time
child: 4 time
child: 5 time
child: 6 time
parent: Notify SIGKILL to my child!
"""
3. 用alarm函数发送信号
进程可以调用alarm
函数给自己发送SIGALARM
信号,使用SIGALARM
信号可以实现一些定时任务。
#python3.6
import signal
import functools
import datetime
def handle_alarm(sig, frame):
print(datetime.datetime.now(), "alarm")
signal.alarm(5)
def main():
signal.signal(signal.SIGALRM, handle_alarm)
signal.alarm(5)
cnt = 0
while True:
pass
if __name__ == "__main__":
main()
"""result:
2018-03-25 13:42:22.380555 alarm
2018-03-25 13:42:27.382360 alarm
2018-03-25 13:42:32.385036 alarm
...
"""
接收信号
当内核从一个议程处理程序返回,准备将控制传递给进程时,会检查进程违背阻塞的待处理信号的集合,如果这个集合为空,呢么内核将控制传递给进程的下一个指令。如果这个集合为非空,那么内核会选择集合中某个信号(通常是最小的k),并且强制进程接收信号,收到这个信号会触发进程的某个行为。一旦完成这个行为,那么默认就默认传递回进程逻辑控制流的下一个指令。
每一个信号都有一个预定义的默认行为,如下:
- 进程终止;
- 进程终止并转向存储器;
- 进程停止直到
SIGCONT
信号重启; - 进程忽略该信号;
进程可以通过signal
来修改和信号相关的默认行为。但SIGSTOP和SIGKILL的默认行为不可更改。
#C
signal(signum, handler)
handler == SIG_IGN:忽略设置的信号
hanldler == SIG_DFL:将信号行为恢复为默认行为
handler == callable:当进程收到该信号就会调用改函数
``
```python
#python3.6
import signal
import functools
import datetime
def handle_sigint(sig, frame):
print("receive signal",sig)
exit(0)
def main():
signal.signal(signal.SIGINT, handle_sigint) #修改Ctrl+c的处理方式
signal.pause() # 进程休眠直到收到信号
if __name__ == "__main__":
main()
"""result:
^Creceive signal 2
"""
显示阻塞和取消阻塞信号
有时候不希望再接收到信号后立即去处理,但也不希望忽略改信号,而是延时一段时间再去处理,就可以通过阻塞信号来实现。当解除阻塞后,被阻塞的信号将会被传递到进程。
信号阻塞和信号忽略是不同的,内核在信号阻塞被解除之前不会传递出去,信号只是本暂停传递;但信号忽略不同,信号已经被传递给进程,只是进程没有处理,并将其丢弃。
#include<signal.h>
int sigpromask(int how, const sigset_t * set, sigset_t * oldset)
import signal
import time
def handle_sigint(sig, frame):
print("receive signal",sig)
exit(0)
def main():
sig_pend = signal.sigpending()
sig_pend.add(signal.SIGINT)
print("mask singal:", signal.SIGINT)
signal.pthread_sigmask(signal.SIG_BLOCK, sig_pend)
signal.signal(signal.SIGINT, handle_sigint)
cnt = 0
while cnt < 10:
print(cnt," time")
time.sleep(1)
cnt += 1
print("unmask singal:", signal.SIGINT)
signal.pthread_sigmask(signal.SIG_UNBLOCK, set([signal.SIGINT]))
if __name__ == "__main__":
main()
"""result:
mask singal: Signals.SIGINT
0 time
1 time
^C2 time #Ctrl+c
3 time
4 time
5 time
6 time
7 time
8 time
9 time
unmask singal: Signals.SIGINT
receive signal 2
"""
参考
- 《深入理解计算机系统》
待补充
- 信号的状态:阻塞信号和未决信号