進程組、做業、會話

基本概念
linux

1、進程shell

進程與程序的區別:程序是指的存儲在存儲設備上(如磁盤)包含了可執行機器指 令(二進制代碼)和數據的靜態實體;而進程能夠認爲是已經被OS從磁盤加載到內存上的、動態的、可運行的指令與數據的集合,是在運行的動態實體。
bash


2、進程組服務器

每一個進程除了有一個進程ID以外,還屬於一個進程組。進程組是一個或多個進程的集合。一般,它們與同一做業相關聯,能夠接收來自同一終端的各類信號。每一個進程組有一個惟一的進程組ID。每一個進程組均可以有一個組長進程。組長進程的標識是,其進程組ID等於其進程ID。網絡

組長進程能夠建立一個進程組,建立該組中的進程,而後終止。只要在某個進程組中一個進程存在,則該進程組就存在,這與其組長進程是否終止無關。ide


3、做業函數

Shell分先後臺來控制的不是進程而是做業(Job)或者進程(Process Group)。一個前臺做業能夠由多個進程組成,一個後臺也能夠由多個進程組成,Shell能夠運行一個前臺做業和任意多個後臺做業,這稱爲做業控制。spa

做業與進程組的區別:若是做業中的某個進程又建立了子進程,則子進程不屬於做業。一旦做業運行結束,Shell就把本身提到前臺,若是原來的前臺進程還存在(若是這個子進程還沒終止),它自動變爲後臺進程組。3d

示例:orm

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }
    else if(id == 0)  //child
    {
        while(1)
        {
            printf("child\n");
            sleep(1);
        }
    }
    else  //father
    {
        int count = 0;
        while(count < 5)
        {
            printf("father\n");
            ++count;
            sleep(1);
        }
        exit(0);
    }

    return 0;
}

父子進程同時向終端輸出信息,5秒以後父進程退出,但子進程仍在繼續。(只能經過kill命令殺死該進程)

wKioL1eetmOQQvXyAABOuO5Eu5Y581.png


4、會話

會話(Session)是一個或多個進程組的集合。

一個會話能夠有一個控制終端。這一般是登錄到其上的終端設備(在終端登錄狀況下)或僞終端設備(在網絡登錄狀況下)。創建與控制終端鏈接的會話首進程被稱爲控制進程。一個會話中的幾個進程組可被分爲一個前臺進程組以及一個或多個後臺進程組。因此一個會話中,應該包括控制進程(會話首進程),一個前臺進程組和任意後臺進程組。

示例:

wKiom1eeqS2TP_cjAAAdtTpM2L4258.png

其中proc1與proc2屬於同一個後臺進程組,proc3,proc4和proc5屬於同一個前臺進程組,Shell自己屬於一個單獨的進程組。這些進程組的控制終端相同,它們同屬於一個會話,當用戶在控制終端輸入特殊的控制鍵(如Ctrl+C,產生SIGINT,Ctrk+\,產生SIGQUIT,Ctrl+Z,產生SIGTSTP),內核發送相應的信號給前臺進程組中的全部進程。


5、終端

在UNIX系統中,用戶經過終端登陸系統後獲得一個Shell進程,這個終端成爲Shell進程的控制終端 (Controlling Terminal),控制終端是保存在PCB中的信息,而咱們知道fork會複製PCB中的信息,所以由Shell進程啓動的其它進程的控制終端也是這個終端。默認狀況下(沒有重定向),每一個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀用戶的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上。此外在控制終端輸入一些特殊的控制鍵能夠給前臺進程發信號,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。

每一個進程均可以經過一個特殊的設備文件/dev/tty訪問它的控制終端。事實上每一個終端設備都對應一個不一樣的設備文件,/dev/tty提供了一個通用的接口,一個進程要訪問它的控制終端既能夠經過/dev/tty也能夠經過該終端設備所對應的設備文件來訪問。ttyname函數能夠由文件描述符查出對應的文件名,該文件描述符必須指向一個終端設備而不能是任意文件。

示例:

#include <stdio.h>

