fork()函數(shù)由linux系統(tǒng)調(diào)用,創(chuàng)建一個與原來進(jìn)程幾乎完全相同的進(jìn)程。調(diào)用fork的進(jìn)程稱為父進(jìn)程,調(diào)用fork()函數(shù)之后產(chǎn)生的進(jìn)程稱為子進(jìn)程。在調(diào)用fork()函數(shù)之后,系統(tǒng)一般(不絕對)會優(yōu)先給子進(jìn)程分配資源,例如存儲數(shù)據(jù)和代碼的空間,然后把父進(jìn)程的所有值都復(fù)制到子進(jìn)程中,只有少數(shù)值與原來的進(jìn)程的值不同。也就是父進(jìn)程與子進(jìn)程的代碼和數(shù)據(jù)是一致的,只有通過判斷fork()的返回值來判斷父子進(jìn)程從而在父子進(jìn)程中實現(xiàn)不能的功能(下面細(xì)說)
注:父進(jìn)程在執(zhí)行fork()函數(shù)之后,父子進(jìn)程都會執(zhí)行fork()的下一句代碼
fork()
fork函數(shù)執(zhí)行一次,返回兩個值,在父進(jìn)程中返回子進(jìn)程的進(jìn)程ID,在子進(jìn)程中返回0,然后父子進(jìn)程都執(zhí)行fork的下一句代碼。函數(shù)聲明如下:
pid_t fork(void);(其中pid_t為進(jìn)程ID的類型聲明)
根據(jù)fork在父子進(jìn)程中的不同返回值,可以實現(xiàn)父子進(jìn)程的不同功能,如下偽代碼:
int main(){
...
pid_t pid = fork();//調(diào)用fork
if(pid == 0){ //如果在子進(jìn)程(因為pid為0),執(zhí)行if的代碼
...
}
else if(pid > 0){ //如果pid>0,即當(dāng)前進(jìn)程為父進(jìn)程,
... //執(zhí)行else if的代碼
}
return 0;
}
注:
- 一般情況下系統(tǒng)會優(yōu)先為子進(jìn)程分配空間資源,然后將父進(jìn)程的數(shù)據(jù)與代碼復(fù)制給子進(jìn)程,而且子進(jìn)程一般會先執(zhí)行完,所以如果在不設(shè)置阻塞集或通過代碼讓子進(jìn)程延時執(zhí)行的話,父進(jìn)程不能捕捉到子進(jìn)程的結(jié)束信號。
- 子進(jìn)程在調(diào)用exit()函數(shù)之后或者在執(zhí)行完代碼之后會主動發(fā)出
SIG_CHLD信號
回收SIG_CHLD信號
子進(jìn)程在退出時會向父進(jìn)程釋放進(jìn)程結(jié)束的信號(SIG_CHLD),在回收該信號(信號回收請看之前的文章)時我們需要注意,由于系統(tǒng)會優(yōu)先為子進(jìn)程分配資源,所以一般情況下,子進(jìn)程會先執(zhí)行完,所以可能父進(jìn)程還沒有對SIG_CHLD信號進(jìn)行捕捉,子進(jìn)程就已經(jīng)把信號釋放了,這樣在父進(jìn)程中就會顯示沒有捕捉到SIG_CHLD信號。如下示例:
#include <unistd.h>
#include <iostream>
using namespace std;
void catch_child(int num){
std::cout << "1" << std::endl;
pid_t pid = waitpid(-1,NULL,WNOHANG); //等待子進(jìn)程終止,
if(pid > 0){
std::cout << "success kill sun process"<< pid <<std::endl;
}
}
int main(int argc, const char** argv) {
for (size_t i = 0; i < 2; i++)
{
pid_t pid = fork(); //創(chuàng)建子進(jìn)程
if(pid<0){
std::cout << "creat pid erro" << std::endl;
}
if(pid == 0) //子進(jìn)程中執(zhí)行的代碼
{
std::cout << "son" << "ppid:"<<getppid()<<std::endl;
}
else //父進(jìn)程捕獲信號
{
struct sigaction act;
act.sa_handler = catch_child; //捕獲函數(shù)綁定
act.sa_flags = 0;
sigaction(SIGCHLD,&act,NULL); //捕獲SIG_CHLD函數(shù)
}
}
getchar();
return 0;
}
執(zhí)行上述程序,你會發(fā)現(xiàn),子進(jìn)程創(chuàng)建成功了,但是在父進(jìn)程中卻沒有捕獲到子進(jìn)程終止的信號,這就是因為子進(jìn)程執(zhí)行的比較快,還沒有等到父進(jìn)程執(zhí)行到sigaction(SIGCHLD,&act,NULL)去捕獲SIG_CHLD信號時,子進(jìn)程就已經(jīng)將SIG_CHLD信號釋放了,如此便導(dǎo)致父進(jìn)程沒有不做到任何子進(jìn)程終止的信號,有興趣的讀者可以嘗試在子進(jìn)程執(zhí)行的代碼里加入sleep(1)讓子進(jìn)程沉睡1秒鐘,然后父進(jìn)程就可以捕獲到信號了,當(dāng)然這是為了證明我給出的出現(xiàn)該現(xiàn)象的原因,在實際解決問題時我們不會這么做,而是采用下面描述的方法。
我們可以通過設(shè)置阻塞集的方法來解決上述問題,具體實現(xiàn)步驟為:
- 在程序的開始將
SIG_CHLD加入阻塞集。 - 在父進(jìn)程執(zhí)行的代碼中,將
SIG_CHLD信號移出阻塞集,然后執(zhí)行sigaction函數(shù)捕捉SIG_CHLD信號。
不難看出上述方法的總體思路就是:將SIG_CHLD信號阻塞至父進(jìn)程開始捕捉該信號為止。具體示例如下:
#include <unistd.h>
#include <iostream>
using namespace std;
void catch_child(int num){
std::cout << "1" << std::endl;
pid_t pid = waitpid(-1,NULL,WNOHANG); //等待子進(jìn)程終止,
if(pid > 0){
std::cout << "success kill sun process"<< pid <<std::endl;
}
}
int main(int argc, const char** argv) {
sigset_t set; //聲明信號集
sigemptyset(&set); //將信號集置空
sigaddset(&set,SIGCHLD); //將`SIG_CHLD`加入其中
sigprocmask(SIG_BLOCK,&set,NULL); //加入阻塞集
for (size_t i = 0; i < 2; i++)
{
pid_t pid = fork(); //創(chuàng)建子進(jìn)程
if(pid<0){
std::cout << "creat pid erro" << std::endl;
}
if(pid == 0) //子進(jìn)程中執(zhí)行的代碼
{
std::cout << "son" << "ppid:"<<getppid()<<std::endl;
}
else //父進(jìn)程捕獲信號
{
struct sigaction act;
act.sa_handler = catch_child; //捕獲函數(shù)綁定
act.sa_flags = 0;
sigemptyset(&set); //移出阻塞集
sigaction(SIGCHLD,&act,NULL); //捕獲SIG_CHLD函數(shù).
//防止父進(jìn)程退出而不能捕獲子SIG_CHLD信號
sleep(1);
}
}
getchar();
return 0;
}
執(zhí)行上述程序,就會將子進(jìn)程成功捕獲退出的信號打印出來。