在我們需要一個(gè)程序成為后臺(tái)的守護(hù)進(jìn)程時(shí),一般是通過(guò)fork 來(lái)創(chuàng)建一個(gè)子進(jìn)程,隨之父進(jìn)程結(jié)束,然后再通過(guò) setsid 來(lái)使子進(jìn)程脫離父進(jìn)程所屬的進(jìn)程組和會(huì)話。(通俗一點(diǎn)也可以這么認(rèn)為,我們需要給這個(gè)已經(jīng)長(zhǎng)大的孩子重新找個(gè)家,并且跟之前家庭斷絕關(guān)系,跟人有點(diǎn)類似,雖然關(guān)系斷絕了,但親爹是誰(shuí)這個(gè)事實(shí)是誰(shuí)也改變不了的,所以不管發(fā)生什么,親爹的ID 號(hào)是不會(huì)變的。因?yàn)橹暗母改富蚣彝ザ加锌赡鼙粶缤鰧?dǎo)致這個(gè)孩子因?yàn)闆](méi)有家也被餓死。)這樣才能不至于當(dāng)終端發(fā)送 Ctrl+c或是該會(huì)話中斷后守護(hù)進(jìn)程也隨之結(jié)束,從而達(dá)到守護(hù)的目的。
我們一般在寫(xiě)程序時(shí)都有這樣的習(xí)慣,先是初始化,然后再開(kāi)始工作。但是在使用 fork 的時(shí)候這一點(diǎn)就要注意了。創(chuàng)建子進(jìn)程的時(shí)候系統(tǒng)會(huì)從父進(jìn)程中將數(shù)據(jù)拷貝一份到子進(jìn)程中,如果 fork之前我們 new 或者 malloc 了一塊內(nèi)存,并且保存了一個(gè)指向該內(nèi)存的指針,那么在創(chuàng)建子進(jìn)程的時(shí)候,也會(huì)將這個(gè)指針拷貝過(guò)去,但此處只拷貝了指針并沒(méi)有拷貝內(nèi)存塊。所以當(dāng)父進(jìn)程結(jié)束的時(shí)候這塊內(nèi)存會(huì)被釋放,那么此時(shí)在子進(jìn)程中的指針則指向了一塊已經(jīng)被釋放的內(nèi)存了。有一段這樣的代碼
void test15()
{
class A
{
public:
A():pid_(0)
{
p_malloc = (char*)malloc(100);
printf("pro p_malloc_ = %d \n",p_malloc_);
}
~A()
{
printf("free(p_malloc_) , p_malloc = %d,pid = %d\n", p_malloc_, pid_);
free(p_malloc);
}
A(const A& a)
{
printf("A(const A& a) \n");
p_malloc_ = a.p_malloc_;
}
void set_pid(int pid)
{
cout << "set pid" << endl;
printf("set pid,pid = %d\n", pid);
pid_ = pid;
}
public:
char *p_malloc_;
int pid_;
};
A a;
pid_t pid;
pid = fork();
a.set_pid(pid);
switch(pid)
{
case -1:
cout << "fork() is error" << endl;
exit(-1);
break;
case 0:
//exit(0);
break;
default:
setsid();
pid_t pre_pid = getppid();
printf("child pro pid = %d, pre_pid = %d \n",pid, pre_pid);
sleep(10);
exit(10);
break;
}
}
int main(int argc, char *argv[])
{ test15();
return 0;
}
在 Solrais 下用 CC 編譯
CC Test.cpp -o Test
之后運(yùn)行結(jié)果為
bash-2.03$ Test
pro p_malloc = 305952
set pid
set pid,pid = 23881
child pro pid = 23881, pre_pid = 22819
set pid
set pid,pid = 0
free(p_malloc) , p_malloc = 305952,pid = 0
我認(rèn)為說(shuō)明了幾個(gè)問(wèn)題:
1:從父進(jìn)程吧數(shù)據(jù)拷貝到子進(jìn)程中的時(shí)候,不按照拷貝構(gòu)造函數(shù)的方式來(lái)拷貝,直接進(jìn)行字節(jié)的拷貝。
2:對(duì)于指針的拷貝,只拷貝指針的值,不拷貝指針?biāo)赶虻膬?nèi)容(malloc 和 new 方式申請(qǐng)的內(nèi)存)。
另外,當(dāng)進(jìn)程以 return 的方式結(jié)束的時(shí)候,對(duì)于沒(méi)有釋放對(duì)象,系統(tǒng)會(huì)調(diào)用將其釋放并調(diào)用他們的析構(gòu)函數(shù),如果以 exit 方式退出則直接釋放內(nèi)存不調(diào)用析構(gòu)函數(shù)。有點(diǎn)類似于 delete 和 free 的區(qū)別。
再回到我們上面所談的地方,此時(shí)如果有一個(gè)連接在一個(gè)類的構(gòu)造函數(shù)中實(shí)現(xiàn),斷開(kāi)連接在它的析構(gòu)函數(shù)中實(shí)現(xiàn)。如果這個(gè)類在 fork 之前已經(jīng) new 了出來(lái),那么當(dāng)父進(jìn)程以return的方式結(jié)束的時(shí)候,它的析構(gòu)函數(shù)就會(huì)斷開(kāi)連接,所以子進(jìn)程中也無(wú)法使用該連接了。這里只是比較隨意的談到了一個(gè)例子,可能還會(huì)有更多的問(wèn)題因這個(gè)原因而產(chǎn)生,所以在使用 fork 的時(shí)候我們要想到這一點(diǎn)從而盡可能的避免 BUG 的產(chǎn)生。