UNIX進程組,會話和作業控制

1. 進程組、會話與終端
(1).每個進程都屬於一個進程組。進程組是一個或多個進程的集合,通常它們與一組作業相關聯,可以接受來自同一終端的各種信號。每個進程組都有唯一的進程組ID(整數,也可以存放在pid_t類型中)。
  #include <unistd.h>
pid_t getpgrp(void);
//返回值;調用進程的進程組ID
    每個進程組都有一個組長進程,組長進程的標識是進程組ID等於其進程ID。組長進程可以創建一個進程組、創建該組中的進程。只有某個進程中有一個進程存在,則該進程就存在,與組長進程是否終止無關。從進程組創建開始到其中最後一個進程離開爲止的時間區間成爲進程組的生存期。進程組中最後一個進程可以終止或者轉移到另一個進程組中。
    進程調用setpgid(setsid也可以)可以參加一個現存的組或者創建一個新進程組
#include <sys/types.h>
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
//返回:若成功則爲0,出錯爲-1
    這將pid進程的進程組ID設置爲pgid。如果pid是0,則使用調用者的進程ID。另外,如果pgid是0,則由pid指定的進程ID被用作爲進程組ID。如果這兩個參數相等,則由pid指定的進程變成進程組組長。
    一個進程只能爲它自己或它的子進程設置進程組I D。在它的子進程調用了exec後,它就不再能改變該子進程的進程組I D。
    在大多數作業控制shell中,在fork之後調用此函數,使父進程設置其子進程的進程組ID,然後使子進程設置其自己的進程組ID。這些調用中有一個是冗餘的,但這樣做可以保證父、子進程在進一步操作之前,子進程都進入了該進程組。否則依賴於哪一個進程先執行,就產生一個競態條件。
(2).session是一個或多個進程組的集合。
    例如,在shell中:
$proc1 | proc2 &
$proc3 | proc4 
那麼此時,session中就會有三個進程組存在,分別是{登陸shell(session leader)},{proc1, proc2}, {proc3, proc4}。    
    進程調用setsid函數就可建立一個新對話期。
#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
    如果調用此函數的進程不是一個進程組的組長,則此函數創建一個新對話期,結果爲:
(a) 此進程變成該新對話期的對話期首進程(session leader,對話期首進程是創建該對話期的進程)。此進程是該新對話期中的唯一進程。
(b) 此進程成爲一個新進程組的組長進程。新進程組ID是此調用進程的進程ID。
(c) 此進程沒有控制終端。如果在調用setsid之前此進程有一個控制終端,那麼這種聯繫也被解除。
    如果此調用進程已經是一個進程組的組長,則此函數返回出錯。爲了保證不處於這種情況,通常先調用fork,然後使其父進程終止,而子進程則繼續。因爲子進程繼承了父進程的進程組ID,而其進程ID則是新分配的,兩者不可能相等,所以這就保證了子進程不是一個進程組的組長。
    對話期和進程組有一些其他特性:
• 一個對話期可以有一個單獨的控制終端(controlling terminal)。這通常是我們在其上登錄的終端設備(終端登錄情況)或僞終端設備(網絡登錄情況)。
• 建立與控制終端連接的對話期首進程,被稱之爲控制進程(controlling process)。
• 一個對話期中的幾個進程組可被分成一個前臺進程組(foreground process group)以及一個或幾個後臺進程組(background process group)。前臺進程組接受終端輸入信號。Shell中的作業控制就是對前後臺進程組的控制,&或Ctrl+Z的進程組就是後臺進程組。
• 如果一個對話期有一個控制終端,則它有一個前臺進程組,其他進程組則爲後臺進程組。
• 無論何時鍵入中斷鍵(常常是DELETE或Ctrl-C)或退出鍵(常常是Ctrl-\),就會造成將中斷信號或退出信號送至前臺進程組的所有進程。
• 終端的掛斷信號送至控制進程(對話期首進程。)
• 系統在登陸時將自動建立控制終端。
    如何分配一個控制終端依賴於實現。在open時,有幾個和控制終端相關的選項:O_NOCTTY 如果要打開的文件爲終端機設備時,則不會將該終端當成進程控制終端。
    有時不管標準輸入、標準輸出是否重新定向,程序都要與控制終端交互作用。保證程序讀寫控制終端的方法是打開文件/dev/tty,在內核中,此特殊文件代表控制終端。如果程序沒有控制終端,則打開此設備將失敗。
    注意:控制終端只有一個,通常控制終端/dev/tty代表當前shell的控制終端,其實是一個指向實際終端設備的連接。實際的終端設備可能是tty1,ttyS1或者pst/1.
(3).控制終端與終端
    首先介紹兩個抽象概念:
    tty(終端設備的統稱):tty一詞源於Teletypes,或者teletypewriters,原來指的是電傳打字機,是通過串行線用打印機鍵盤通過閱讀和發送信息的東西,後來這東西被鍵盤與顯示器取代,所以現在叫終端比較合適。終端是一種字符型設備,它有多種類型,通常使用tty來簡稱各種類型的終端設備。
    pty(僞終端,虛擬終端):遠程telnet到主機或使用xterm時不也需要一個終端交互。
    在Linux系統的設備特殊文件目錄/dev/下,終端特殊設備文件一般有以下幾種:
  1、串行端口終端(/dev/ttySn) 串行端口終端(Serial Port Terminal)是使用計算機串行端口連接的終端設備。計算機把每個串行端口都看作是一個字符設備。有段時間這些串行端口設備通常被稱爲終端設備,因爲那時它的最大用途就是用來連接終端。
   2、僞終端(/dev/pty/) 僞終端(Pseudo Terminal)是成對的邏輯終端設備(即master和slave設備, 對master的操作會反映到slave上)。例如/dev/ptyp3和/dev/ttyp3(或者在設備文件系統中分別是/dev/pty /m3和 /dev/pty/s3)。它們與實際物理設備並不直接相關。如果一個程序把ptyp3(master設備)看作是一個串行端口設備,則它對該端口的讀/ 寫操作會反映在該邏輯終端設備對應的另一個ttyp3(slave設備)上面。而ttyp3則是另一個程序用於讀寫操作的邏輯設備。telnet主機A就是通過“僞終端”與主機A的登錄程序進行通信。
    3、控制終端(/dev/tty) 如果當前進程有控制終端(Controlling Terminal)的話,那麼/dev/tty就是當前進程的控制終端的設備特殊文件。可以使用命令”ps –ax”來查看進程與哪個控制終端相連。對於你登錄的shell,/dev/tty是你當前的控制終端,設備號是(5,0)。使用命令”tty”可以查看它具體對應哪個實際終端設備。/dev/tty有些類似於到實際所使用終端設備的一個聯接。在當前的控制終端的讀寫都會寫到當前的終端設備中,例如echo "hello" > /dev/tty ,都會直接顯示在當前的終端中。而cat </dev/tty會從當前終端讀取輸入(行緩衝)並輸出出來。
    4、控制檯終端(/dev/ttyn, /dev/console) 在Linux 系統中,計算機顯示器通常被稱爲控制檯終端 (Console)。它仿真了類型爲Linux的一種終端(TERM=Linux),並且有一些設備特殊文件與之相關聯:tty0、tty1、tty2 等。當你在控制檯上登錄時,使用的是tty1。使用Alt+[F1—F6]組合鍵時,我們就可以切換到tty2、tty3等上面去。tty1–tty6等稱爲虛擬終端,而tty0則是當前所使用虛擬終端的一個別名,系統所產生的信息會發送到該終端上(這時也叫控制檯終端)。因此不管當前正在使用哪個虛擬終端,系統信息都會發送到控制檯終端上。/dev/console即控制檯,是與操作系統交互的設備,系統將一些信息直接輸出到控制檯上。目前只有在單用戶模式下,才允許用戶登錄控制檯。
    5、虛擬終端(/dev/pts/n)在Xwindows模式下的僞終端.如我在Kubuntu下用konsole,就是用的虛擬終端,用tty命令可看到/dev/pts/1。
    6、其它類型 Linux系統中還針對很多不同的字符設備存在有很多其它種類的終端設備特殊文件。例如針對ISDN設備的/dev/ttyIn終端設備等。
    
(4).幾個常用的終端相關命令
(a). 在ubuntu等發行版本中,圖形界面下Ctrl+Alt+F1-F6是打開tty1-6的終端()。在tty1-6這些終端下Alt1-6是切換終端,Alt+F7進入圖形界面。

(b). 可以通過ps -t的方式查看其他終端進程(這些終端在初始進入時候屬於getty狀態,由於tty1終端尚未登錄所以運行getty。而且沒有其他進程使用tty1終端):
$ps -t tty1
PID TTY          TIME CMD
1524 tty1     00:00:00 getty

(c). 可以使用shell的tty命令來識別現在使用的終端:
$ tty
/dev/pts/0

