信號允許進程終端其他進程把沼,他通知進程系統(tǒng)中發(fā)生了一種某種類型的事件详民,又稱為軟件中斷。
信號傳遞的步驟
- 發(fā)送信號涛酗,內(nèi)核通過更新進程上下文中的某個狀態(tài)铡原,發(fā)送一個信號給目的進程。發(fā)送信號有2個原因:1)內(nèi)核檢測到一個事件商叹,比如除零錯誤(SIGFPE)或子進程終止燕刻;2)一個進程調(diào)用了kill函數(shù),顯示地要求內(nèi)核發(fā)送一個信號給目的進程剖笙。一個進程可以發(fā)送信號給自己卵洗。
- 接收信號,當目的進程收到內(nèi)核發(fā)送的信號弥咪,進程可以忽略該信號、終止或者通過執(zhí)行一個信號處理程序的用戶層函數(shù)捕獲該信號聚至。
常見的信號:
SIGINT
程序終止信號酷勺,用戶鍵入INTR字符(通常是Ctrl-C)時發(fā)出,用于通知前臺進程組終止進程扳躬。默認終止進程鸥印,不可捕獲、阻塞或忽略坦报。
SIGQUIT
與SIGINT類似库说,但由QUIT字符(通常是Ctrl-\)來控制,進程在因收到SIGQUIT退出時會產(chǎn)生core文件片择,在這個意義上類似于一個程序錯誤信號潜的。默認終止進程,不可捕獲字管、阻塞或忽略啰挪。
SIGKILL
用來立即結束程序的運行信不,本信號不能被阻塞、處理或忽略亡呵。如果管理員發(fā)現(xiàn)某個進程終止不了抽活,可嘗試發(fā)送這個信號。
默認導致進程退出锰什,不可忽略或捕捉下硕。
SIGUSER1/SIGUSER2
留給用戶使用,默認終止汁胆,可捕獲梭姓、阻塞或忽略
SIGALARM
時鐘定時信號,計算的是實際的時間或時鐘時間嫩码,alarm函數(shù)使用該信號誉尖。默認終止進程,可捕獲铸题、阻塞或忽略
SIGCHLD
子進程結束時铡恕,父進程會收到這個信號。丢间,如果父進程沒有處理這個信號探熔,也沒有等待子進程,子進程雖然終止千劈,但是還是會在內(nèi)核進程表中占有該信號,這是進程變?yōu)榻┦M程牌捷,應盡量避免墙牌。默認忽略,可捕獲暗甥、阻塞或忽略
信號的發(fā)送
1. 從鍵盤發(fā)送
Ctrl+Z:發(fā)送SIGTSTP信號
Ctrl+C:發(fā)送SIGINT信號
2. 通過kill函數(shù)
進程可以通過調(diào)用kill
函數(shù)發(fā)送信號給其他進程(包括他自己)
#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函數(shù)發(fā)送信號
進程可以調(diào)用alarm
函數(shù)給自己發(fā)送SIGALARM
信號喜滨,使用SIGALARM
信號可以實現(xiàn)一些定時任務。
#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
...
"""
接收信號
當內(nèi)核從一個議程處理程序返回撤防,準備將控制傳遞給進程時虽风,會檢查進程違背阻塞的待處理信號的集合,如果這個集合為空寄月,呢么內(nèi)核將控制傳遞給進程的下一個指令辜膝。如果這個集合為非空,那么內(nèi)核會選擇集合中某個信號(通常是最小的k)漾肮,并且強制進程接收信號厂抖,收到這個信號會觸發(fā)進程的某個行為。一旦完成這個行為克懊,那么默認就默認傳遞回進程邏輯控制流的下一個指令忱辅。
每一個信號都有一個預定義的默認行為七蜘,如下:
- 進程終止;
- 進程終止并轉向存儲器墙懂;
- 進程停止直到
SIGCONT
信號重啟橡卤; - 進程忽略該信號;
進程可以通過signal
來修改和信號相關的默認行為损搬。但SIGSTOP和SIGKILL的默認行為不可更改碧库。
#C
signal(signum, handler)
handler == SIG_IGN:忽略設置的信號
hanldler == SIG_DFL:將信號行為恢復為默認行為
handler == callable:當進程收到該信號就會調(diào)用改函數(shù)
``
```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
"""
顯示阻塞和取消阻塞信號
有時候不希望再接收到信號后立即去處理,但也不希望忽略改信號场躯,而是延時一段時間再去處理谈为,就可以通過阻塞信號來實現(xiàn)。當解除阻塞后踢关,被阻塞的信號將會被傳遞到進程伞鲫。
信號阻塞和信號忽略是不同的,內(nèi)核在信號阻塞被解除之前不會傳遞出去签舞,信號只是本暫停傳遞秕脓;但信號忽略不同,信號已經(jīng)被傳遞給進程儒搭,只是進程沒有處理吠架,并將其丟棄。
#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
"""
參考
- 《深入理解計算機系統(tǒng)》
待補充
- 信號的狀態(tài):阻塞信號和未決信號