摘要:
在之前的《服務器編程入門(5)Linux服務器程序規范》中,我們提到過將服務器程序后台化,這就是本節將要討論的守護進程.
本節主要關注一下問題:
1 什么是守護進程?
2 守護進程的啟動方法有哪些?
3 如何創建一個守護進程?
1 什么是守護進程?
在后台運行,且不與任何控制終端關聯的進程。
守護進程不與作業控制、終端會話管理、終端產生信號等發生交互,也可以避免在后台運行的守護進程非預期地輸出到終端。
兩個特點:
- 守護進程執行中的信息不顯示在任何一個終端上
- 守護進程不被終端產生的無用信號所中斷
在理解更多關於守護進程的概念之前,我們先了解一下進程、進程組、會話期和控制終端的關系。
- 每一個進程有一個進程ID,每個進程都屬於一個進程組
- 每個進程組有一個組長進程組長進程的ID等於進程組ID
- 會話期是一個或多個進程組的集合,一個會話期可以有一個單獨的控制終端(其中,只有一個前台進程組可以控制終端的交互)
- 從shell中啟動的每個進程將繼承一個終端,以便進程與用戶交互,同時繼承父進程的會話期和進程組ID,因此子進程會受發給該會話期或進程組的信號的影響。
守護進程與普通進程的區別如下圖所示:
2 守護進程的啟動方法有哪些?
- 在系統啟動階段,許多守護進程又系統初始化腳本啟動。這些腳本通常位於/etc目錄或以/etc/rc開頭的某個目錄中。由這些腳本啟動的守護進程一開始時就擁有超級用戶特權。
- 許多網絡服務器由inetd超級服務器啟動。
- cron守護進程按照規則定期執行一些程序。由它啟動執行的程序同樣作為守護進程運行。
- at命令用於指定將來某個時刻運行程序,由它啟動的程序同樣作為守護進程。
- 從用戶終端或前台或后台啟動。
3 使用庫函數daemon創建守護進程
首先我們使用庫函數daemon創建守護進程,然后研究一下守護進程的創建過程,並實現一個守護進程化函數,達到和庫函數daemon相同的效果。
函數:daemon
聲明:
#include <unistd.h> int daemon(int nochdir, int noclose);
作用:通過在服務器程序中調用它,可以把一個普通進程轉變為守護進程。
參數說明:
If nochdir is zero, daemon() changes the process’s current working directory to the root directory ("/"); otherwise,
If noclose is zero, daemon() redirects standard input, standard output and standard error to /dev/null; otherwise, no changes are made to these file descriptors.
Demo:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <time.h> #include <fcntl.h> #include <string.h> #include <error.h> int main(int argc, char* argv[]) { time_t t; int fd; // 將當前進程變成守護進程 if (daemon(0, 0) == -1) { perror("daomon error"); exit(EXIT_FAILURE); } while(1) { //這時工作目錄已經被daemon函數切換到了系統根目錄下 fd = open("daemon.log", O_WRONLY|O_CREAT|O_APPEND, 0644); if (fd == -1) { perror("open daemon.log error"); exit(EXIT_FAILURE); } t = time(0); char *buf = asctime(localtime(&t)); write(fd, buf, strlen(buf)); //向daemon.log文件中寫入當前時間 close(fd); sleep(60); // 每隔60s寫入一次 } }
運行截圖:
執行ps命令發現,並沒有名為testDaemon的守護進程,主要原因是daemon函數會將當前工作目錄切換到/目錄下,而普通用戶沒有權限在系統根目錄創建文件。
所以實際上出錯在fd = open("daemon.log", O_WRONLY|O_CREAT|O_APPEND, 0644); 這里,又因為守護進程是不和當前終端交互的,所以沒有看到報錯信息。
現在我們切換到root用戶執行程序,運行截圖:
4 創建守護進程過程分析,用自己實現的myDaemon函數創建守護進程
守護進程創建過程:
代碼實現:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <time.h> #include <fcntl.h> #include <string.h> #include <error.h> #include <signal.h> #define MAXFD 64 extern int daemon_proc; int myDaemon(int ,int ); int main(int argc, char* argv[]) { time_t t; int fd; if (myDaemon(0, 0) == -1) { perror("daomon error"); exit(EXIT_FAILURE); } while(1) { fd = open("daemon.log", O_WRONLY|O_CREAT|O_APPEND, 0644); if (fd == -1) { perror("open daemon.log error"); exit(EXIT_FAILURE); } t = time(0); char *buf = asctime(localtime(&t)); write(fd, buf, strlen(buf)); close(fd); sleep(60); } fprintf(stderr, "Hello world!\n"); } int myDaemon(int nochdir, int noclose) { int i; pid_t pid; if ( (pid = fork()) < 0 ) return -1; else if (pid) { /* parent terminated */ _exit(0); } /* child 1 continues... */ if (setsid() < 0) /* become session leader */ return -1; signal(SIGHUP, SIG_IGN); /* ignore SIGHUP singal */ if ( (pid = fork()) < 0 ) return -1; else if (pid) { _exit(0); /* child 1 terminated */ } /* child 2 continues... */ daemon_proc = 1; /* use syslog instead of fprintf to stderr */ if (nochdir == 0) chdir("/"); /* change working directory */ if (noclose == 0) { /*close off file descriptors*/ for (i = 0; i < MAXFD; i++) close(i); /* redirect stdin, stdout, and stderr to /dev/null */ open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); } umask(0); }
運行截圖:
參考資料:
《UNIX網絡編程 卷1:套接字聯網API(第3版)》