【說明:利用fork複製的新進程,並未所有都複製了父進程的東東,這個在結尾時還會說明,先有個瞭解】linux
關於fork進程,能夠用下面這種的通俗方式來理解:shell
首先咱們先來理解一個概念,這個以後在最後還會進行總結的:數組
fork新建立的進程,會複製父進程的全部信息(代碼段+數據段+堆棧段+PCB),可是「全部」並不是絕對,仍是有少部分信息是不同的,另外還能夠理解,每個進程都有本身獨立的4GB的地址空間(對於32位系統來講)bash
子進程與父進程的區別在於: 一、父進程設置的鎖,子進程不繼承 對於排它鎖,若是說子進程會共享父進程的鎖的話,那就有矛盾了。數據結構
二、各自的進程ID和父進程ID不一樣異步
三、子進程的未決告警被清除【瞭解】函數
四、子進程的未決信號集設置爲空集【瞭解】學習
【說明:關於 fork函數,它有一個特徵:調用一次,返回兩次,就如上面返回值的說明】動畫
如上圖所說:fork()出來的子進程成功,對於子進程來講則返回0;而對於父進程來講返回子進程ID,這是有緣由的,對於進程中的PCB保存了pid和ppid,因此對於子進程來講,有辦法知道pid和ppid,而對於父進程來講,若是不返回子進程的id,則就沒法知道新建立的進程的號碼,由於PCB中並不會保存子進程的ID列表,這樣就會讓PCB膨脹,因此這也是有緣由的。ui
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
printf("before fork pid = %d\n", getpid());//打印當前進程ID,也就是原始父進程
pid_t pid;
pid = fork();//產生一個新的進程,注意:它裏會有兩個進程執行
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
{//證實是父進程
printf("this is parent pid=%d childpid=%d\n", getpid(), pid);//getpid()爲當前父進程的ID,而pid則爲新建立的進程id,也就是子進程
}
else if (pid == 0)
{//證實是子進程
printf("this is child pid=%d parentpid=%d\n", getpid(), getppid());//getpid()爲當前子進程的ID,getppid()爲當前子進程所屬父進程的ID
}
return 0;
}
複製代碼
編譯運行:
緣由是因爲當執行父進程以後,它就退出了,這時子進程執行時,這時子進程的父進程就變爲init進程,因此就變成了1,若是咱們讓父進程輸出延時一下,保證子進程執行時父進程沒退出,就如咱們的預期了:
此次再看效果:
對於fork函數,可能有一點比較難以理解,爲啥它一次調用會有兩次返回呢?這裏再來用文字來解釋一下:fork成功意味着建立了一個進程副本,意味着也就有兩個進程了,兩個進程都要執行各自相應的動做,因此兩個進程都得要返回,實際上在內核中,是在各自的進程地址空間中返回的:
關於上面的第二個注意點,,若是父進程退出了,子進程尚未退出,咱們將子進程稱爲孤兒進程,這時會將子進程託孤給init進程。 關於第三點,其中提到了「殭屍進程」,用程序來看下現象:
這時,查看一下當前的進程狀態:
殭屍狀態,咱們儘可能得避免它,避免它的方法之一,能夠採用一個系統調用----signal(信號,關於它,以後會詳述,這裏只是先了解一下):
這時,編譯運行,再看效果:
下面再來理解一來系統是如何實現fork()的.
實際系統實現時,並未真正把全部的數據(代碼段+數據段+堆棧段+PCB)都複製一份,只是爲了方便理解,咱們能夠認爲是數據(代碼段+數據段+堆棧段+PCB)都複製了一份,實際上代碼段是隻讀的,是能夠被共享的,每一個進程只要保存一個指向這個資源的指針既可,這能夠加快進程的建立速度,大大提升了效率
而對於須要修改的進程纔會複製那份資源,對於linux而言,它是基於頁的的方式進行復制的,並沒將全部數據都進行復制,只是複製須要頁,其它頁是不會複製的,因此咱們得正確理解「每一個進程有本身獨立的4GB(對於32位系統來講)的地址空間」,實際上不被修改的數據是共享的,對於這個理論,大體瞭解下,也是爲了加深對fork()函數的理解。
父進程打開兩個文件:
這時經過fork()函數新建了一個子進程,這時它共享父進程的文件,其結構以下:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid());
int fd;
fd = open("test.txt", O_WRONLY);//父進程打開一個文件
if (fd == -1)
ERR_EXIT("open error");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
{
printf("this is parent pid=%d childpid=%d\n", getpid(), pid);
write(fd, "parent", 6);//父進程往文件中寫入內容
sleep(1);//睡眠是爲了不孤兒進程的產生,保證子進程執行的時候,父進程沒有退出
}
else if (pid == 0)
{
printf("this is child pid=%d parentpid=%d\n", getpid(), getppid());
write(fd, "child", 5);//子進程往文件中寫入內容
}
return 0;
}
複製代碼
先建立一個"test.txt",裏面是空內容:
也就是能夠說明,子進程是共享父進程打開的文件表項的。
注意:有時候可能會test.txt的內容輸出以下:
上面這種輸出並無按照咱們的預想,可能的緣由是跟兩個進程的靜態問題形成的,這個問題比較複雜,能夠這樣理解:也就是還沒等子進程執行,父進程就已經結束了,這時子進程的文件偏移量會從0開始,因此以前父進程寫入了parent,因爲它退出來,子進程從0的位置開始寫,因此最終輸出就如上圖所示了,爲了保證如咱們預期來輸出,能夠將睡眠時間加長上些,保證子進程執行時,父進程沒有退出,以下:
上節中咱們知道,fork的拷貝機制是copy on write,圖中所說的exec函數,是指加載一個新的程序來執行,這個下面會有介紹到,先大概瞭解下,若是說沒有copy on write機制的話,那父子進程都有本身獨立的進程空間,也就是子進程須要完徹底全的拷貝父進程的地址空間,而若是子進程中執行exec的話,等於它被一個新的程序替換掉了,它根本不須要拷貝父進程的數據,因此就會形成地址空間的浪費,這時才引入了vfork,也就是vfork以後子進程在執行exec以前,是不會拷貝父進程的地址空間的,無論子進程有沒有改寫數據,它是一個歷史問題(說得有點抽象,下面會以具體代碼來一一闡述的)。
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int gval = 100;
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid());
pid_t pid;
pid = fork();//這裏是用的copy on write機制
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
{
sleep(1);//它的目的是爲了讓子進程先對gval進行++操做,以便觀察父進程是否會受影響
printf("this is parent pid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
sleep(3);
}
else if (pid == 0)
{
gval++;//子進程來改寫數據
printf("this is child pid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
}
return 0;
}
複製代碼
編譯運行:
其緣由也就是因爲fork()是採用copy on write的機制,下面用圖來解析一下上面的結果:
下面將其改成vfork來實現:
編譯運行:
這個輸出結果,能夠代表,vfork產生子進程,當改寫數據時也不會拷貝父進程的空間的,父子是共享一份空間,因此當子進程改寫的數據會反映到父進程上。
另外這段程序中出現了一個「段錯誤」,這是由於:
這時,編譯再運行,就不會有錯誤了:
另外執行exec函數也同樣,關於它的使用,以後再來介紹。
提示:vfork是一個歷史問題,瞭解一下既可,實際中不多用它!
在演示vfork時,提到「子進程必須馬上執行_exit」,那若是用exit(0)退呢?
編譯運行:
咱們一般會將return 0 與exit(0)劃等號,但若是在vfork()中,仍是劃等號麼?
編譯運行:
那exit與_exit有啥區別呢?下面來探討下,在探討以前,先來回顧一下進程的五種終止方式:
下面以一個圖來講明exit與_exit的區別:
區別一:exit是C庫中的一個函數;而_exit是系統調用。
區別二:exit在調用內核以前,作了「調用終止處理程序、清除I/O緩衝」;而_exit是直接操做內核,不會作這兩件事。
編譯運行:
exit(0)至關於return 0;因此可想將上面return 0換爲exit(0)也是同樣的能在屏幕上打印出來,那若是換成_exit(0)呢?
這時編譯運行:
這時就正常顯示了:
另外對於exit來講,它會調用「終止處理程序」,所謂「終止處理程序」,就是指在程序結束的時候會調用的函數代碼段,這些代碼段,須要咱們安裝才能夠,能夠用以下函數:
其中傳遞的參數是函數指針。
編譯運行:
若是換成是_exit()呢?
編譯運行:
插一句:對於fork函數,有一個問題需進一步闡述一下,以便加深對它的理解:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid());
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
{
printf("this is parent pid=%d childpid=%d\n", getpid(), pid);
sleep(3);
}
else if (pid == 0)
{
printf("this is child pid=%d parentpid=%d\n", getpid(), getppid());
}
return 0;
}
複製代碼
輸出:
對於上面這段程序,就是上節中學習過的,可是有個問題值得思考一下,爲啥fork()以後,不是從"before fork"從main的第一行起輸出,而是從fork()以後的代碼中去輸出,這時由於fork()以後,拷貝了「代碼段+數據段+堆棧段+PCB」,也就是兩個進程的信息幾乎都是同樣,而因爲堆棧段+PCB幾乎是同樣的,因此它會維護當前運行的信息,因此每一個進程會從fork()以後的代碼繼續執行,這一點須要理解。
另外,再看一個跟fork()相關的程序:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
fork();
fork();
fork();
printf("ok\n");
return 0;
}
複製代碼
編譯運行:
這是爲何呢?由於第一個fork()時,會產生兩個進程,這時這兩個進程都會執行它後面的代碼,也就是第二個fork(),這時就有四個進程執行第二個fork()了,一樣的,這時四個進程就會執行它下面的代碼,也就是第三個fork(),這時就再產生四個進程,總共也就是八個進程了,這個比較很差理解,好好想一下!
最後,咱們來講明一下execve函數,這個在上面介紹vfork()函數時,已經提到過了,它的做用是:替換進程映像,這時對它進行使用說明:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int gval = 100;
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid());
pid_t pid;
pid = vfork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
{
printf("this is parent pid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
}
else if (pid == 0)
{
char *const args[] = {"ps", NULL};
execve("/bin/ps", args, NULL);//將子進程徹底替換成/bin/ps中的ps進程命令,因此這句話以後的代碼就不會執行了,由於是徹底被替換了
gval++;
printf("this is child pid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
}
return 0;
}
複製代碼
編譯運行:
對於fork()函數,它建立了一個新進程,新進程與原進程幾乎是同樣的,而對於shell命令,如:
對於shell命令,它自己就是一個進程,要想執行ls程序,則需去加載ls程序,這時shell命令進程則需fork()建立一個新進程,而咱們知道新建立的進程與原進程幾乎是同樣的,也就意味着新的進程的代碼仍是跟shell程序自己是同樣的,也就沒法執行ls程序,因此,這時咱們只有將新進程用ls程序替換,也就是用exec系列函數來替換,這也就是它的意義所在。
編譯運行:
那若是被execlp函數替換後的進程ID是否會發生變化呢?爲了說明這個問題,咱們先編寫一個打印進程ID的程序:
hello.c:
編譯,會用execl替換咱們編寫的程序,來論證咱們提出的問題:
再來用execl替換成咱們寫的hello程序:
這時運行:
若是將程序作一點小改動,以下:
這時編譯運行:
這時爲何呢?這是由於execlp函數執行失敗了,因此沒有替換成功,能夠打印一下錯誤信息:
編譯運行:
這時由於:
其中linux的環境變量以下:
下面就具體對execlp系列的每一個函數進行研究,先從總體上來看一下這些函數:
下面用代碼來演示一下execlp與execvp這兩個函數用法的差異:
編譯運行:
換成不帶l的函數,看下它的使用方式:
其運行結果跟上面同樣,這就是帶l的函數與不帶l函數的使用區別。
下面來講明一下函數參數的意義:
下面,咱們來研究一下下面兩個函數的區別:
編譯運行:
這是由於execl中的程序名須要帶上全路徑,而execlp不須要定全路徑,會自動在環境變量中去搜尋,這就是帶p與不帶p的區別,因而咱們看一下ls命令的路徑:
因而,將這個路徑替換一下:
再次編譯運行:
因此,對於下面這兩個函數也就明白啥區別了:
這裏就不作實驗了,對於exec系列的函數,最後還剩一個execle函數:
下面就以實際代碼來解析下這個參數的含義:
hello.c仍是以前的代碼,再貼出來:
編譯運行:
下面咱們將hello.c來輸出程序的環境變量,實際上有對應的shell命令可以輸出,效果以下:
因而改裝咱們的hello.c:
而對於environ的數據結構是這樣的:
這時,編譯一下執行hello:
這時,咱們再執行以前替換hello的函數,這時也會輸出環境信息:
這時咱們將execl函數,改成execle,並傳遞咱們本身的環境信息:
編譯運行:
至此,咱們已經把exec系列相關的函數的區別,就已經所有學完了,能夠好好體會下,對於這些函數,下面再來講明下:
execve咱們能夠看一下幫助:
最後,再來補充一個知識,在以前咱們學過了fcntl函數,該函數功能很強大,其中還漏了一個沒有學到,就是:
編譯運行:
若是沒有用fcntl設置,咱們是能看到./hello程序的輸出結果的,這也就是FD_CLOEXEC標誌的做用了,它會對exec系列的函數產生影響,記住這點就能夠了。
其實,打開一個文件時,也能夠帶上FD_CLOEXEC:
【說明:關於信號,很快就會有一個專題來仔細研究它,如今能夠簡單認爲:它是一種異步通知事件】
【說明:若是父進程沒有查詢子進程的退出狀態,子進程是沒有辦法真正徹底退出的,這時子進程的狀態就稱爲殭屍狀態,該進程就叫殭屍進程】
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>//提供wait函數聲明
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
sleep(3);//子進程休眠,是爲了看到父進程會等待子進程退出
printf("this is child\n");
exit(100);
}
printf("this is parent\n");
int status;
wait(&status);等待子進程退出
return 0;
}
複製代碼
看一下編譯運行效果,下面用動畫來展示,以便能體現到wait的意義:
從圖中能夠感覺到,父進程雖然是已經輸出了,可是一直是等到子進程退出了才退出,這也就是wait會讓父進程去查子進程的退出狀態,從而避免了殭屍進程的出現。
編譯運行:
對於這些狀態信息,能夠經過調用系統宏來查詢,下面具體來介紹下:
編譯運行:
下面咱們能夠用abort函數,來模擬子進程非法退出的狀況:
這時再編譯運行:
實際上,對於子進程非法退出的,還能夠判斷得再仔細一些,由於有好幾種狀況能夠形成子進程非法退出,如上圖所示,一是由於捕獲信號而終止,二是被暫停了,具體用法以下:
編譯運行:
【說明:上面的這些宏在sys/wait.h頭文件裏定義】
對於上面剛學完的wait,它是等待隨意的進程退出,由於一個父進程能夠有多個子進程,若是隻要有一個子進程退出,父進程的wait就會返回;
而waitpid則能夠等待特定的進程退出,這是二者的區別,下面就來具體學習下這個函數的用法:
對於waitpid的pid參數的解釋與其值有關:
實際上能夠經過查看man幫助獲得這個信息:
能夠將咱們以前的程序用waitpid替換一下,效果同樣:
也一樣,用它來改裝咱們以前的程序,對於父進程,實際上只有一個子進程,就能夠直接傳子進程的id既可,只等待這個子進程:
效果同樣:
好比:waitpid(-100,&status,0)的意思就是,等待進程組ID=100裏面的任一一個子進程。
最後,關於wait和waitpid,進行一個總結:
另外,對於僵進程,已經被提到過好幾回了,最後再來總結一下:
實際上,它就等於用代碼去執行咱們在命令行中敲的那些shell命令,下面就以實際代碼來認識這個函數的使用:
編譯運行:
實際上,system()函數是調用"/bin/sh -c",以下:
對於這個函數的使用,其實沒什麼難的,可是這個函數頗有表明性,咱們能夠經過它來綜合運用咱們所學習東西,這樣其實仍是挺有意義的,接下來,會本身實現一個跟system一樣的功能,來達到理解system函數的實現原理:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int my_system(const char *command);//本身實現有system函數聲明
int main(int argc, char *argv[])
{
my_system("ls -l | wc -w");//這裏改用本身實如今system
return 0;
}
int my_system(const char *command)
{
pid_t pid;
int status;
if (command == NULL)
return 1;
  if ((pid = fork()) < 0)
status = -1;//出現不能執行system調用的其餘錯誤時返回-1
  else if (pid == 0)
{//子進程
execl("/bin/sh", "sh", "-c", command, NULL);//替換成sh進程
exit(127);//若是沒法啓動shell運行命令,system將返回127,由於若是成功替換了以後,是不會執行到這句來的
}
else
{//父進程會等到子進程執行完
while (waitpid(pid, &status, 0) < 0)
{
if (errno == EINTR)//若是是被信號打斷的,則從新waitpid
continue;
status = -1;
break;
}
     //這時就順利執行完了
}
return status;
}
複製代碼
編譯運行:
在描述它以前,首先得先了解兩個概念:進程組、會話期:
而它裏面有bash shell進程組,裏面只有bash進程:
而一個會話期,實際上就對應一個終端,當咱們打開多個虛擬終端時,能夠用tty來查看終端數:
而守護進程是跟控制終端無關的,而且是在後臺執行的,若是想讓咱們在shell中啓動的進程變成守護進程,則應該將它放到會話期當中:
那這時,咱們須要一個建立新的會話期的函數,其實是系統函數,它爲setsid(),經過man來查看一下它的說明:
這就意味着,咱們在建立一個新的會話期以前,須要準備一個進程,保證該進程不是一個進程組組長,那如何保證呢?因爲咱們運行的shell命令的父進程多是進程組組長,因此須要讓父進程退出,這樣就能夠保證fork出來的子進程不是進程組組長,從而能夠建立一個新的會話期了,總結一下上面說的流程:
按照上面的步驟下面以具體代碼來實現一個守護進程:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int setup_daemon(void);
int main(int argc, char *argv[])
{
return 0;
}
int setup_daemon(void)
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)//將父進程退出,保證子進程不是進程組組長
exit(EXIT_SUCCESS);
setsid();//若是走到這,表明是子進程,因爲它不是一個進程組組長,因此能夠建立一個新的會話期
return 0;
}
複製代碼
當咱們用setsid()建立一個新的會話期以後,會有一個什麼樣的影響呢,仍是接着看它的說明介紹:
也就是以下圖所示:
其實上面的程序就已經實現了一個守護進程,咱們調用一下運行看下:
編譯運行:
咱們來查看下進程:
守護進程一般是在系統運行而運行的,一般將當前目錄改成根目錄,由於有可能守護進程是在某個shell提示符下運行的, 那麼當前目錄就是shell提示符所在的目的, 就拿咱們建立的這個守護進程而言,它的當前目錄爲:
這樣,系統管理員就沒法umount這個目錄,由於守護進程是學期在後期運行的,這個目錄不該該做爲它的環境,因此這就產生了建立守護進程的第四個步驟:
修改代碼:
最後還有一個步驟:
【說明:/dev/null表示空設備,這裏就是把日誌記錄到空設備裏,就是不記錄日誌。】
這時再運行,若是咱們往屏幕輸出內容,這時是看不到內容的,由於已經將標準輸出重定向了空設備:
實際上linux上已經有現成的方法能夠建立一個守護進程了,以下:
在運行它以前,咱們來看下如今應該有幾個守護進程了:
先將其都殺掉,以便來觀察調用系統的建立守護進程是否成功:
這時,再運行:
對於系統的這個函數,都是傳遞的0,若是傳遞1會怎樣呢?
編譯運行:
實際上,對於咱們寫的守護進程,也能夠模擬成跟系統調用方式同樣,修改程序以下:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int setup_daemon(int nochdir, int noclose);//模擬系統建立守護進程的函數聲明
int main(int argc, char *argv[])
{
setup_daemon(1, 1);//這時改用跟調用系統建立守護進程的本身實現的函數
printf("test ...\n");
for (;;) ;
return 0;
}
int setup_daemon(int nochdir, int noclose)
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
exit(EXIT_SUCCESS);
setsid();
if (nochdir == 0)//實現很簡單,作下參數判斷既可
chdir("/");
if (noclose == 0)
{
int i;
for (i=0; i<3; ++i)
close(i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
return 0;
}
複製代碼
編譯運行:
【提示:在建立守護進程時,不重定向至空設備其實對於開發期間便於調試,若是等程序發佈了以後,就得重定向了!】