php守护进程热更新,服务器编程--守护进程
守護(Daemon)進程又叫作“精靈進程”,雖然守護進程這個名字更為常用,但是個人感覺還是精靈進程較為機靈可愛些。服務器進程一般都是守護進程,這類進程的一個顯著特點就是無交互地在后臺進程。注意:這里所說的無交互并不是說真的不能和這類進程打交道,不能控制其運行,那樣他們還能提供什么服務?而是說不能通過傳統的終端用類似shell的交互方式控制其運行。
那么怎么創建守護進程呢?咋們就邊看代碼邊講解。
1
2? #include 3? #include 4? #include 5? #include 6? #include 7? #include 8? #include 9? #include 10? #include 11? #include 12? #include 13? #include 14? #include 15
1-15行: 加載必要的頭文件,其實這些頭文件并不是隨意羅列的,而是當需要時再添加,具體方法是需要調用某個庫函數或者系統調用時,用man查找它被定義的頭文件的路徑,然后添加之。如果在編譯的時候顯示某個函數沒有被定義的錯誤,這時也可以用man查找所需的頭文件之所在。有的時候甚至需要用grep到/usr/include目錄下面查找變量或者函數的定義。
16? void daemonize(const char *prgname, ...)
17? {
18????????? va_list args;
19????????? char buf[512];
20????????? int pid, i;
21????????? struct sigaction act, oldact;
22????????? struct rlimit lim;
23
24????????? /* Detach controlling terminal */
25????????? if ((pid = fork()) < 0)
26????????????????? exit(1);
27????????? else if (pid > 0)
28????????????????? _exit(0);
29????????? setsid();
30
25-29行:從當前進程fork出一個子進程,然后當前進程退出。如果當前進程是shell從前臺啟動的的話,當當前進程退出的時候,子進程將變成孤兒進程,接著自動被啟動進程(init)收養,當然它所在的進程組也將從前臺轉為后臺。調用完setsid()之后,子進程將創建一個新的會話和進程組,sid和gpid都是子進程的pid,因為子進程已經和當前進程不屬于一個會話,那么與會話相關聯的控制終端也不復存在。如果你足夠細心,你可能注意到了這段代碼中用了兩個進程退出函數exit和_exit,為什么要如此呢?_exit并不會執行由atexit或者on_exit注冊的進程退出回調函數,除此之外,它和exit并沒有區別。假設用戶在調用daemonize把當前進程守護化之前注冊過進程退出回調函數,如果fork成功而當前進程通過調用exit退出,那么回調函數將被執行,而這時執行回調函數也許是不當的,因為子進程并沒有退出,當子進程退出的時候也許還將執行一遍回調函數。exit和_exit的選用正是為了保證進程退出回調函數被且盡被執行一次。以下對exit和_exit的選用也是基于此目的,遇到時將不再贅述。事實上,daemonize函數應該盡早調用,最好不要再其前面做太多的非必要操作,類似注冊進程退出回調函數的舉動應該盡量避免。
31????????? /* Avoid owning controlling terminal again */
32????????? memset(&act, 0, sizeof(act));
33????????? act.sa_handler = SIG_IGN;
34????????? sigemptyset(&act.sa_mask);
35????????? sigaction(SIGHUP, &act, &oldact);
36????????? if ((pid = fork()) < 0)
37????????????????? exit(1);
38????????? else if (pid > 0)
39????????????????? _exit(0);
40????????? /* Wait for the death of it's parent. */
41????????? while (getppid() != 1)
42????????????????? ;
43????????? sigaction(SIGHUP, &oldact, NULL);
44
31-43行:這段代碼的意義何在呢?有些UNIX操作系統(如SVR4)的會話首進程打開一個終端設備時,如果其所在會話組并沒有控制終端,那么這個終端設備將自動成為這個會話組的控制終端。通過這次的fork而產生的孫子進程因為不是會話首進程,也就失去了為此會話設置控制終端的能力。當會話首進程退出的時候可能向其所在會話組的所有進程發送SIGHUP信號,而SIGHUP信號的默認處理函數是結束進程。為了防止孫子進程因此意外結束,忽略SIGHUP信號直到子進程退出,孫子進程被啟動進程(init)收養。我查看了Linux內核的相關代碼,發現只有當進程被SIGSTP終止時才會被發送SIGHUP和SIGCONT信號,所以此段關于信號的處理部分在Linux下是無效的,也許其他操作系統行為有異,姑且加之。
45????????? /* Deal with file operations */
46????????? umask(0);
47????????? if (chdir("/") < 0)
48????????????????????????? exit(1);
49????????? if (getrlimit(RLIMIT_NOFILE, &lim) < 0)
50????????????????? exit(1);
51????????? if (lim.rlim_cur == RLIM_INFINITY)
52????????????????? lim.rlim_cur = 1024;
53????????? for (i = 0; i < lim.rlim_cur; i ++) {
54????????????????? if (close(i) < 0 && errno != EBADF)
55????????????????????????? exit(1);
56????????? }
57????????? if (open("/dev/null", O_RDWR) < 0
58????????????????????????? || dup(0) < 0
59????????????????????????? || dup(0) < 0)
60????????????????? exit(1);
61
45-60行:設置文件掩碼為0,改變當前工作目錄到系統根目錄,關閉所有打開的文件描述符,并把標準輸入、標準輸出和標準錯誤輸出重定向到空設備(/dev/null),使他們保持沉默。
62????????? /* Ignore all traditional signals */
63????????? for (i = 1; i < 32; i ++)
64????????????????? sigaction(i, &act, NULL);
65
62-65行:忽略所有的傳統信號,當然SIGKILL信號是無法忽略的,所以我也沒有檢查返回值。按照設計慣例:SIGHUP用來熱更新系統配置;SIGTERM用來結束進程,這個信號一般是需要捕捉并處理的,不然被SIGKILL強制殺死的滋味可不好受哦。(修正:62-65行的操作最好免除,因為大部分信號是不希望被忽略的,如SEGV)。
66????????? /* Initialize the log file */
67????????? va_start(args, prgname);
68????????? vsnprintf(buf, sizeof(buf), prgname, args);
69????????? va_end(args);
70????????? openlog(buf, LOG_CONS | LOG_PID, LOG_DAEMON);
71? }
72
66-71行:一般的服務器都需要用日志(log)記錄守護進程的狀態等信息以備分析和調試之用,這段代碼就是打開到系統日志服務器(syslogd)的連接,并設置記錄守護進程的進程名和pid。
至此,進程的守護化就順利完成了。
服務器程序一般都具有排他性,換句話說就是一個操作系統上只允許有一個守護進程實例存在。以下代碼實現了這個功能:
96 int uniqued(const char *prgname)
97 {
98???????? char buf[512];
99???????? int fd, retval = -1;
100
101???????? assert(prgname != NULL);
102???????? snprintf(buf, sizeof(buf), "/var/run/%s.pid", prgname);
103???????? if ((fd = open(buf, O_RDWR | O_CREAT)) < 0)
104???????????????? goto out;
105???????? if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
106???????????????? if (errno == EWOULDBLOCK)
107???????????????????????? retval = 0;
108???????????????? else
109???????????????????????? unlink(buf);
110???????????????? goto err;
111???????? }
112???????? if (ftruncate(fd, 0) < 0)
113???????????????? goto err;
114???????? snprintf(buf, sizeof(buf), "%ld\n", (long)getpid());
115???????? if (write(fd, buf, strlen(buf)) != strlen(buf))
116???????????????? goto err;
117???????? retval = fd;
118
119 out:
120???????? return retval;
121
122 err:
123???????? while (close(fd) < 0 && errno == EINTR)
124???????????????? ;
125???????? goto out;
126 }
遵從慣例,記錄有守護進程進程號的文件被放在/var/run/目錄下,并被命名為:守護進程名.pid。函數uniqued利用排他文件鎖保證了守護進程實例的單一性。
總結
以上是生活随笔為你收集整理的php守护进程热更新,服务器编程--守护进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery调用WebService返回
- 下一篇: php flash斗地主,flash斗地