int main()
{
    printf("fd: %d -> %s\n",0, ttyname(0));
    printf("fd: %d -> %s\n",1, ttyname(1));
    printf("fd: %d -> %s\n",2, ttyname(2));

    return 0;
}

wKiom1eersPieNs6AAAVC2u2R0Q286.png

再打開一個終端運行該程序:

wKioL1eery-yeWgWAAAU1D36jCo795.png

再打開一個終端運行該程序:

wKioL1eer1-xgNwyAAAUns5H8FQ553.png

將終端0的運行結果重定向到終端1

wKioL1eesiGAiV5cAAA-jkbArEs244.png

咱們發現,終端1的標準輸出變成了終端1的。而標準輸入和標準錯誤沒有變化,仍是終端0


做業控制


會話(Session)與進程組

事實上,Shell分先後臺來控制的不是進程而是做業 (Job)或者進程組(ProcessGroup)。一個前臺做業能夠由多個進程組成,一個後臺做業也能夠由多個進程組成,Shell能夠同時運行一個前臺做業和任意多個後臺做業,這稱爲做業控制(Job Control)。

wKiom1eeqS2TP_cjAAAdtTpM2L4258.png

其中proc1和proc2屬於同一個後臺進程組,proc三、proc四、proc5屬於同一個前臺進程組,Shell進程自己屬於一個單獨的進程組。這些進程組的控制終端相同,它們屬於同一個Session。當用戶在控制終端輸入特殊的控制鍵(例如Ctrl-C)時,內核會發送相應的信號(例如SIGINT)給前臺進程組的全部進程。

shell執行命令的過程:

由Shell進程fork出的子進程原本具備和Shell相同的Session、進程組和控制終端,可是Shell調用setpgid函數將做業中的某個子進程指定爲一個新進程組的Leader,而後調用setpgid將該做業中的其它子進程也轉移到這個進程組中。若是這個進程組須要在前臺運行,就調用tcsetpgrp函數將它設置爲前臺進程組,因爲一個Session只能有一個前臺進程組,因此Shell所在的進程組就自動變成後臺進程組。在上面的例子中,proc三、proc四、proc5被Shell放到同一個前臺進程組,其中有一個進程是該進程組的Leader,Shell調用wait等待它們運行結束。一旦它們所有運行結束,Shell就調用tcsetpgrp函數將本身提到前臺繼續接受命令。可是注意,若是proc三、proc四、proc5中的 某個進程又fork出子進程,子進程也屬於同一進程組,可是Shell並不知道子進程的存在,也不會調用wait等待它結束。換句話說,proc3 | proc4 | proc5是Shell的做業,而這個子進程不是,這是做業和進程組在概念上的區別。一旦做業運行結束,Shell就把本身提到前臺,若是原來的前臺進程組還存在(若是這個子進程還沒終止),則它自動變成後臺進程組。

示例:

wKiom1eevZDjo_fPAAAo0C8-CFg148.png

這個做業由ps和cat兩個進程組成,在前臺運行。從PPID列能夠看出這兩個進程的父進程是bash。從PGRP列能夠看出,bash在id爲5122的進程組中,這個id等於bash的進程id,因此它是進程組的Leader,而兩個子進程在id爲6576的進程組中,ps是這個進程組的Leader。從SESS能夠看出三個進程都在同一Session中,bash是Session Leader。從TPGID能夠看出,前臺進程組的id是6576,也就是兩個子進程所在的進程組。

wKiom1eevqnws_a4AAApUQhkf_E209.png

這個做業由ps和cat兩個進程組成,在後臺運行,bash不等做業結束就打印提示信息[1] 7141而後給出提示符接受新的命令,[1]是做業的編號,若是同時運行多個做業能夠用這個編號區分,7141是該做業中某個進程的id。

與做業控制有關的信號

wKioL1eev1CTak_iAAASMxjOVpk814.png

將cat放到後臺運行,因爲cat須要讀標準輸入(也就是終端輸入),然後臺進程是不能讀終端輸入的,所以內核發SIGTTIN信號給進程,該信號的默認處理動做是使進程中止。

wKioL1eewnjhyQ2GAAA9feQqThE405.png