(d). stty - set tty, change and print terminal line settings
$stty -a 命令用於檢查和修改當前控制終端的通信參數。UNIX系統爲鍵盤的輸入和終端的輸出提供了重要的控制手段,可以通過stty命令對特定終端或通信線路設置選項.
$stty tostop #[-]tostop STOP嘗試向終端寫入數據的後臺任務。(SIGTTOU)
$echo "hello world" & #試圖輸出的進程會被終止
[1] 3063
$ fg
echo "hello world"
hello world

$ stty -tostop
$ echo "hello world" &
[1] 3065
hello world #不STOP結果直接被輸出出來。
$ jobs
[1]+  完成                  echo "hello world"
所有選項,-option_name是關閉,option_name是打開。對於控制終端的設置也是管理中重要的工作之一。

(4).需要有一種方法來通知內核哪一個進程組是前臺進程組,這樣,終端設備驅動程序就能瞭解將終端輸入和終端產生的信號送到何處。
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p); //成功則返回與終端文件描述符fd相關聯的前臺進程的組ID,出錯則返回-1。
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p); //成功則返回0,出錯則返回-1
//struct termios定義一和終端相關的標識字段,例忽略BREAK鍵,忽略校驗等等。
    大多數應用程序不直接調用這兩個函數,它們通常由作業控制shell調用。


2. 作業控制
(1).允許在一個終端上起動多個作業(進程組),控制哪一個作業可以存取該終端,以及哪些作業在後臺運行。作業控制要求三種形式的支持:
(a).支持作業控制的shell。
(b).內核中的終端驅動程序必須支持作業控制。
(c).必須提供對某些作業控制信號的支持。
三個特殊字符可使終端驅動程序產生信號,並將它們送至前臺進程組,它們是:
• 中斷字符(一般採用DELETE或Ctrl-C)產生SIGINT。
• 退出字符(一般採用Ctrl-\)產生SIGQUIT。
• 掛起字符(一般採用Ctrl-Z)產生SIGTSTP。
(2).不支持作業控制的Shell
    對於不支持作業控制的Shell,例如bsh,它的命令和它自身的進程處於同一個會話和前臺進程組。在後臺執行的命令(&)和管道命令的進程依然和Shell是同一個進程組。
    如果一個後臺進程試圖取走終端,例如cat > temp &。在有作業控制時,後臺作業被放在後臺進程組中。如果後臺作業試土讀控制終端,則會產生信號SIGTTIN。在沒有作業控制時,其處理方法是如果該進程自己沒有重定向標準輸入,則Shell會自動將標準輸入重定向到/dev/null。讀/dev/null則會產生一個EOF讓cat讀到文件末尾,正常結束。另外,管道執行的結構圖如下:
<圖>
(3).支持作業控制的Shell
$ ps -o pid -o ppid -o sid -o pgid -o command
  PID  PPID   SID  PGID COMMAND
 2074  2068  2074  2074 /bin/bash
 2580  2074  2074  2580 ps -o pid -o ppid -o sid -o pgid -o command
可以看出它們有不同的PGID。對於管道命令,他們屬於同一個進程組:
$ ps -o pid -o ppid -o sid -o pgid -o command | cat
  PID  PPID   SID  PGID COMMAND
 2074  2068  2074  2074 /bin/bash
 2584  2074  2074  2584 ps -o pid -o ppid -o sid -o pgid -o command
 2585  2074  2074  2584 cat
(4).SIGHUP信號
    SIGHUP會在以下3種情況下被髮送給相應的進程:
1、終端關閉時,該信號被髮送到session首進程以及作爲job提交的進程(即用 & 符號提交的進程)
2、session首進程退出時,該信號被髮送到該session中的前臺進程組中的每一個進程
3、若父進程退出導致進程組成爲孤兒進程組,且該進程組中有進程處於停止狀態(收到SIGTSTP信號),SIGHUP會被髮送到該進程組中的每一個進程。
    系統對SIGHUP信號的默認處理是終止收到該信號的進程。所以若程序中沒有捕捉該信號,當收到該信號時,進程就會退出。
    If a controlling process exits, the system revokes further access to the controlling terminal and sends a SIGHUP signal to the foreground process group. If a process such as a job-control shell exits, each process group that it created will become an orphaned process group。
(5).bash作業控制命令
(a). nohup:使用nohup讓程序永遠後臺運行
   由於很多程序不是守護進程,我們又想讓它在後臺運行,不受SIGHUP信號影響(例如shell退出或者終端連接斷開),那麼使用nohup命令。
