[TOC] 一、進程相關的概念 進程需要瞭解 進程,父進程,進程組,會話和控制終端的相關概念。 1. 進程和父進程:每個進程都有父進程,而所有的進程以init進程為根,形成一個樹狀結構 2. 進程組:每個進程都會屬於一個進程組(process group),每個進程組中可以包含多個進程。進程組會有一 ...
目錄
- 一、進程相關的概念
- 二、關閉會話時子進程進程被殺死
- 三、nohup的原理
- 四、setsid原理
- 五、daemon &和守護進程的區別
- 六、服務進程為什麼要fork兩次
- 七、systemd管理daemon
- 八、僵屍進程
- 九、進程名字和啟動時指定進程名字
- 十、source command和./command 和exec命令的區別
一、進程相關的概念
進程需要瞭解 進程,父進程,進程組,會話和控制終端的相關概念。
進程和父進程:每個進程都有父進程,而所有的進程以init進程為根,形成一個樹狀結構
進程組:每個進程都會屬於一個進程組(process group),每個進程組中可以包含多個進程。進程組會有一個進程組領導進程 (process group leader),領導進程的PID成為進程組的ID (process group ID, PGID),以識別進程組。
kill給組發送信號進程組號前加負號如:kill -9 -2189
- 會話:一個或是多個進程組集合。 進程可以通過調用 pid_t setsid(); 來建立一個新會話,如果調用此函數的進程不是進程組長,就會創建一個新的會話,那麼此時會:
- 該進程稱為會話首進程 (session leader)
- 該進程稱為進程組組長
該進程沒有控制終端,即使之前有控制終端這種聯繫也會斷掉
可以使用第三個特性來創建 daemon 進程。 調用 getsid 可以獲得會話首進程進程組 pid,也就是會話首進程進程 id。
- 控制終端:
- 一個會話持有一個控制終端 (controlling terminal),可以是終端設備也可以是偽終端
- 建立與控制終端連接的會話首進程被稱為控制進程 (controlling process)
- 一個會話有多個進程組,允許存在多個後臺進程組 (backgroup process group) 和一個前臺進程組 (foregroup process group)
- 鍵入終端的中斷鍵 (Ctrl+C) 會發送中斷信號給前臺進程組所有進程
- 鍵入終端的退出鍵 (Ctrl+) 會發送退出信號給前臺進程組所有進程
- 終端或是網路斷開會將掛斷信號發送給會話首進程
可以看到執行ps -fj結果如下:
UID PID PPID PGID SID C STIME TTY TIME CMD
chen 36829 36825 36829 36829 0 10:56 pts/0 00:00:00 -bash
chen 37247 36829 37247 36829 0 10:57 pts/0 00:00:00 vim
chen 90490 36829 90490 36829 0 11:57 pts/0 00:00:00 ps -fj
其中PID就是進程id,PPID是父進程id,PGID為進程組id,SID為會話ID
二、關閉會話時子進程進程被殺死
終端在關閉時會發送SIGHUP信號給session leader,此處就是bash進程,bash收到後向session內的所有進程發送SIGHUP然後退出。
SIGHUP信號如果為註冊處理函數預設行為就是退出。所以會話退出時子進程都被殺死。
解決方案:
- 註冊SIGHUP信號處理函數:可以在代碼中處理或者使用nohup命令(
nohup daemon & &>daemon.log
) - 重新設置setsid:可以在代碼中處理或者使用setsid命令(
setsid daemon
)
三、nohup的原理
其實很簡單就是註冊了SIGHUP的一個處理函數,忽略這個信號,然後去執行實際的命令。
源碼地址:https://github.com/MaiZure/coreutils-8.3/blob/master/src/nohup.c,nohup的使用也推薦< /dev/null
來重定向stdin
關鍵代碼:
// 註冊處理函數
signal (SIGHUP, SIG_IGN);
char **cmd = argv + optind;
//執行實際的代碼
execvp (*cmd, cmd);
四、setsid原理
fork進程之後的子進程共用父進程的很多東西,並且會話組長就是父進程的會長組長,所以會收到來自父進程會話組長的信號。
setsid用餘新建一個會話,調用這個函數之後會噹噹前進程成為進程組組長和會話組組長,那麼原來的會話產生的信號便不會發送到這個進程,從而不會受影響。
五、daemon &和守護進程的區別
因為守護進程的實現是用的setsid,所以其實就是setsid和nohup的區別,兩者都可以用來防止進程在終端斷開的時候被殺死,nohup還需要配合&放入後臺運行。區別的的話守護進程已經脫離了終端,不受終端控制,也就沒有 了stdin,stdout和stderr,而使用nohup之後的進程還是有一個終端,只是忽略了其中的SIGHUP信號,存在正常的stdin,stdout和stderr,nohup預設將stdout和stderr重定向到了nohup.out。
最佳實踐:
- 如果是一次性的後臺任務,可以使用nohup十分方便
- 如果是長期運行的服務,則推薦使用系統的systemd來管理服務
- 如果是定時運行的任務則推薦使用cron來運行
六、服務進程為什麼要fork兩次
首先說明兩次不是必須的,有很多程式都採用了一次fork。
第一次:為了調用setsid,這也解釋了為什麼調用setsid之前需要先fork的原因:
linux規定調用這個函數之前,當前進程不允許是session leader。進程組leader是該進程組的第一個進程,fork出來的進程必定不是第一個,所以可以調用setsid。另外父進程一般直接退出,可以讓shell收到進程結束的通知繼續執行,而不是等待他結束。
第二次:為了限制進程打開控制終端,只有會話組長能打開控制終端(非必須,相當於加了個限制條件Daemon不需要打開終端)
七、systemd管理daemon
現在很多的linux發行版都採用systemd來代替原來的init程式,systemd提供了很優秀的進程管理功能,我們需要註冊服務時可以利用systemd功能,可以參看鳥哥的systemd介紹。
另外補充點內核進程和Systemd進程:
0號進程為內核進程,1號為Systemd進程,其他還有些內核進程在ps命令查看時以[]包裹。具體關係見:LINUX PID 1 和 SYSTEMD
八、僵屍進程
這個定義摘抄自維基百科:在類UNIX系統中,僵屍進程是指完成執行(通過exit系統調用,或運行時發生致命錯誤或收到終止信號所致)但在操作系統的進程表中仍然有一個表項(進程式控制制塊PCB),處於"終止狀態"的進程。這發生於子進程需要保留表項以允許其父進程讀取子進程的exit status:一旦退出態通過wait系統調用讀取,僵屍進程條目就從進程表中刪除,稱之為"回收(reaped)"。
九、進程名字和啟動時指定進程名字
kill,ps,top,pstree這些命令都比較熟悉就不再提了。
至於還有一組命令則不是通過進程號而是通過進程名字來操作進程,pkill和killall一樣都是通過名字來殺死進程,而pgrep是通過名字來尋找進程。
他們的原理都是通過查找/proc這個記憶體文件系統。
在啟動的時候可以通過exec命令重命名:
bash -c "exec -a myname sleep 500 &"
你可以通過ps -ef|grep myname
來查看進程的詳細信息
十、source command和./command 和exec命令的區別
通常執行腳本有三種方式
- ./command(同sh command)
- source command(同. command)
- exec command
簡單說明下上面三種方式:
第一種其實就是對應了linux的fork系統調用,在執行command時候,command是在子進程中執行的,當前shell等待直到子進程的command運行完畢在返回到當前shell。第二種則是直接在當前的進程中直接執行,執行完繼續接受用戶輸入。第三種則對應了linux的exec系統調用,當前進程的執行流程會轉向command,command是在當前進程直接執行,但是執行完之後便會直接退出。
所以我們一般用的是第一和第二兩種,這種的主要區別就是開不開新的進程(開進程是要一定開銷的),另外因為第二種是在當前進程執行的,所以如果在command中設置了變數,那麼相當於在當前進程中設置了變數,所以我們一般是用第一種去執行避免當前進程的變數被污染。
思考:
現在加入你在終端已經運行了一個非常耗時的任務,你按ctrl+z放入了後臺,然後利用bg開始任務,因為終端斷開就會收到SIGHUP信號,有沒有辦法忽略這個信號或者終端斷開不收到這個信號?
遺留:
進程調試工具:ltrace strace ftrace
參考鏈接: