天天看點

12@程序信号處理

文章目錄

  • ​​信号控制​​
  • ​​一、 信号說明​​
  • ​​二 、捕捉信号​​
  • ​​例1:​​
  • ​​例2:​​
  • ​​三、 關于HUP信号​​
  • ​​1、 nohup指令​​
  • ​​2、setsid指令​​
  • ​​3、 在子shell中送出任務​​
  • ​​4、screen指令​​
  • ​​5、遠端示範​​
  • ​​四、僵屍程序與孤兒程序​​
  • ​​1、僵屍程序​​
  • ​​2、孤兒程序​​

信号控制

一、 信号說明

在腳本執行過程中, 可能會被一些鍵盤操作快捷方式所打斷, 影響腳本運作
# HUP(1):  1、挂起信号 2、往往可以讓程序重新加載配置
本信号在使用者終端連接配接(正常或非正常)結束時發出, 通常是在終端的控制程序結束時, 通知同一session内的各個作業, 這時它們與控制終端不再關聯。

登入Linux時,系統會配置設定給登入使用者一個終端(Session)。在這個終端運作的所有程式,包括前台程序組和背景程序組,一般都 屬于這個 Session。當使用者退出Linux登入時,前台程序組和背景有對終端輸出的程序将會收到SIGHUP信号。這個信号的預設操作為終止程序,是以前台進 程組和背景有終端輸出的程序就會中止。不過,可以捕獲這個信号,比如wget能捕獲SIGHUP信号,并忽略它,這樣就算退出了Linux登入,wget也 能繼續下載下傳。

此外,對于與終端脫離關系的守護程序,這個信号用于通知它重新讀取配置檔案。

# INT(2):  中斷, 通常因為按下ctrl+c而産生的信号,用于通知前台程序組終止程序。
# QUIT(3): 退出,和SIGINT類似, 但由QUIT字元(通常是Ctrl-\)來控制. 程序在因收到SIGQUIT退出時會産生core檔案, 在這個意義上類似于一個程式錯誤信号。

# TSTP(20): 停止進行運作,通常因為按下ctrl+z而産生的信号

# KILL (9)
用來立即結束程式的運作. 本信号不能被阻塞、處理和忽略。如果管理者發現某個程序終止不了,可嘗試發送這個信号。
# TERM(15): 
終止,是不帶參數時kill預設發送的信号,預設是殺死程序,與SIGKILL不同的是該信号可以被阻塞和處理。通常用TERM信号來要求程式自己正常退出,如果程序終止不了,我們才會嘗試SIGKILL。
  
# ===============了解===============
# ABRT(6): 中止, 通常因某些嚴重錯誤産生的引号   

# SIGCHLD 
子程序結束時, 父程序會收到這個信号。

如果父程序沒有處理這個信号,也沒有等待(wait)子程序,子程序雖然終止,但是還會在核心程序表中占有表項,這時的子程序稱為僵屍 程序。這種情 況我們應該避免(父程序或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子程序,或者父程序先終止,這時子程序的終止自動由init程序 來接管)。

# 更多詳見:man 7 signal      

二 、捕捉信号

我們可以用trap指令捕捉信号(trap指令并不能捕獲所有信号,但是常用信号HUP、INT、QUIT、TERM都是可以捕獲的),執行我們規定的操作
# 操作1:捕捉信号、執行引号内的操作
trap "echo 已經識别中斷信号:ctrl+c" INT

# 示例2:捕捉信号、不執行任何操作
trap "" INT  

# 示例3:也可以同時捕獲多個信号
trap ""      

例1:

[root@egon test]# cat m.sh 
#!/bin/bash

trap "echo 已經識别到中斷信号:ctrl+c" INT
trap 'echo 已經識别到中斷信号:ctrl+\\' QUIT
trap 'echo 已經識别到中斷信号:ctrl+z' TSTP

read -p "請輸入你的名字: " name
echo "你的名字是:$name"
[root@egon test]# chmod +x m.sh 
[root@egon test]# ./m.sh 
請輸入你的名字: ^C已經識别到中斷信号:ctrl+c
^C已經識别到中斷信号:ctrl+c
^C已經識别到中斷信号:ctrl+c
^\已經識别到中斷信号:ctrl+\
^\已經識别到中斷信号:ctrl+\
^Z已經識别到中斷信号:ctrl+z
^Z已經識别到中斷信号:ctrl+z
egon
你的名字是:egon
[root@egon test]#      

例2:

#!/bin/bash
trap "" HUP INT QUIT TSTP  # kill -HUP 目前程序的pid,也無法終止其運作

clear
i=0
while true
do
    [ $i == 0 ] && i=1 || i=0
    if [ $i == 0 ];then
        echo -e "\033[31m 紅燈亮 \033[0m"
    else
        echo -e "\033[32m 綠燈亮 \033[0m"
    fi
    sleep 1      
#注:  可以使用kill -9終止以上程序   pkill終止服務      

三、 關于HUP信号

