在我們需要一個程序成為后臺的守護進程時,一般是通過fork 來創建一個子進程,隨之父進程結束,然后再通過 setsid 來使子進程脫離父進程所屬的進程組和會話。(通俗一點也可以這么認為,我們需要給這個已經長大的孩子重新找個家,并且跟之前家庭斷絕關系,跟人有點類似,雖然關系斷絕了,但親爹是誰這個事實是誰也改變不了的,所以不管發生什么,親爹的ID 號是不會變的。因為之前的父母或家庭都有可能被滅亡導致這個孩子因為沒有家也被餓死。)這樣才能不至于當終端發送 Ctrl+c或是該會話中斷后守護進程也隨之結束,從而達到守護的目的。
我們一般在寫程序時都有這樣的習慣,先是初始化,然后再開始工作。但是在使用 fork 的時候這一點就要注意了。創建子進程的時候系統會從父進程中將數據拷貝一份到子進程中,如果 fork之前我們 new 或者 malloc 了一塊內存,并且保存了一個指向該內存的指針,那么在創建子進程的時候,也會將這個指針拷貝過去,但此處只拷貝了指針并沒有拷貝內存塊。所以當父進程結束的時候這塊內存會被釋放,那么此時在子進程中的指針則指向了一塊已經被釋放的內存了。有一段這樣的代碼
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
之后運行結果為
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
我認為說明了幾個問題:
1:從父進程吧數據拷貝到子進程中的時候,不按照拷貝構造函數的方式來拷貝,直接進行字節的拷貝。
2:對于指針的拷貝,只拷貝指針的值,不拷貝指針所指向的內容(malloc 和 new 方式申請的內存)。
另外,當進程以 return 的方式結束的時候,對于沒有釋放對象,系統會調用將其釋放并調用他們的析構函數,如果以 exit 方式退出則直接釋放內存不調用析構函數。有點類似于 delete 和 free 的區別。
再回到我們上面所談的地方,此時如果有一個連接在一個類的構造函數中實現,斷開連接在它的析構函數中實現。如果這個類在 fork 之前已經 new 了出來,那么當父進程以return的方式結束的時候,它的析構函數就會斷開連接,所以子進程中也無法使用該連接了。這里只是比較隨意的談到了一個例子,可能還會有更多的問題因這個原因而產生,所以在使用 fork 的時候我們要想到這一點從而盡可能的避免 BUG 的產生。