在上一篇中介紹了tty的相關原理,這篇將介紹跟tty密切相關的session和進程組。html
本篇主要目的是澄清一些概念,不涉及細節前端
session就是一組進程的集合,session id就是這個session中leader的進程ID。node
session的主要特色是當session的leader退出後,session中的全部其它進程將會收到SIGHUP信號,其默認行爲是終止進程,即session的leader退出後,session中的其它進程也會退出。linux
若是session和tty關聯的話,它們之間只能一一對應,一個tty只能屬於一個session,一個session只能打開一個tty。固然session也能夠不和任何tty關聯。shell
session能夠在任什麼時候候建立,調用setsid函數便可,session中的第一個進程即爲這個session的leader,leader是不能變的。常見的建立session的場景是:segmentfault
用戶登陸後,啓動shell時將會建立新的session,shell會做爲session的leader,隨後shell裏面運行的進程都將屬於這個session,當shell退出後,全部該用戶運行的進程將退出。這類session通常都會和一個特定的tty關聯,session的leader會成爲tty的控制進程,當session的前端進程組發生變化時,控制進程負責更新tty上關聯的前端進程組,當tty要關閉的時候,控制進程所在session的全部進程都會收到SIGHUP信號。後端
啓動deamon進程,這類進程須要和父進程劃清界限,因此須要啓動一個新的session。這類session通常不會和任何tty關聯。bash
進程組(process group)也是一組進程的集合,進程組id就是這個進程組中leader的進程ID。session
進程組的主要特色是能夠以進程組爲單位經過函數killpg發送信號函數
進程組主要用在shell裏面,shell負責進程組的管理,包括建立、銷燬等。(這裏shell就是session的leader)
對大部分進程來講,它本身就是進程組的leader,而且進程組裏面就只有它本身一個進程
shell裏面執行相似ls|more
這樣的以管道鏈接起來的命令時,兩個進程就屬於同一個進程組,ls是進程組的leader。
shell裏面啓動一個進程後,通常都會將該進程放到一個單獨的進程組,而後該進程fork的全部進程都會屬於該進程組,好比多進程的程序,它的全部進程都會屬於同一個進程組,當在shell裏面按下CTRL+C時,該程序的全部進程都會收到SIGINT而退出。
shell中啓動一個進程時,默認狀況下,該進程就是一個前端進程組的leader,能夠收到用戶的輸入,而且能夠將輸出打印到終端,只有當該進程組退出後,shell才能夠再響應用戶的輸入。
但咱們也能夠將該進程組運行在後臺,這樣shell就能夠繼續相應用戶的輸入,常見的方法以下:
啓動程序時,在後面加&,如sleep 1000 &
,進程將會進入後臺繼續運行
程序啓動後,能夠按CTRL+Z讓它進入後臺,和後面加&不一樣的是,進程會被暫停執行
對於後臺運行的進程組,在shell裏面體現爲job的概念,即一個後臺進程組就是一個job,job有以下限制:
默認狀況下,只要後臺進程組的任何一個進程讀tty,將會使整個進程組的全部進程暫停
默認狀況下,只要後臺進程組的任何一個進程寫tty,將有可能會使整個進程組的全部進程暫停(依賴於tty的配置,請參考TTY/PTS概述)
全部後臺運行的進程組能夠經過jobs命令查看到,也能夠經過fg命令將後臺進程組切換到前端,這樣就能夠繼續接收用戶的輸入了。這兩個命令的具體用法請參考它們的幫助文件,這裏只給出一個簡單的例子:
#一般狀況下,sleep命令會一直等待在那裏,直到指定的時間過去後才退出。 #shell啓動sleep程序時,就將sleep放到了一個新的進程組, #而且該進程組爲前端進程組,雖然sleep不須要輸入,也沒有輸出, #但當前session的標準輸入和輸出仍是歸它,別人用不了, #只有咱們按下CTRL+C使sleep進程退出後,shell本身從新變成了前端進程組, #因而shell從新具有了響應輸入以及輸出能力 dev@debian:~$ sleep 1000 ^C #咱們能夠在命令行的後面加上&符號,shell仍是照樣會建立新的進程組, #而且sleep進程就是新進程組的leader, #可是shell會將sleep進程組放到後端,讓它成爲後臺進程組 #這裏[1]是job id,1627是進程組的ID,即sleep進程的id dev@debian:~$ sleep 1000 & [1] 1627 #能夠經過jobs命令看到當前有哪些後臺進程組(job) dev@debian:~$ jobs [1]+ Running sleep 1000 & #使用fg命令帶上job id,便可讓後端進程組回到前端, #而後咱們使用CTRL+Z命令可讓它再次回到後端,並暫停進程的執行 #CTRL+Z和&不同的地方就是CTRL+Z會讓進程暫停執行,而&不會 dev@debian:~$ fg 1 sleep 1000 ^Z [1]+ Stopped sleep 1000 #Stopped狀態表示進程在後臺已經暫停執行了 dev@debian:~$ jobs [1]+ Stopped sleep 1000
deamon程序雖然也是一個session的leader,但通常它不會建立新的進程組,也沒有job的管理功能,因此這種狀況下一個session就只有一個進程組,全部的進程都屬於一樣的進程組和session。
咱們這裏看一下shell做爲session leader的狀況,假設咱們在shell裏面執行了這些命令:
dev@debian:~$ sleep 1000 & [1] 1646 dev@debian:~$ cat | wc -l & [2] 1648 dev@debian:~$ jobs [1]- Running sleep 1000 & [2]+ Stopped cat | wc -l
下面這張圖標明瞭這種狀況下它們之間的關係:
+--------------------------------------------------------------+ | | | pg1 pg2 pg3 pg4 | | +------+ +-------+ +-----+ +------+ | | | bash | | sleep | | cat | | jobs | | | +------+ +-------+ +-----+ +------+ | | session leader | wc | | | +-----+ | | | +--------------------------------------------------------------+ session
pg = process group(進程組)
bash是session的leader,sleep、cat、wc和jobs這四個進程都由bash fork而來,因此他們也屬於這個session
bash也是本身所在進程組的leader
bash會爲本身啓動的每一個進程都建立一個新的進程組,因此這裏sleep和jobs進程屬於本身單獨的進程組
對於用管道符號「|」鏈接起來的命令,bash會將它們放到一個進程組中
nohup是咋回事呢?nohup幹了這麼幾件事:
將stdin重定向到/dev/null,因而程序讀標準輸入將會返回EOF
將stdout和stderr重定向到nohup.out或者用戶經過參數指定的文件,程序全部輸出到stdout和stderr的內容將會寫入該文件(有時在文件中看不到輸出,有多是程序沒有調用flush)
屏蔽掉SIGHUP信號
調用exec啓動指定的命令(nohup進程將會被新進程取代,但進程ID不變)
從上面nohup乾的事能夠看出,經過nohup啓動的程序有這些特色:
nohup程序不負責將進程放到後臺,這也是爲何咱們常常在nohup命令後面要加上符號「&」的緣由
因爲stdin、stdout和stderr都被重定向了,nohup啓動的程序不會讀寫tty
因爲stdin重定向到了/dev/null,程序讀stdin的時候會收到EOF返回值
nohup啓動的進程本質上仍是屬於當前session的一個進程組,因此在當前shell裏面能夠經過jobs看到nohup啓動的程序
當session leader退出後,該進程會收到SIGHUP信號,但因爲nohup幫咱們忽略了該信號,因此該進程不會退出
因爲session leader已經退出,而nohup啓動的進程屬於該session,因而出現了一種狀況,那就是經過nohup啓動的這個進程組所在的session沒有leader,這是一種特殊的狀況,內核會幫咱們處理這種特殊狀況,這裏就再也不深刻介紹
經過nohup,咱們最後達到了就算session leader(通常是shell)退出後,進程還能夠照常運行的目的。
經過nohup,就能夠實現讓進程在後臺一直執行的功能,爲何咱們還要寫deamon進程呢?
從上面的nohup的介紹中能夠看出來,雖然進程是在後臺執行,但進程跟當前session仍是有着千絲萬縷的關係,至少其父進程仍是被session管着的,因此咱們仍是須要一個跟任何session都沒有關係的進程來實現deamon的功能。實現deamon進程的大概步驟以下:
調用fork生成一個新進程,而後原來的進程退出,這樣新進程就變成了孤兒進程,因而被init進程接收,這樣新進程就和調用進程沒有父子關係了。
調用setsid,建立新的session,新進程將成爲新session的leader,同時該新session不和任何tty關聯。
切換當前工做目錄到其它地方,通常是切換到根目錄,這樣就取消了對原工做目錄的引用,若是原工做目錄是某個掛載點下面的目錄,這樣就不會影響該掛載點的卸載。
關閉一些從父進程繼承過來而本身不須要的fd,避免不當心讀寫這些fd。
重定向stdin、stdout和stderr,避免讀寫它們出現錯誤。