fork()函數(shù)由linux系統(tǒng)調(diào)用,創(chuàng)建一個(gè)與原來(lái)進(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)一般(不絕對(duì))會(huì)優(yōu)先給子進(jìn)程分配資源,例如存儲(chǔ)數(shù)據(jù)和代碼的空間,然后把父進(jìn)程的所有值都復(fù)制到子進(jìn)程中,只有少數(shù)值與原來(lái)的進(jìn)程的值不同。也就是父進(jìn)程與子進(jìn)程的代碼和數(shù)據(jù)是一致的,只有通過(guò)判斷fork()的返回值來(lái)判斷父子進(jìn)程從而在父子進(jìn)程中實(shí)現(xiàn)不能的功能(下面細(xì)說(shuō))
注:父進(jìn)程在執(zhí)行fork()函數(shù)之后,父子進(jìn)程都會(huì)執(zhí)行fork()的下一句代碼
fork()
fork函數(shù)執(zhí)行一次,返回兩個(gè)值,在父進(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)程中的不同返回值,可以實(shí)現(xiàn)父子進(jìn)程的不同功能,如下偽代碼:
int main(){
...
pid_t pid = fork();//調(diào)用fork
if(pid == 0){ //如果在子進(jìn)程(因?yàn)閜id為0),執(zhí)行if的代碼
...
}
else if(pid > 0){ //如果pid>0,即當(dāng)前進(jìn)程為父進(jìn)程,
... //執(zhí)行else if的代碼
}
return 0;
}
注:
- 一般情況下系統(tǒng)會(huì)優(yōu)先為子進(jìn)程分配空間資源,然后將父進(jìn)程的數(shù)據(jù)與代碼復(fù)制給子進(jìn)程,而且子進(jìn)程一般會(huì)先執(zhí)行完,所以如果在不設(shè)置阻塞集或通過(guò)代碼讓子進(jìn)程延時(shí)執(zhí)行的話,父進(jìn)程不能捕捉到子進(jìn)程的結(jié)束信號(hào)。
- 子進(jìn)程在調(diào)用exit()函數(shù)之后或者在執(zhí)行完代碼之后會(huì)主動(dòng)發(fā)出
SIG_CHLD
信號(hào)
回收SIG_CHLD信號(hào)
子進(jìn)程在退出時(shí)會(huì)向父進(jìn)程釋放進(jìn)程結(jié)束的信號(hào)(SIG_CHLD
),在回收該信號(hào)(信號(hào)回收請(qǐng)看之前的文章)時(shí)我們需要注意,由于系統(tǒng)會(huì)優(yōu)先為子進(jìn)程分配資源,所以一般情況下,子進(jìn)程會(huì)先執(zhí)行完,所以可能父進(jìn)程還沒(méi)有對(duì)SIG_CHLD
信號(hào)進(jìn)行捕捉,子進(jìn)程就已經(jīng)把信號(hào)釋放了,這樣在父進(jìn)程中就會(huì)顯示沒(méi)有捕捉到SIG_CHLD
信號(hào)。如下示例:
#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)程捕獲信號(hào)
{
struct sigaction act;
act.sa_handler = catch_child; //捕獲函數(shù)綁定
act.sa_flags = 0;
sigaction(SIGCHLD,&act,NULL); //捕獲SIG_CHLD函數(shù)
}
}
getchar();
return 0;
}
執(zhí)行上述程序,你會(huì)發(fā)現(xiàn),子進(jìn)程創(chuàng)建成功了,但是在父進(jìn)程中卻沒(méi)有捕獲到子進(jìn)程終止的信號(hào),這就是因?yàn)樽舆M(jìn)程執(zhí)行的比較快,還沒(méi)有等到父進(jìn)程執(zhí)行到sigaction(SIGCHLD,&act,NULL)
去捕獲SIG_CHLD
信號(hào)時(shí),子進(jìn)程就已經(jīng)將SIG_CHLD
信號(hào)釋放了,如此便導(dǎo)致父進(jìn)程沒(méi)有不做到任何子進(jìn)程終止的信號(hào),有興趣的讀者可以嘗試在子進(jìn)程執(zhí)行的代碼里加入sleep(1)
讓子進(jìn)程沉睡1秒鐘,然后父進(jìn)程就可以捕獲到信號(hào)了,當(dāng)然這是為了證明我給出的出現(xiàn)該現(xiàn)象的原因,在實(shí)際解決問(wèn)題時(shí)我們不會(huì)這么做,而是采用下面描述的方法。
我們可以通過(guò)設(shè)置阻塞集的方法來(lái)解決上述問(wèn)題,具體實(shí)現(xiàn)步驟為:
- 在程序的開(kāi)始將
SIG_CHLD
加入阻塞集。 - 在父進(jìn)程執(zhí)行的代碼中,將
SIG_CHLD
信號(hào)移出阻塞集,然后執(zhí)行sigaction
函數(shù)捕捉SIG_CHLD
信號(hào)。
不難看出上述方法的總體思路就是:將SIG_CHLD
信號(hào)阻塞至父進(jìn)程開(kāi)始捕捉該信號(hào)為止。具體示例如下:
#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; //聲明信號(hào)集
sigemptyset(&set); //將信號(hào)集置空
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)程捕獲信號(hào)
{
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信號(hào)
sleep(1);
}
}
getchar();
return 0;
}
執(zhí)行上述程序,就會(huì)將子進(jìn)程成功捕獲退出的信號(hào)打印出來(lái)。