要了解Linux的HUP信号,需要從hangup說起
在 Unix 的早期版本中,每個終端都會通過 modem 和系統通訊。
當使用者 logout 時,modem 就會挂斷(hang up)電話。 
同理,當 modem 斷開連接配接時,就會給終端發送 hangup 信号來通知其關閉所有子程序。      
綜上所述,當使用者登出(logout)或者網絡斷開或者終端關閉(注意注意注意,一定是終端整體關閉,不是單純的exit)時,終端都會收到Linux HUP信号(hangup)信号,然後終端在結束前會關閉其所有子程序。

如果我們想讓我們的程序在背景一直運作,不要因為使用者登出(logout)或者網絡斷開或者終端關閉而一起被幹掉,那麼我們有兩種解決方案      

1、 nohup指令

- 方案1:讓程序忽略Linux HUP信号
- 方案2:讓程序運作在新的會話裡,進而成為不屬于此終端的子程序,就不會在目前終端挂掉的情況下一起被帶走。      
針對方案1,我們可以使用nohup指令,nohup 的用途就是讓送出的指令忽略 hangup 信号,該指令通常與&符号一起使用
nohup 的使用是十分友善的,隻需在要處理的指令前加上 nohup 即可,但是 nohup 指令會從終端解除程序的關聯,程序會丢掉STDOUT,STDERR的連結。标準輸出和标準錯誤預設會被重定向到 nohup.out 檔案中。一般我們可在結尾加上"&"來将指令同時放入背景運作,也可用">filename 2>&1"來更改預設的重定向檔案名。      

2、setsid指令

針對方案1,我們還可以用setsid指令實作,原理與3.1是一樣的,setid是直接将程序的父pid設定成1,即讓運作的程序歸屬于init的子程序,那麼除非init結束,該子程序才會結束,目前程序所在的終端結束後并不會影響程序的運作
# 1、在終端2中執行指令
[root@egon ~]# setsid ping www.baidu.com  # 也可以在後面加&符号

# 2、關閉終端2

# 3、在終端1中檢視
[root@egon ~]# ps -ef |grep [p]ing
root     102335      1  0 17:53 ?        00:00:00 ping www.baidu.com      

3、 在子shell中送出任務

# 1、在終端2中執行指令
[root@egon ~]# (ping www.baidu.com &)  # 送出的作業并不在作業清單中

# 2、關閉終端2

# 3、在終端1中檢視
[root@egon ~]# ps -ef |grep [p]ing
root     102361      1  0 17:55 ?        00:00:00 ping www.baidu.com
            
可以看到新送出的程序的父 ID(PPID)為1(init 程序的 PID),并不是目前終端的程序 ID。是以并不屬于目前終端的子程序,進而也就不會受到目前終端的Linux HUP信号的影響了。      

4、screen指令

# 1、安裝
[root@egon ~]# yum install screen -y



# 2、運作指令
方式一:開啟一個視窗并用-S指定視窗名,也可以不指定
[root@egon ~]# screen -S egon_dsb 
'''
Screen将建立一個執行shell的全屏視窗。你可以執行任意shell程式,就像在ssh視窗中那樣。
在該視窗中鍵入exit則退出該視窗,如果此時,這是該screen會話的唯一視窗,該screen會話退出,否則screen自動切換到前一個視窗。
'''



方式二:Screen指令後跟你要執行的程式
[root@egon ~]# screen vim test.txt
'''
Screen建立一個執行vim test.txt的單視窗會話,退出vim将退出該視窗/會話。
'''

# 3、原理分析
screen程式會幫我們管理運作的指令,退出screen,我們的指令還會繼續運作,若關閉screen所在的終端,則screen程式的ppid變為1,是以screen不會死掉,對應着它幫我們管理的指令也不會退出
測試略



# 4:重新連接配接會話
在終端1中運作
[root@egon ~]# screen
[root@egon ~]# n=1;while true;do echo $n;sleep 1;((n++));done

[root@egon ~]# 按下ctrl+a,然後再按下ctrl+d,注意要連貫,手别哆嗦,

[root@egon ~]# 此時可以關閉整個終端,我們的程式并不會結束

打開一個新的終端
[root@egon ~]# screen -ls
There is a screen on:
    109125.pts-0.egon    (Detached)
1 Socket in /var/run/screen/S-root.

[root@egon ~]# screen -r 109125  # 會繼續運作
[root@egon ~]# 

注意:如果我們剛開始已經用screen -S xxx指定了名字,那麼我們其實可以直接
screen -r xxx ,就無須去找程序id了      

5、遠端示範

# 在終端1:
[root@egon ~]# screen -S egon_av

# 開啟一個新的終端,在該終端執行的指令,終端1會同步顯示
[root@egon ~]# screen -x egon_av      

四、僵屍程序與孤兒程序

1、僵屍程序

#1、什麼是僵屍程序
作業系統負責管理程序
我們的應用程式若想開啟子程序,都是在向作業系統發送系統調用
當一個子程序開啟起來以後,它的運作與父程序是異步的,彼此互不影響,誰先死都不一定