jobs命令能夠查看當前有哪些做業。fg命令能夠將某個做業提至前臺運行,若是該做業的進程組正在後臺運行則提至前臺運行,若是該做業處於中止狀態,則給進程組的每一個進程發SIGCONT信號使它繼續運行。參數%1表示將第1個做業提至前臺運行。cat提到前臺運行後,掛起等待終端輸入,當輸入hello並回車後,cat打印出一樣的一行,而後繼續掛起等待輸入。若是輸入Ctrl-Z則向全部前臺進程發SIGTSTP信號,該信號的默認動做是使進程中止,cat繼續之後臺做業的形式存在。

wKiom1eewxXB_xv8AAAadwT7J00126.png

bg命令可讓某個中止的做業在後臺繼續運行,也須要給該做業的進程組的每一個進程發SIGCONT信號。cat進程繼續運行,又要讀終端輸入,然而它在後臺不能讀終端輸入,因此又收到SIGTTIN信號而中止。

wKioL1eexOiivzDyAABjk4mbgiw783.png

用kill命令給一箇中止的進程發SIGTERM信號,這個信號並不會馬上處理,而要等進程準備繼續運行以前處理,默認動做是終止進程。但若是給一箇中止的進程發SIGKILL信號就不一樣了。以下:

wKiom1ee4laDMfj-AABettmLMC0222.png

SIGKILL信號既不能被阻塞也不能被忽略,也不能用自定義函數捕捉,只能按系統的默認動做馬上處理。與此相似的還有SIGSTOP信號,給一個進程發SIGSTOP信號會使進程中止,這個默認的處理動做不能改變。這樣保證了無論什麼樣的進程都能用SIGKILL終止或者用SIGSTOP中止,當系統出現異常時管理員老是有辦法殺掉有問題的進程或者暫時停掉懷疑有問題的進程。


守護進程

守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件。守護進程是一種頗有用的進程。Linux的大多數服務器就是用守護進程實現的。好比,Internet服務器inetd,Web服務器httpd等。同時,守護進程完成許多系統任務。好比,做業規劃進程crond,打印進程lpd等。(這裏的結尾字母d就是Daemon的意思)

linux建立守護進程的步驟以下:

建立守護進程最關鍵的一步是調用setsid函數建立一個新的Session,併成爲Session Leader。該函數調用成功時返回新建立的Session的id(其實也就是當前進程的id),出錯返回-1。注意,調用這個函數以前,當前進程不容許是進程組的Leader,不然該函數返回-1。要保證當前進程不是進程組的Leader也很容易,只要先fork再調用setsid就好了。fork建立的子進程和父進程在同一個進程組中,進程組的Leader必然是該組的第一個進程,因此子進程不多是該組的第一個進程,在子進程中調用setsid就不會有問題了。

成功調用該函數的結果是:

1. 建立一個新的Session,當前進程成爲Session Leader,當前進程的id就是Session的id。

2. 建立一個新的進程組,當前進程成爲進程組的Leader,當前進程的id就是進程組的id。

3. 若是當前進程本來有一個控制終端,則它失去這個控制終端,成爲一個沒有控制終端的進程。所謂失去控制終端是指,原來的控制終端仍然是打開的,仍然能夠讀寫,但只是一個普通的打開文件而不是控制終端了。

建立守護進程

1. 調用umask將文件模式建立屏蔽字設置爲0.

2. 調用fork,父進程退出(exit)。

緣由:

1)若是該守護進程是做爲一條簡單的shell命令啓動的,那麼父進程終止使得shell認爲該命令已經執行完畢。

2)保證子進程不是一個進程組的組長進程。

3. 調用setsid建立一個新會話。setsid會致使:

1)調用進程成爲新會話的首進程。 

2)調用進程成爲一個進程組的組長進程 。

3)調用進程沒有控制終端。(再次fork一次,保證daemon進程,以後不會打開tty設備)

4. 將當前工做目錄更改成根目錄。

5. 關閉不在須要的文件描述符。

6. 其餘:忽略SIGCHLD信號。

相關文章
相關標籤/搜索