進程組
在linux中,每一個進程還屬於一個進程組,一個進程組有多個進程組成,通常,他們聯合起來作業,可以接受從同一個終端下的各種信號。並且每一個進程組都有一個進程組ID。在Linux中叫做PGID,一個進程組由多個進程組成,進程組中有一個組長進程,組長進程的標識是他的進程ID和組ID相同。一般一個進程組中第一個創建的進程就是組長進程。一個進程組中只要還有一個進程存在,那麼這個進程組就存在。
我們可以通過ps 命令,來查看進程信息。
-a 列出所有用戶的進程
-x 不僅列出有終端控制的,也列出沒有終端控制的進程。
-j列出與作業控制相關的信息
PID:進程ID
PPID:父進程ID
PGID:所屬進程組的ID
SID:會話ID
TTY:相關控制終端
作業
Shell分前後臺來控制的不是進程而是作業或者叫做進程組,Shell可以運行一個前臺作業和任意多個後臺作業,這就叫做作業的控制。
作業和進程組的區別:如果一個進程組中的某個進程fork出來了子進程,那麼子進程屬於進程組,而不屬於作業。
現在我們就可以理解爲什麼當我們運行一個進程之後,再輸入命令,就沒有作用了,原因就是因爲讓我們啓動一個作業以後,默認是放在前臺的,而前臺只能有一個進程,因此Shell就被提到後臺了,無法接受我們的指令。
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 pid_t pid;
6 if(pid=fork()==0)
7 {
8 //child
9 while(1)
10 {
11 printf("hello, world\n");
12 sleep(2);
13 }
14
15 }
16 else
17 {
18 sleep(5);
19 }
20
21 return 0;
22 }
來看一下結果:
輸入./a.out按下回車,就會在前臺起一個作業,父進程sleep(5)秒,在這5秒期間,Shell被提到後臺,無法接受我們輸入的指令,五秒之後,父進程退出,那麼Shell變成前臺作業,可以接受我們的ls指令,子進程屬於進程組但是不屬於作業,因此它在後臺一直向屏幕上打印hello,world。
會話
會話是由一個或者多個進程組的集合,一個會話可以有一個控制終端,這通常是登陸到其上的終端設備(終端登陸情況下)和僞終端情況(xshell網絡登陸),建立與終端控制的會話首進程我們稱之爲控制進程,一般爲bash,一個會話的進程組可以分爲一個前臺進程和任意多個後臺進程,所以一個會話包括一個控制進程(會話首進程),一個前臺進程組和任意多個後臺進程組。
作業控制
事實上,Shell分前後臺來控制的不是進程而是作業,Shell可以運行一個前臺作業和任意多個後臺作業,這就叫做作業控制。
- 在啓動程序的時候,和後面跟上&選項表示把進程放在後臺運行。
- jobs命令可以查看後臺作業
- fg+作業編號,就可以把後臺作業提到前臺。
- 一個前臺進程我們也可以用ctrl+z讓他變成stop狀態放在後臺,然後再用bg命令讓它運行起來。此時還是在後臺
- ctrl+c只能結束掉前臺作業。
下面來看一個例子,在前臺新起一個作業cat,它的作用是從標準輸入中讀取數據,然後輸出到標準輸出中
當我們把cat放到後臺運行時,再查看它的狀態
此時cat的狀態變成stopped,爲什麼會這樣呢,原因是bash不允許後臺進程從標準輸入中讀取數據,就會給cat發送一個SIGTTIN信號,該信號的默認處理動作是讓進程停止。但是後臺進程是運行寫的。前面我們寫的一個小程序子進程就是後臺作業,一直再向屏幕上寫,驗證了這一點,當我們嘗試給一個停止的進程發送信號的時候,並不會立即處理,而是等到進程運行起來的時候,纔會去處理。但是linux中有強大的9號信號,可以插死除了殭屍進程之外的各種狀態進程。
守護進程
守護進程也叫做精靈進程,是在運行期間的一種特殊的進程,它獨立於控制終端並且週期性的執行某種任務或者等待某些事件的發生,這是一種很有用的進程,再linux中很多的服務都是由守護進程實現的,比如,http服務器,ssh服務器,Web服務器,守護進程完成着很多的系統任務,比如說作業規劃進程crond。
linux系統啓動的時候有很服務進程啓動,這些進程沒有控制終端,無法和用戶進行交互,其他進程都是在用戶登陸或者程序運行時創建,在運行結束或者用戶註銷的時候終止,但是守護進程不受用戶登陸和註銷的影響,他們一直在運行。
通過ps -ajx|more來查看守護進程
- 凡是TPGID一欄寫着-1的都是沒有控制終端的進程,也就是守護進程。
-在COMMAND一欄中用[]括起來的表示內核線程,沒有用戶態代碼,因此沒有程序文件名和命令行,通常以K開頭的表示kernal。
-我們可以看出來,守護進程通常是以d結尾的,表示Daemon。
創建守護進程
創建守護進程用setsid函數創建一個新的Session,併成爲Session Leader。
#include<unistd.h>
setsid(void);
這個函數成功調用新的Session的id,也就是當前進程的id,出錯返回-1
注意:調用這個函數之前,當前進程不允許是所屬進程組的組長,否則調用出錯,必須要出子進程,然後再調用setsid,這是因爲fork出子進程,子進程也屬於這個進程組,但肯定不是組長進程,因爲守護進程自成一個進程組,它的ID肯定是組長ID,如果創建它的進程是組長進程,那麼再內核中就會有兩個ID一樣的進程中,這是錯誤的。
這個函數調用成功的結果是:
- 創建一個新的Session,當前進程成爲Session Leader,當前進程的id就是Session的id,即自成一個會話
- 創建一個新的進程組,並且自成組長
- 如果當前進程有一個控制終端,那麼它將會失去這個控制終端,沒有控制終端
基於上面幾點,我們來自己寫一個守護進程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<signal.h>
5 #include<fcnatl.h>
6 #include<stdlib.h>
7 #include<unistd.h>
8
9
10 void mydaemon()
11 {
12 umask(0);//文件創建模式的屏蔽字設爲0
13 pid_t pid;
14 if(pid=fork()==0)
15 {
16 //child
17 //三個重定向
18 setsid();
19 signal(SIGCHLD,SIG_IGN);
20
21 //忽略SIGCHLD信號
22 if(chdir("./")<0)
23 {
24 perror("chdir\n");
25 exit(0);
26 }//更改當前工作目錄爲根目錄
27 close(0);
28 int fd=open("/dev/null",O_RDWR);
29 dup2(fd,1);
30 dup2(fd,2);
31 }
32 else
33 {
34 //father
35 exit(0);
36 }
37 }
38 int main()
39 {
40
41 mydaemom();
42 while(1);
43
44
45 return 0;
46 }
看看結果:
這是我們根據守護進程的特性,自己模擬實現的一個創建守護進程的函數,當然有封裝好的接口拱我們使用。
1 #include<unistd.h>
2 daemon(0,0);
daemon()函數也是必須fork出子進程纔可以調用,這裏兩個參數默認都是0,表示更改進程默認打開的三個文件描述符,和更改進程的工作目錄。