進程組
概念:一個或多個進程的集合。
每一個進程除了有一個進程ID外,還屬於一個進程組,同時也只能屬於一個進程組。每個進程組都有一個唯一的進程組ID,且都可以有一個組長進程。一般在進程組中,第一個進程是組長進程。
爲啥要創建進程組呢?爲了方便對進程進行管理。假設要完成一個任務,需要同時併發10個進程,當用戶處於某種原因要終止這個任務時,如若沒有進程組,就需要手動的一個一個的去殺死這10個進程,並且嚴格按照進行間的關係順序,否則會打亂進程間的關係,有了進程組,就可以將這10個進程設置一個進程組,他們共有一個組號(pgrp),並且選取一個進程作爲組長,(通常選取“輩分”最高的那個,通常該進程的ID就是該進程組的ID)。現在就可以通過殺死整個進程組來關閉這10個進程。組長進程可以創建進程組,創建該組中的進程,然後終止。只要在某個進程組中一個進程存在,則該組進程就存在,這與組長進程是否終止無關。
作業
shell分前後臺來控制的是作業或進程組,不是進程。一個前臺作業可以由多個進程組成,一個後臺可以由多個進程組成。
作業控制:shell可以運行一個前臺作業和任意多個後臺作業。
1、 與作業控制有關的信號:
我們cat爲例(把他放在後臺,從終端讀)
(1)由於cat需要讀標準輸入(也就是終端輸入),而後臺進程是不能讀終端輸入的,因此內核發SIGTTIN信號給進程, 該信號的默認處理動作是使進程停止。
[liu153@liu153 7-31_class16]$ cat &
[1] 895
[liu153@liu153 7-31_class16]$ //再嗯回車
[1]+ Stopped cat
[liu153@liu153 7-31_class16]$
(2)jobs命令:查看當前前後前後臺有哪些作業
(3)fg命令:可以將某個作業提至前臺運行:
a、如果該作業的進程組正在後臺運行則提至前臺運行;
b、如果該作業處於停止狀態,則給進程組的每個進程發SIGCONT信號使它繼續行。
參數%1表示將第1個作業提至前臺運行。
cat提到前臺運行後,掛起等待終端輸入,當 輸入hello並回車後,cat打印出同樣的一行,然後繼續掛起等待輸入。緊接着, 如果輸入Ctrl-Z則向所有前 臺進程發SIGTSTP 信號,該信號的默認動作是使進程停止,cat繼續以後臺作業的形式存在。
(4)bg命令:可以讓某個停止的作業在後臺繼續運行。也需要給該作業的進程組的每個進程發SIGCONT信號。cat進程繼續運行,又要讀終端輸入,然而它在後臺不能讀終端輸入,所以又收到SIGTTIN信號而停止。
2、給一個停止的進程發SIGTERM與SIGKILL信號的區別:
(1)kill 命令給一個停止的進程發送SIGTERM信號時並不會立即被處理,而是等到合適的時候處理,默認處理動作是終止進程。
(2)SIGKILL信號既不能被阻塞也不能被忽略 ,也不能用自定義函數捕捉 ,只能按系統的默認動作立刻處理(SIGSTOP 信號也與此類似)。(這樣保證了不管什麼樣的進程都能用 SIGKILL終止或者用SIGSTOP停止, 當系統出現異 常時管理員總是有辦法殺掉有問題的進程或者暫時停掉懷疑有問題的進程。)
作業與進程組的區別:
如果一個作業中的某個進程又創建了一個子進程,該子進程不屬於作業。一旦作業運行結束,shell就把它提到前臺,如果原來前臺進程還存在,它自動變爲後臺進程組。
會話
概念:一個或多個進程組的集合,但只能有一個前臺進程組。
控制進程:建立與控制終端連接的會話首進程。每個會話都有一個會話首領(leader)即創建會話的進程。
sys_setsid()調用能創建一個會話。
注意:只有當前進程不是進程組的組長時,才能創建一個新的會話。
一次會話中應該包括:一個控制進程,一個前臺進程和任意多個後臺進程。
終端
控制終端:會話的領頭進程打開一個終端,之後,該終端就成爲該會話的控制終端。一個會話只能有一個控制終端。
進程屬於一個進程組,進程組屬於一個會話,會話可能有也可能沒有控制終端。一般而言,當用戶在某個終端上登錄時,一個新的會話就開始了。進程組由組中的領頭進程標識,領頭進程的進程標識符就是進程組的組標識符。類似地,每個會話也對應有一個領頭進程。
同一會話中的進程通過該會話的領頭進程和一個終端相連,該終端作爲這個會話的控制終端。一個會話只能有一個控制終端,而一個控制終端只能控制一個會話。用戶通過控制終端,可以向該控制終端所控制的會話中的進程發送鍵盤信號。
當我們打開多個終端窗口時,實際上就創建了多個終端會話。每個會話都會有自己的前臺工作和後臺工作。
查看終端設備
測試代碼:
#include<stdio.h>
#include<unistd.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;
}
終端1運行結果:
[liu153@liu153 7-31_class16]$ ./a.out
fd :0->/dev/pts/2
fd :1->/dev/pts/2
fd :2->/dev/pts/2
終端2運行結果:
[liu153@liu153 7-31_class16]$ ./a.out
fd :0->/dev/pts/0
fd :1->/dev/pts/0
fd :2->/dev/pts/0
[liu153@liu153 7-31_class16]$
終端3運行結果:
[liu153@liu153 7-31_class16]$ ./a.out
fd :0->/dev/pts/3
fd :1->/dev/pts/3
fd :2->/dev/pts/3
[liu153@liu153 7-31_class16]$
守護進程(精靈進程)
是運行在後臺的一種特殊進程,獨立於控制終端並且週期性的執行某種任務或等待處理某些發生的事件。守護進程是一種很有用的進程,Linux的大多數服務器就是用守護進程實現的。 守護進程完成許多系統任務。大多數守護進程以d結尾,凡是中括號括起來的都是內核線程。
root 2 0.0 0.0 0 0 ? S Jul27 0:00 [kthreadd]
root 17 0.0 0.0 0 0 ? S Jul27 0:00 [kacpid]
root 22 0.0 0.0 0 0 ? S Jul27 0:00 [ksuspend_usbd]
root 23 0.0 0.0 0 0 ? S Jul27 0:04 [khubd]
root 24 0.0 0.0 0 0 ? S Jul27 0:00 [kseriod]
root 28 0.0 0.0 0 0 ? S Jul27 0:00 [khungtaskd]
root 30 0.0 0.0 0 0 ? SN Jul27 0:00 [ksmd]
root 38 0.0 0.0 0 0 ? S Jul27 0:00 [pciehpd]
root 40 0.0 0.0 0 0 ? S Jul27 0:00 [kpsmoused]
root 72 0.0 0.0 0 0 ? S Jul27 0:00 [kstriped]
root 1103 0.0 0.0 0 0 ? S Jul27 0:00 [kauditd]
root 2001 0.0 0.0 0 0 ? S< Jul27 0:00 [krfcommd]
守護進程的特性:
1、後臺運行(最重要的)
2、守護進程必須與其運行前的環境隔離開來。
3、啓動方式有其特殊之處:可以在linux系統啓動時從啓動腳本/etc/rc.d中啓動,可以在作業規劃進程cround啓動。還可以由用戶終端(通常是shell)執行。
後臺進程與守護進程的區別
1、後臺進程與特定終端關聯,與會話緊密相聯。
2、守護進程是後臺進程的一種,與終端無關
創建守護進程
1、調用umask將文件模式創建屏蔽字段設置爲0
2、調用fork函數,父進程退出。
原因:1)如果該守護進程是作爲一條簡單的shell命令啓動的,那麼父進程終止使得shell認爲該命令已經執行完畢。
2)保證子進程不是一個 進程組的組長進程。
3、調用setsid創建一個新會話。
setsid會導致:
1)調用進程成爲新會話的首進程。
2)調用進程成爲一個進程組的組長進程 。
3)調用進程沒有控制終端。(再次fork一次,保證 daemon進程,之後不會打開tty設備)
4、將當前工作目錄更改爲根目錄。
5、關閉不在需要的文件描述符。
6、 其他:忽略SIGCHLD信號。
創建守護進程代碼:
測試1:
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include<signal.h>
#include <fcntl.h>
void my_daemon()
{
umask(0);// 設置文件掩碼爲0
pid_t id = fork();
if (id == 0)
{
// child
setsid();// 設置 新會話
chdir("/");// 更換 目錄
close(0);
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);// 註冊子進程退出忽略信號
}
else
{
sleep(14);
exit(0);// 終止父進程
}
close(0);// 關閉標準輸入
int fd0 = open("dev/null", O_RDWR);// 重定向所有標準輸出、 錯誤到/dev/null
dup2(fd0, 1);
dup2(fd0, 2);
}
int main()
{
my_daemon();
while(1);
}
測試2;
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
void create_daemon(void)
{
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0);//設置文件掩碼爲0
if(pid = fork() < 0){
}else if(pid != 0){
exit(0);//第一次fork()終止父進程
}
setsid();//設置新會話
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGCHLD,&sa,NULL) < 0){//註冊子進程退出忽略信號
return ;
}
if(pid = fork() < 0){//爲何要fork兩次?->再次fork,終止子進程,保證孫子進程不是話首進程,從而保證後續不會再和其他終端關聯
printf("fork error !\n");
return ;
}else if(pid != 0){
exit(0);
}
if(chdir("/")<0){//更改工作目錄到根
printf("child dir error\n");
return ;
}
close(0);
fd0 = open("/dev/null",O_RDWR);//關閉標準輸入,重定向所有標準(輸入輸出錯誤)到/dev/null
dup2(fd0,1);
dup2(fd0,2);
}
int main()
{
create_daemon();
while(1)
{
sleep(1);
}
return 0;
}
運行監視:
關閉標準輸入,重定向所有標準(輸入輸出錯誤)到/dev/null