1.孤兒進程
1.1 產生原因
- 父進程先於子進程終止,就會產生孤兒進程,在某些開發環境下(需要子進程自己申請空間),孤兒進程的危害遠遠大於殭屍進程,因爲孤兒進程充滿了不確定性。
1.2 創建一個孤兒進程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
// 創建一個子進程
pid_t pid;
pid = fork();
if(pid > 0)
{
printf("Parent pid:%d\n", getpid());
}
else if(pid == 0)
{
while(1)
{
printf("Child pid:%d\n", getpid());
sleep(1);
}
}
return 0;
}
1.3 預防孤兒進程
- 開發多進程模型時,要對進程數量、進程工作狀態、進程工作變化進行檢測,主要檢測父進程,使父進程的生命週期比大於子進程的生命週期。
1.4 init 核心進程將把所有失去父進程的子進程託管給圖形化進程
- 注意:在ubuntu14.04下,所有失去父進程的子進程是由init進程託管的;在ubuntu16.04下,所有失去父進程的子進程將由圖形化進程託管。
- 將init進程kill掉會導致關機或者重啓;將圖形化進程kill掉會註銷計算機。
2.進程間關係
2.1 從init進程到用戶程序進程的流程
- 開機運行init進程,init進程fork許多子進程,其中一個使用exec族函數運行shell(終端)程序,在該進程中再次fork子進程,用來在終端上運行用戶自己生產的可執行文件;
- init->fork->exec->shell->fork->exec->app->fork->子進程
2.2 進程組
- 1.Linux下所有的進程都是強親緣關係,每一個進程都有進程組,進程組的概念非常最要,內核是通過進程組來有效管理多進程的。
- 2.獲取進程組ID可以使用以下兩個函數:
- pid_t getpid(pid_t pid):參數爲想要知道組id的進程id,返回值爲該進程的進程組id;
- pid_t getpgrp(void):沒有參數,因此返回值爲調用該函數的進程所在的進程組id;
- 3.進程組組長的標誌:pid == pgid
2.3 setpgid 函數
- 1.該函數可以將進程轉移到其他組,或者創建一個新的進程組;
- 2.子進程可以通過 setpgid 函數變爲新的進程組的組長(創建新的進程組);一個進程組的組長調用 setpgid 函數嘗試創建新的進程組沒有任何意義;
- 3.int setpgid(pid_t pid, pid_t pgid):第一個參數是要設置的進程的id,第二個參數是要轉移到的進程組的組id;返回值爲0若轉移進程(創建進程組)成功,失敗則返回-1;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
printf("Parent pid:%d\tppid:%d\tpgid:%d\t", getpid(), getppid(), getpgrp());
}
else if(pid == 0)
{
printf("Child pid:%d\tppid:%d\tpgid:%d\t", getpid(), getppid(), getpgrp());
printf("\nSet pgid...\n");
setpgid(getpid(), getpid());
printf("Child pid:%d\tppid:%d\tpgid:%d\t", getpid(), getppid(), getpgrp());
}
else
{
perror("fork call error");
exit(1);
}
return 0;
}
-
4.上述代碼運行結果
- 子進程id不變,ppid不變,組id變爲了自己的pid。
-
4.進程組組長使用 setpgid 函數可以將自己轉移到其他組,但不能創建一個新組;
2.4 每個新的進程都會參加一個會話,只要會話發起者終止,就會強制殺死所有參與者,所以想要進程不受會話限制,就要脫離現有會話(脫離控制終端),讓終端的存在與否與進程無關
- 1.會話:會話是基於連接的;會話的源頭,就是用戶與系統之間連接的啓用;
- 2.會話發起人標誌: pid == gid == sid ;
- 3.想要發起新會話必須是進程組的組長;
2.5 setsid 函數
- 1.任意進程調用該函數能完成兩項工作:
- 1.1 是該進程創建一個新的進程組;
- 1.2 是該進程變爲一個新會話的發起者(創建新會話);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
printf("Parent pid:%d\tppid:%d\tpgid:%d\t", getpid(), getppid(), getpgrp());
}
else if(pid == 0)
{
printf("Child pid:%d\tppid:%d\tpgid:%d\t", getpid(), getppid(), getpgrp());
printf("\nSet pgid...\n");
setsid();
printf("Child pid:%d\tppid:%d\tpgid:%d\t", getpid(), getppid(), getpgrp());
}
else
{
perror("fork call error");
exit(1);
}
return 0;
}
- 2.上述代碼運行結果(輸入 ps ajx 命令查看)
- 子進程的pid與gid及sid(會話id)均相等。
2.6 守護進程(精靈進程daemon)
- 1.生命週期較長(隨着系統持續),週期性執行某個固定任務持續在後臺運行,通常脫離終端獨立運行;
- 2.守護進程是一種人爲的孤兒進程。
2.7 創建一個守護進程
- 1.創建子進程,並終結父進程;
- 2.子進程創建後創建新進程組並創建新會話;
- 3.修改當前進程的工作目錄,這樣做的原因是:當刪除可執行文件後,無論可執行文件是否與其他文件進行交互都能繼續執行;
- 4.改變當前進程的 umask 權限掩碼,目的是爲了能讓守護進程按照自己的功能創建文件;
- 5.關閉無用的文件描述符,比如 STDIN_FILENO ;
- 6.守護進程的核心工作(需要週期性執行的代碼);
- 7.守護進程的推出處理;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
// 1.創建子進程
pid_t pid;
pid = fork();
if(pid > 0)
{
exit(1);
}
else if(pid == 0)
{
// 2.創建新的進程組和新的會話
setsid();
// 3.修改當前工作目錄
chdir("/tmp/");
// 4.改變當前進程的 umask 掩碼權限
umask(0);
// 5.關閉無用描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 6.守護進程核心工作
int fd = open("daemon.log", O_RDWR|O_CREAT, 0664);
time_t tm;
char time_buf[100];
while(1)
{
bzero(time_buf, sizeof(time_buf));
tm = time();
ctime_r(&tm, time_buf);
write(fd, time_buf, sizeof(time_buf));
sleep(3);
}
// 7.守護進程的推出處理
}
else
{
perror("fork error");
exit(1);
}
return 0;
}