1 守護進程的概述
Daemon(守護進程)是運行在后臺的一種特殊進程。它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件。它不需要用戶輸入就能運行而且提供某種服務,不是對整個系統就是對某個用戶程序提供服務。Linux系統的大多數服務器就是通過守護進程實現的。常見的守護進程包括系統日志進程syslogd、 web服務器httpd、郵件服務器sendmail和數據庫服務器mysqld等。
守護進程一般在系統啟動時開始運行,除非強行終止,否則直到系統關機都保持運行。守護進程經常以超級用戶(root)權限運行,因為它們要使用特殊的端口(1-1024)或訪問某些特殊的資源。
守護進程的父進程是init進程,因為它真正的父進程在fork出子進程后就先于子進程exit退出了,所以它是一個由init繼承的孤兒進程。守護進程是非交互式程序,沒有控制終端,所以任何輸出,無論是向標準輸出設備stdout還是標準出錯設備stderr的輸出都需要特殊處理。
守護進程的名稱通常以d結尾,比如sshd、xinetd、crond等。
2 守護進程的創建
首先我們要了解一些基本概念:
進程組 :
- 每個進程都屬于各自的進程組
- 每個進程都有一個進程組號,該號等于該進程組組長的PID號 .
- 一個進程只能為它自己或子進程設置進程組ID號
會話:
會話(session)是一個或多個進程組的集合。setsid()函數可以建立一個新會話:
如果,調用setsid的進程不是一個進程組的組長,此函數創建一個新的會話。
- 此進程變成該會話的首進程
- 此進程變成一個新進程組的組長進程。
- 此進程沒有控制終端,如果在調用setsid前,該進程有控制終端,那么與該終端的聯系被解除。 如果該進程是一個進程組的組長,此函數返回錯誤。
- 為了保證這一點,我們先調用fork()然后exit(),此時只有子進程在運行,子進程不會是進程組的組長
編寫守護進程的一般步驟步驟:
-
在父進程中執行fork并exit推出:保證調用setsid()函數的進程不是進程組組長
-
在子進程中調用setsid函數創建新的會話:調用進程成為新的會話組長和新的進程組長,并與原來的會話和進程組脫離。由于會話對控制終端的獨占性,進程同時與控制終端脫離。
-
再次 fork() 一個子進程并讓父進程退出:現在,進程已經成為無終端的會話組長,但它可以重新申請打開一個控制終端,可以通過 fork() 一個子進程,該子進程不是會話首進程,該進程將不能重新打開控制終端。退出父進程。
-
在子進程中調用chdir函數,讓根目錄 ”/” 成為子進程的工作目錄:使用fork創建的子進程繼承了父進程的當前工作目錄。由于在進程運行中,當前目錄所在的文件系統(如“/mnt/usb”)是不能卸載的,這對以后的使用會造成諸多的麻煩(比如系統由于某種原因要進入單用戶模式)。因此,通常的做法是讓"/"作為守護進程的當前工作目錄,這樣就可以避免上述的問題,當然,如有特殊需要,也可以把當前工作目錄換成其他的路徑,如/tmp。改變工作目錄的常見函數是chdir。
-
在子進程中調用umask函數,設置進程的umask為0:進程從創建它的父進程那里繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。為防止這一點,將文件創建掩模清除:umask(0)
-
在子進程中關閉任何不需要的文件描述符
4、5、6步驟是修改繼承來自父類的資源,順序無所謂。
一張簡單的圖可以完美詮釋之前幾個步驟:
以下程序是創建一個守護進程,然后利用這個守護進程每隔一分鐘向daemon.log文件中寫入當前時間,當守護進程收到 SIGQUIT 信號后退出。
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>static bool flag = true;
void create_daemon();
void handler(int);int main()
{time_t t;int fd;create_daemon();struct sigaction act;act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;if(sigaction(SIGQUIT, &act, NULL)){printf("sigaction error.\n");exit(0);}while(flag){fd = open("/home/mick/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);if(fd == -1){printf("open error\n");}t = time(0);char *buf = asctime(localtime(&t));write(fd, buf, strlen(buf));close(fd);sleep(60);}return 0;
}
void handler(int sig)
{printf("I got a signal %d\nI'm quitting.\n", sig);flag = false;
}
void create_daemon()
{pid_t pid;pid = fork();if(pid == -1){printf("fork error\n");exit(1);}else if(pid){exit(0);}if(-1 == setsid()){printf("setsid error\n");exit(1);}pid = fork();if(pid == -1){printf("fork error\n");exit(1);}else if(pid){exit(0);}chdir("/");int i;for(i = 0; i < 3; ++i){close(i);}umask(0);return;
}
注意守護進程一般需要在 root 權限下運行。
通過
ps -ef | grep 'daemon'
可以看到:
root 26454 2025 0 14:20 ? 00:00:00 ./daemon
并且產生了 daemon.log,里面是這樣的時間標簽
Thu Dec 8 14:35:11 2016
Thu Dec 8 14:36:11 2016
Thu Dec 8 14:37:11 2016
最后我們想退出守護進程,只需給守護進程發送 SIGQUIT 信號即可
sudo kill -3 26454
再次使用 ps 會發現進程已經退出。
3 利用庫函數daemon()創建守護進程
其實我們完全可以利用 daemon() 函數創建守護進程,其函數原型:
#include <unistd.h>int daemon(int nochdir, int noclose);DESCRIPTION The daemon() function is for programs wishing to detach themselvesfrom the controlling terminal and run in the background as systemdaemons.If nochdir is zero, daemon() changes the process's current workingdirectory to the root directory ("/"); otherwise, the current workingdirectory is left unchanged.If noclose is zero, daemon() redirects standard input, standardoutput and standard error to /dev/null; otherwise, no changes aremade to these file descriptors.RETURN VALUE (This function forks, and if the fork(2) succeeds, the parent calls_exit(2), so that further errors are seen by the child only.) Onsuccess daemon() returns zero. If an error occurs, daemon() returns-1 and sets errno to any of the errors specified for the fork(2) andsetsid(2).
daemon() 函數將調用進程變成守護進程。
參數:
nochdir:=0將當前目錄更改至“/”
noclose:=0將標準輸入、標準輸出、標準錯誤重定向至“/dev/null”
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>#define ERR_EXIT(m) \
do\
{\perror(m);\exit(EXIT_FAILURE);\
}\
while (0);\
void creat_daemon(void);
int main(void)
{time_t t;int fd;if(daemon(0,0) == -1)ERR_EXIT("daemon error");while(1){fd = open("daemon.log",O_WRONLY|O_CREAT|O_APPEND,0644);if(fd == -1)ERR_EXIT("open error");t = time(0);char *buf = asctime(localtime(&t));write(fd,buf,strlen(buf));close(fd);sleep(60); }return 0;
}
運行結果和上面一樣