$nohup sleep 100 &
$appending output to nohup.out #無論是否將命令重定向輸出輸出終端,輸出都將附加到當前目錄的nohup文件中。
$ps -t pts/0
#可以看到sleep 100進程終端爲pts/0
$exit
   然後打開另一終端:
    $ tty
/dev/pts/1
$ ps -ef | grep sleep | grep -v grep
luffy     4171     1  0 18:12 ?        00:00:00 sleep 100
#100秒後再次查詢,結果爲空。說明進程正常退出
(b). 作業號 %n:支持作業控制的Shell可以識別%+作業號的作業進程
例如:
$ sleep 10&
[1] 4294
$ %1 #bring %1 to front,same as fg %1
sleep 10
(c).$fg 則將第一個作業放到前臺。fg %n
(d).bg 將一個在後臺暫停的命令,變成繼續執行 如果後臺中有多個命令,可以用bg %jobnumber將選中的命令調出,%jobnumber是通過jobs命令查到的後臺正在執行的命令的序號(不是pid)。
(e).jobs
(f). ctrl+z or &

(6).Linux讓進程在後臺執行
(a). nohup命令:略
(b). setsid命令. 如果我們的進程不屬於接受 HUP 信號的終端的子進程,那麼自然也就不會受到 HUP 信號的影響了。setsid 就能幫助我們做到這一點。系統調用setsid()請見前面。
$setsid ping www.baidu.com
$ PING www.a.shifen.com (119.75.218.70) 56(84) bytes of data.
64 bytes from 119.75.218.70: icmp_req=1 ttl=54 time=1.88 ms
......
^C
64 bytes from 119.75.218.70: icmp_req=3 ttl=54 time=1.65 ms
64 bytes from 119.75.218.70: icmp_req=4 ttl=54 time=1.92 ms
......
輸出信息會不斷出現在這個終端,由於這個進程已經不是這個Shell的會話了,所以Ctrl+C不能終止這個進程。就算關閉整個終端,程序也會繼續執行。
在另一個終端將它終止 :
$ ps -e -o pid -o sid -o pgid -o command | grep ping | grep -v grep
 4474  4474  4474 ping www.baidu.com
#可以看出sid=pid.由於是
$ kill -SIGKILL 4396
$ ps -ef | grep ping |grep -v grep
進程被kill掉。
(c).(&)
subshell:一個或多個命令包含在()裏執行就能讓他們在子shell中執行。所以讓在子shell中執行的作業用jobs看不到,自然也不會接收任何hup信號。例如:
$(ping www.baidu.com &)
(d).disown
對於已經提交出去的命令,使用作業調度disown來達到目的:
用disown -h jobspec 來使某個作業忽略HUP信號。
用disown -ah 來使所有的作業都忽略HUP信號。
用disown -rh 來使正在運行的作業忽略HUP信號。
# cp -r testLargeFile largeFile &
[1] 4825
# jobs
[1]+  Running                 cp -i -r testLargeFile largeFile &
# disown -h %1
# ps -ef |grep largeFile
root      4825   968  1 09:46 pts/4    00:00:00 cp -i -r testLargeFile largeFile
root      4853   968  0 09:46 pts/4    00:00:00 grep largeFile
# logout  
對於正在運行,沒有加&放到後臺運行的程序可以先ctrl+Z停止進程,然後用bg %n讓這個作業繼續執行。再用disown忽略hup信號。
(e). screen終端模擬器,功能強大這裏只簡單介紹。
沒有啓動screen的進程樹 :
$ ping www.baidu.com >~/temp.output &
[1] 4962
$ pstree -H 4962 #讓指定的進程高亮顯示
init─┬─/usr/bin/termin─┬─bash
     │                 ├─bash─┬─ping
......
在使用了screen的進程樹
# screen -r Urumchi
# ping www.ibm.com &
[1] 9488
# pstree -H 9488
init-+-/usr/bin/termin-+-bash
     |                 |-bash-+-ping
     |                 |      `-screen---screen-+-bash

Reference:
APUE
man
Stty使用一技 http://fanqiang.chinaunix.net/a1/b4/20020606/060200245.html
Linux中的終端、控制檯、tty、pty等概念 http://news.newhua.com/news1program_language/2010/623/10623141048745773199BCF0CFH6AKB9930IGCFKHBH4IBE65IDFI07F.html
IBM文庫-Linux 技巧:讓進程在後臺可靠運行的幾種方法 http://www.ibm.com/developerworks/cn/linux/l-cn-nohup/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章