在我們學習完進程和線程之後,我們知道進程與進程之間是相互獨立的,而線程與線程之間是有一定的聯繫的。那麼進程與進程之間就真的沒有關係嗎?接下來就來學習一下進程間關係以及一個新的名詞守護進程。
在學習進程間關係之前,我們來學習幾個名詞:
1、進程組/作業/會話
(1)進程組
我們在學習進程的時候就知道,每一個進程都有自己唯一的進程ID,那麼今天我就要說,每一個進程除了有一個進程ID之外,還屬於一個進程組,那麼相應的也就會有一個組ID。進程組是一個或多個進程的集合。通常,它們與同一任務相關聯,可以是接收來自同一終端的各種信號。每個進程組有一個唯一的進程組ID,每個進程組都可以有一個組長進程。那麼組長進程可以創建一個進程組,創建該組中的進程,然後終止。也就是說:只要在某個進程組中有一個進程存在,那麼該進程組就存在,這與組長存在與否沒有關係。
在上圖中,有幾個命令要給大家來解釋一下。
‘&’:表示將進程組放至後臺執行;
PID爲進程ID,PGID爲進程組ID,那麼進程ID和進程組ID的就是組長ID,所以組長爲5183號進程,由上圖也可看出,組長進程是進程組中的第一個進程;
而這個組中有三個進程:5183 5184 5185;
當kill -9 5183 即殺死組長進程之後,再查看進程發現當殺死組長進程之後,進程組仍存在;
ps選項:
a :不僅列出當前用戶的進程,而且也列出其他用戶的進程;
x :表示不僅列有控制終端的進程,也列出無控制終端的進程;
J :表示列出與作業控制相關的進程;
ps axj |grep sleep 列出與sleep相關的所有進程;
grep -v grep 表示不顯示含有’grep’的任務;
在此要說明的是,ctrl C 終止是終止了一個前臺進程組(也可以說是終止了一個前臺任務)。
(2)作業
Shell分前後臺來控制的不是進程而是作業或者是進程組。一個前臺作業可以由多個進程組成,一個後臺作業也可以由多個進程組成。Shell可以運行一個前臺作業和任意多個後臺作業,這稱爲作業控制。
作業與進程組的區別:如果作業中的一個進程有創建了一個子進程,那麼該子進程不屬於作業,但屬於進程組。
一旦作業結束,shell就把自己提到前臺(子進程還在,但不屬於作業),如果原來的前臺進程還存在(如果這個子進程還沒終止),它自動變爲後臺進程。
當你運行一個死循環的代碼時,你就會發現,此時如果你想要輸入一個命令,那麼是不可能的,因爲當你在進行這個作業的時候,shell被逼到了後臺,所以它是無法執行你的命令的,但當你Ctrl C結束掉你得當前進程後,shell就又被提到了前臺,所以就可以繼續接受用戶輸入。
(3)會話
會話是一個或多個進程組的集合。一個會話可以有一個控制終端。這通常是登錄到其上的終端設備(在終端登錄情況下)或僞終端設備(在網絡登錄情況下),建立與終端控制連接的會話首進程被稱爲控制進程。一個會話中的幾個進程組可被分爲一個前臺進程組合以及一個或多個後臺進程組。所以一個會話中,應該包括控制進程(會首進程),一個前臺進程組合任意後臺進程組(也就是說,會話由會話首進程,控制終端,前臺、後臺作業組成)。
會話的默認首進程是bash。一旦會話首進程被殺,則會話依舊存在,但終端被殺,會話 。
每打開一個終端,就相當於新建了一個會話。
SID就是會話ID,也就是3939,三個進程都屬於同一個進程組,同一個會話。
那麼3939是誰呢?通過命令ps aux |grep -E 3939 可以看出3939是bash。
2、作業控制
會話與進程組“shell可以同時運行一個前臺進程和任意多個後臺進程”是不太全面的。事實上,shell分前後臺來控制的不是進程而是作業(job)或者是進程組(Process Grope)。一個前臺作業可以由多個進程組成,一個後臺作業也可以由多個進程組成,shell可以同時運行一個前臺作業和任意多個後臺作業,這稱爲作業控制。
下面就來介紹幾個作業控制的常見命令:
3、作業控制相關的信號
總結一下:後臺進程不能從前臺(終端下)讀取數據,但是後臺進程可以向終端寫數據。
4、守護進程
(1)初始守護進程
守護進程也叫精靈進程,是運行在後臺的一種特殊進程。它獨立於控制終端並且週期性的執行某種任務或者等待處理某些發生的事件。Linux的大多數服務器都是由守護進程實現的。比如 ftp服務器,ssh服務器,web服務器,http等。同時,守護進程完成許多系統任務,如作業規劃進程crond(定時任務)等。
Linux系統啓動時會啓動很多系統服務進程,這些系統服務進程沒有控制終端,不能直接與用戶交互。其他進程都是在用戶登錄或者運行程序時創建,在運行結束或用戶註銷時終止,但系統服務進程(守護進程)不受用戶註銷登錄的影響,它們一直運行着,着中進程有一個名字叫守護進程。
下面我們採用ps axj 命令查看系統中的進程:
ps axj |more
- 凡是TPGID一欄寫着-1的都是沒有控制終端的進程,也就是守護進程。
- 在COMMAND一欄用[]括起來的名字表示內核線程,這些線程在內核裏創建,沒有用戶控件代碼,因此沒有程序文件名和命令行,通常採用以k開頭的名字,表示kernel.
- init進程我們已經很熟悉了,udevd負責維護/dev目錄下的設備文件,acpid負責電源管理, syslogd 負責維護/var/log下的日誌文件。
- 可以看出,守護進程通常採用以d結尾的名字,表示Daemon。
(2)創建守護進程
創建守護進程最關鍵的一部是調用setsid()函數來創建一個新的會話,並且成爲會話Leader。
#include<unistd.h>
pid_t setsid(void);
該函數調用成功返回新創建的會話ID(其實也就相當於當前進程的id),出錯就返回-1。
注意:調用該函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不是進程組的Leader也很容易,只要先fork一下,然後再調用setsid就行了。fork創建的子進程和父進程再同一個進程組中,進程組的Leader必然也是該組的第一個進程,所以子進程不可能是改組的第一個進程,在子進程中調用setsid就不會有什麼問題了。
成功調用該函數的結果是:
- 創建一個新的會話,當前進程成爲會話Leader,當前進程的id就是會話id。
- 創建一個新的進程組,當前進程成爲進程組的Leader,當前進程的id就是進程組的id。
- 如果當前進程原本有一個控制終端,則它失去這個控制終端,成一個沒有控制終端的進程。所謂失去控制終端是指原來的控制終端仍然是打開的,仍然可以讀寫,但只是一個普通的打開文件而不是控制終端了。
接下來實現一個守護進程:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<signal.h>
#include<stdlib.h>
void mydaemon(void)
{
umask(0);//1、調用umask將文件模式創建屏蔽字設置爲0
//2、調用fork,父進程退出exit
pid_t id = fork();
if(id > 0 )
{
exit(1);
}
setsid(); //3、創建新會話
signal(SIGCHLD,SIG_IGN);//4、忽略SIGCHLD信號
if(chdir("/")<0) //5、將工作目錄設置爲根目錄
{
printf("child dir error\n");
return;
}
close(0); //6、關閉文件描述符
close(1);
close(2);
//int fd0;
//close(0);
//fd0=open("/dev/null",o_RDWR);
//dup2(fd0,1);
//dup2(fd0,2);
}
int main()
{
mydaemon();
while(1)
{
sleep(1);
}
return 0;
}
在庫中,其實已經提供了一個函數,只要調用該函數,就可以自動創建一個守護進程,這個函數就是daemon()函數。
int daemon(int nochdir , int noclose);
#include<stdio.h>
#include<unistd.h>
int main()
{
daemon(0,0);//(關閉更改目錄,關閉文件描述符)
while(1) ;
}
Linux向外提供網絡服務以守護進程方式。