linux作業系統的設計規定:父程序應該具備随時擷取子程序狀态的能力
如果子程序先于父程序運作完畢,此時若linux作業系統立刻把該子程序的所有資源全部釋放掉,那麼父程序來檢視子程序狀态時,會突然發現自己剛剛生了一個兒子,但是兒子沒了!!!
這就違背了linux作業系統的設計規定
是以,linux系統出于好心,若子程序先于父程序運作完畢/死掉,那麼linux系統在清理子程序的時候,會将子程序占用的重型資源都釋放掉(比如占用的記憶體空間、cpu資源、打開的檔案等),但是會保留一部分子程序的關鍵狀态資訊,比如程序号the process ID,退出狀态the termination status of the process,運作時間the amount of CPU time taken by the process等,此時子程序就相當于死了但是沒死幹淨,因而得名"僵屍程序",其實僵屍程序是linux作業系統出于好心,為父程序準備的一些子程序的狀态資料,專門供父程序查閱,也就是說"僵屍程序"是linux系統的一種資料結構,所有的子程序結束後都會進入僵屍程序的狀态





# 2、那麼問題來了,僵屍程序殘存的那些資料不需要回收嗎???
當然需要回收了,但是僵屍程序畢竟是linux系統出于好心,為父程序準備的資料,至于回收操作,應該是父程序覺得自己無需檢視僵屍程序的資料了,父程序覺得留着僵屍程序的資料也沒啥用了,然後由父程序發起一個系統調用wait / waitpid來通知linux作業系統說:哥們,謝謝你為我儲存着這些僵屍的子程序狀态,我現在用不上他了,你可以把他們回收掉了。然後作業系統再清理掉僵屍程序的殘餘狀态,你看,兩者配合的非常默契,但是,怕就怕在。。。






# 3、分三種情況讨論
1)linux系統自帶的一些優秀的開源軟體,這些軟體在開啟子程序時,父程序内部都會及時調用wait/waitpid來通知作業系統回收僵屍程序,是以,我們通常看不到優秀的開源軟體堆積僵屍程序,因為很及時就回收了,與linux系統配合的很默契

2)一些水準良好的程式員開發的應用程式,這些程式員技術功底深厚,知道父程序要對子程序負責,會在父程序内考慮調用wait/waitpid來通知作業系統回收僵屍程序,但是發起系統調用wait/waitpid的時間可能慢了些,于是我們可以在linux系統中通過指令檢視到僵屍程序狀态
[root@egon ~]# ps aux | grep [Z]+

3)一些垃圾程式員,技術非常垃圾,隻知道開子程序,父程序也不結束,就在那傻不拉幾地一直開子程序,也壓根不知道啥叫僵屍程序,至于wait/waitpid的系統調用更是沒聽說過,這個時候,就真的垃圾了,作業系統中會堆積很多僵屍程序,此時我們的計算機會進入一個奇怪的現象,就是記憶體充足、硬碟充足、cpu空閑,但是,啟動新的軟體就是無法啟動起來,為啥,因為作業系統負責管理程序,每啟動一個程序就會配置設定一個pid号,而pid号是有限的,正常情況下pid也用不完,但怕就怕堆積一堆僵屍程序,他吃不了多少記憶體,但能吃一堆pid



4、#如果清理僵屍程序
針對情況3,隻有一種解決方案,就是殺死父程序,那麼僵屍的子程序會被linux系統中pid為1的頂級程序(init或systemd)接管,頂級程序會定期發起系統調用wait/waitpid來通知作業系統清理僵屍

針對情況2,可以發送信号給父程序,通知它快點發起系統調用wait/waitpid來清理僵屍的兒子
kill -CHLD 父程序PID

5、#結語
僵屍程序是linux系統出于好心設計的一種資料結構,一個子程序死掉後,相當于作業系統出于好心幫它的爸爸儲存它的遺體,之說以會在某種場景下有害,是因為它的爸爸不靠譜,兒子死了,也不及時收屍(發起系統調用讓作業系統收屍)      

​​【程序詳解連結位址】​​

2、孤兒程序

#測試:(建立完子程序後,主程序所在的這個腳本就退出了,當父程序先于子程序結束時,子程序會被頂級程序收養,成為孤兒程序,而非僵屍程序),檔案内容

import os
import sys
import time

pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
#執行pid=os.fork()則會生成一個子程序
#傳回值pid有兩種值:
#    如果傳回的pid值為0,表示在子程序當中
#    如果傳回的pid值>0,表示在父程序當中
if pid > 0:
    print 'father died..'
    sys.exit(0)

# 保證主線程退出完畢
time.sleep(1)
print 'im child', os.getpid(), os.getppid()

執行檔案,輸出結果:
im father pid 32515 ppid 32015
father died..
im child 32516 1

#由上可看出,子程序已經被pid為1的頂級程序接收了,是以僵屍程序在這種情況下是不存在的,存在隻有孤兒程序而已,孤兒程序聲明周期結束自然會被頂級程序來銷毀。      

繼續閱讀