一. 進程組/作業/會話
1.進程組
????每一個進程除了有一個進程ID之外, 還屬于一個進程組. 進程是一個或多個進程的集合. 通常, 它們與同一個作業向關聯, 可以接收來自同一個終端下的各種命令,信號. 每一個進程組都有唯一的進程組 ID. 每一個進程組都可以有一個組長進程. 組長進程的標識是, 其進程組 ID 等于組長進程 ID. 組長進程可以創建一個進程組, 也可以創建該進程組中的進程, 然后終止.只要該組中有一個進程存在, 則該組就存在, 與該組中的組長進程是否存在沒有任何關系
2. 相關接口函數
????????????????
????其中getpgrp 函數用來返回調用進程的進程組 ID, setpid 用來將 pid 進程的進程組 ID 設置為 pgid, 如果兩個參數值相等, 則由 pid 指定的進程將會變成進程組組長. 如果 pid 為 0, 則使用調用者的進程 id. 另外, 如果 pgid 為 0, 則由進程 pid 指定的進程 id將會變成進程組 id.一個進程只能為自己的子進程或者為自己設定進程組ID, 但是當這個函數的子進程已經調用了 exec 之后, 父進程就不能再改變子進程的組ID了
????在大多數shell下通常是調用 fork 后讓父進程調用 setpgid , 同時也讓子進程調用 setpgid.
????其中 ps 命令常用來顯示進程相關信息, 其中選項 a 表示不僅列出當前用戶的進程, 也列出其他所有用戶進程, x 表示不僅列出有控制終端的進程, 也列出沒有控制終端的進程, j 表示列出與作業相關的信息, 同時在上圖中可以看出當我們殺死這個進程組中的組長時, 此時該組還是任然存在的(該組中的成員還依舊在). 利用 jobs 命令可以查看后臺相關進程
3. 作業
????shell分前臺和后臺運行的不是進程, 而是作業或者進程組. 一個前臺作業可以由多個進程構成, 一個后臺作業也可以由多個進程組成, shell可以一次運行一個前臺作業和任意多個后臺作業, 這就叫做作業控制.
????作業和進程組的區別: 在作業中某個進程創建了一個子進程, 該子進程屬于該進程組, 但是該子進程不屬于這個作業.一旦作業結束, shell就把自己提到前臺, (子進程還在, 但是子進程不屬于改作業), 如果原來的前臺進程還在, (如果原來的子進程還沒有終止), 它將自動成為一個后臺進程. 此時我們就可以理解當我們在前臺起一個新作業時, 此時shell無法執行, 那是因為shell被放到了后臺, 而當改作業退出的時候, shell就被提到了前臺.
#include<stdio.h>
#include<unistd.h>int main()
{pid_t id = fork();if(id == -1){return 1;}else if(id == 0){while(1){printf("child(%d)# I am running\n", getpid());}sleep(2);}else{int i = 5;while(i){printf("parent(%d)# I am going to dead\n", getpid());i--;}sleep(2);}return 0;
}
3. 會話
????會話是一個或多個進程組的集合, 一個會話可以有一個控制終端. 這通常是登錄到其上的終端設備或者是偽終端設備. 建立與控制終端連接的會話首進程是控制進程. 一個終端下的幾個進程組可以被分為一個前臺作業以及一個或多個后臺作業.所以一個會話中應該包括一個控制進程, 一個前臺進程組和多個后臺進程組
????通過上面的一些解釋, 我們可以得知, 一個作業或者一個進程組由多個進程組成, 而一個會話則由一個前臺進程組和多個后臺進程組構成.
(1)會話相關接口
????????????????????????????????
????該函數用于建立一個新的會話, 如果調用函數的進程不是一個進程的組長, 那么就建立新的會話. 此時該進程會變成新的會話首進程, 而此時該進程就會成為新會話中的唯一的一個進程. 而該進程會成為一個新進程組長, 該進程 ID 等于調用該進程的進程 ID. 該進程沒有控制端, 如果在調用 setsid 前該進程有一個控制端, 那么原有的這個控制端將會變成一個普通文件不再是控制終端
????????????????????????????????
????查看進程的會話編號SID, 當 pid = 0 時, 函數返回調用進程會話首進程進程組 ID
????一個作業有多個進程構成, 一個會話由多個作業構成, 其中包括一個前臺作業和多個后臺作業, 建立一個會話, 就是會建立一個話首進程, 而刪除話首進程, 該會話將會退出.
(2)相關命令
???? 1)將一個作業放到后臺去執行時, 只需在可執行程序后面加一個 & 即可. 例如 sleep 100 | sleep 200 &
???? 2)查看后臺作業: jobs
???? 3) 將一個后臺作業由前臺提到后臺 Ctrl + Z, bg 作業號即可
4. 作業控制相關信號
???? 后臺作業是不能讀取終端輸入的, jobs 命令可以查看后臺相關作業. fg 可以將某個作業從后臺提到前臺, 但是此時如果該作業是停止狀態, 則給作業發送一個 SIGCONT 信號使得該作業能夠繼續運行. bg 將某個停止的后臺作業在后臺運行, 也需要給作業發送一個 SIGCONT 信號. 此時該作業就可以在后臺運行了.后臺作業不能從終端讀取數據, 但是后臺作業可以往終端寫數據
二.守護進程
1. 相關概念
????守護進程也叫做精靈進程, 是運行在后臺的一個特殊進程. 它獨立于控制終端, 自成進程組, 自成會話, 并且周期性地執行某些任務或者周期性地處理某些發生的事件. Linux 下大多數的服務器就是用守護進程來實現的.
???? Linux下有一些進程沒有控制終端, 不能直接和用戶交互. 其他進程都需要用戶登錄或運行時創建, 但是守護進程不受用戶登錄或注銷的影響, 它會一直運行.
????通過 ps 命令查看系統中的進程, 其中 PPID 是父進程編號, PID 是當前進程編號, PGID 是進程組ID, SID 是會話 ID, TTY 表示終端名稱, TPGID 表示會話組ID, STAT 表示進程狀態, UID表示用戶ID, COMMAND 表示命令字符串可以看到 凡是 TPGID 為 -1 的都是守護進程, COMMAND 用 [] 括起來的都是內核線程, 這些線程都是在內核中創建, 沒有用戶代碼, 沒有程序文件以及命令行, 通常以 k 開頭. 守護進程一般以 d 結尾的名字
2. 守護進程的創建
#include<unitd.h>
pid_t setsid();
調用成功時返回創建的會話ID, 失敗時返回 -1
????該函數在調用的時候必須保證調用的進程不能是進程組組長, 為了保證該點, 就先 fork 然后再讓子進程去執行 setsid 即可. 其中在創建會話時, 子進程就會自成組長, 即子進程的 id 就會成為其組長的id, 并且該進程也會自成會話, 即會話 ID就是該進程的 ID, 并且如果當前進程有一個會話終端的時候, 它將會失去原有的會話終端, 即原來的會話終端還是打開的, 仍然可以讀寫, 但是將會變成一個普通文件, 不再是控制終端了.
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>void mydaemon()
{int fd0;pid_t id;struct sigaction sa;//1. 屏蔽umaskumask(0);id = fork();if(id < 0){perror("fork");exit(1);}//2. fork 父進程退出, 子進程運行if(id > 0){exit(0);}if(setsid() < 0){perror("setsid");exit(1);}//3. 初始化sasigemptyset(&sa.sa_mask);sa.sa_handler = SIG_IGN;sa.sa_flags = 0;//4. 對SIGCHLD信號忽略, 防止產生僵尸進程if(sigaction(SIGCHLD, &sa, NULL) < 0){perror("sigaction");exit(1);}fd0 = open("/dev/null", O_RDWR);close(0);dup2(fd0, 1);dup2(fd0, 2);
}int main()
{mydaemon();while(1){sleep(1);}return 0;
}
????????
????關閉當前終端, 打開另外一個終端時, 繼續查看, 會發現原來的會話還存在, 于是我們可以得出結論, 守護進程單獨成組, 單獨成回話, 不受控制終端控制
????????