Linux下一个简单守护进程的实现 (Daemon)
在Linux/UNIX系統(tǒng)引導(dǎo)的時(shí)候會(huì)開啟很多服務(wù),這些服務(wù)稱為守護(hù)進(jìn)程(也叫Daemon進(jìn)程)。守護(hù)進(jìn)程是脫離于控制終端并且在后臺(tái)周期性地執(zhí)行某種任務(wù)或等待處理某些事件的進(jìn)程,脫離終端是為了避免進(jìn)程在執(zhí)行過程中的信息在任何終端上顯示并且進(jìn)程也不會(huì)被任何終端所產(chǎn)生的中斷信息所終止。
?
創(chuàng)建守護(hù)進(jìn)程的一般步驟:
?
(1) 創(chuàng)建子進(jìn)程,退出父進(jìn)程
為了脫離控制終端需要退出父進(jìn)程,之后的工作都由子進(jìn)程完成。在Linux中父進(jìn)程先于子進(jìn)程退出會(huì)造成子進(jìn)程成為孤兒進(jìn)程,而每當(dāng)系統(tǒng)發(fā)現(xiàn)一個(gè)孤兒進(jìn)程時(shí),就會(huì)自動(dòng)由1號(hào)進(jìn)程(init)收養(yǎng)它,這樣,原先的子進(jìn)程就會(huì)變成init進(jìn)程的子進(jìn)程。
ps –ef | grep ProcName? 通過PID/PPID查看進(jìn)程的父子關(guān)系
?
(2) 在子進(jìn)程中創(chuàng)建新的會(huì)話
使用系統(tǒng)函數(shù)setsid來完成。
man 2 setsid??? 查看關(guān)于setsid函數(shù)的說明
setsid – creates a session and sets theprocess group ID
#include <unistd.h>
pid_t setsid(void);
setsid() creates a new session if thecalling process is not a process group leader. The calling process is theleader of the new session, the process group leader of the new process group,and has no controlling tty. The process group ID and session ID of the callingprocess are set to the PID of the calling process. The calling process will bethe only process in this new process group and in this new session.
進(jìn)程組:是一個(gè)或多個(gè)進(jìn)程的集合。進(jìn)程組有進(jìn)程組ID來唯一標(biāo)識(shí)。除了進(jìn)程號(hào)PID之外,進(jìn)程組ID也是一個(gè)進(jìn)程的必備屬性。每個(gè)進(jìn)程組都有一個(gè)組長進(jìn)程,其組長進(jìn)程的進(jìn)程號(hào)等于進(jìn)程組ID,且該進(jìn)程組ID不會(huì)因組長進(jìn)程的退出而受到影響。
setsid函數(shù)作用:用于創(chuàng)建一個(gè)新的會(huì)話,并擔(dān)任該會(huì)話組的組長。調(diào)用setsid有3個(gè)作用
(a) 讓進(jìn)程擺脫原會(huì)話的控制;
(b) 讓進(jìn)程擺脫原進(jìn)程組的控制;
(c) 讓進(jìn)程擺脫原控制終端的控制;
使用setsid函數(shù)的目的:由于創(chuàng)建守護(hù)進(jìn)程的第一步調(diào)用了fork函數(shù)來創(chuàng)建子進(jìn)程再將父進(jìn)程退出。由于在調(diào)用fork函數(shù)時(shí), 子進(jìn)程拷貝了父進(jìn)程的會(huì)話期、進(jìn)程組、控制終端等,雖然父進(jìn)程退出了,但會(huì)話期、進(jìn)程組、控制終端等并沒有改變,因此,這還不是真正意義上的獨(dú)立開了。使 用setsid函數(shù)后,能夠使進(jìn)程完全獨(dú)立出來,從而擺脫其他進(jìn)程的控制。
?
(3) 改變當(dāng)前目錄為根目錄
使用fork創(chuàng)建的子進(jìn)程繼承了父進(jìn)程的當(dāng)前的工作目錄。由于在進(jìn)程運(yùn)行中,當(dāng)前目錄所在的文件系統(tǒng)是不能卸載的,這對以后的使用會(huì)造成諸多的麻 煩。因此,通常的做法是讓根目錄”/”作為守護(hù)進(jìn)程的當(dāng)前工作目錄。這樣就可以避免上述的問題。如有特殊的需求,也可以把當(dāng)前工作目錄換成其他的路徑。改 變工作目錄的方法是使用chdir函數(shù)。
?
(4) 重設(shè)文件權(quán)限掩碼
文件權(quán)限掩碼:是指屏蔽掉文件權(quán)限中的對應(yīng)位。例如,有個(gè)文件權(quán)限掩碼是050,它就屏蔽了文件組擁有者的可讀與可執(zhí)行權(quán)限(對應(yīng)二進(jìn)制 為,rwx, 101)。由于fork函數(shù)創(chuàng)建的子進(jìn)程繼承了父進(jìn)程的文件權(quán)限掩碼,這就給子進(jìn)程使用文件帶來了諸多的麻煩。因此,把文件權(quán)限掩碼設(shè)置為0(即,不屏蔽 任何權(quán)限),可以增強(qiáng)該守護(hù)進(jìn)程的靈活性。設(shè)置文件權(quán)限掩碼的函數(shù)是umask。通常的使用方法為umask(0)。
?
(5) 關(guān)閉文件描述符
用fork創(chuàng)建的子進(jìn)程也會(huì)從父進(jìn)程那里繼承一些已經(jīng)打開了的文件。這些被打開的文件可能永遠(yuǎn)不會(huì)被守護(hù)進(jìn)程讀寫,但它們一樣消耗系統(tǒng)資源,而且可 能導(dǎo)致所在的文件系統(tǒng)無法卸載。在使用setsid調(diào)用之后,守護(hù)進(jìn)程已經(jīng)與所屬的控制終端失去了聯(lián)系,因此從終端輸入的字符不可能達(dá)到守護(hù)進(jìn)程,守護(hù)進(jìn) 程中用常規(guī)方法(如printf)輸出的字符也不可能在終端上顯示出來。所以,文件描述符為0、1、2(即,標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出)的三個(gè)文 件已經(jīng)失去了存在的價(jià)值,也應(yīng)該關(guān)閉。
?
(6) 守護(hù)進(jìn)程退出處理
當(dāng)用戶需要外部停止守護(hù)進(jìn)程時(shí),通常使用kill命令停止該守護(hù)進(jìn)程。所以,守護(hù)進(jìn)程中需要編碼來實(shí)現(xiàn)kill發(fā)出的signal信號(hào)處理,達(dá)到進(jìn)程正常退出。
?
下面是一個(gè)簡單的實(shí)現(xiàn):
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h>// open #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<sys/wait.h> #include<signal.h> #define MAXFILE 65535 volatile sig_atomic_t _running = 1; int fd; // signal handler void sigterm_handler(int arg) { _running = 0; } int main() { pid_t pid; char *buf = "This is a Daemon, wcdj\n"; /* 屏蔽一些有關(guān)控制終端操作的信號(hào) * 防止在守護(hù)進(jìn)程沒有正常運(yùn)轉(zhuǎn)起來時(shí),因控制終端受到干擾退出或掛起 * */ signal(SIGINT, SIG_IGN);// 終端中斷 signal(SIGHUP, SIG_IGN);// 連接掛斷 signal(SIGQUIT, SIG_IGN);// 終端退出 signal(SIGPIPE, SIG_IGN);// 向無讀進(jìn)程的管道寫數(shù)據(jù) signal(SIGTTOU, SIG_IGN);// 后臺(tái)程序嘗試寫操作 signal(SIGTTIN, SIG_IGN);// 后臺(tái)程序嘗試讀操作 signal(SIGTERM, SIG_IGN);// 終止 // test //sleep(20);// try cmd: ./test &; kill -s SIGTERM PID // [1] fork child process and exit father process pid = fork(); if(pid < 0) { perror("fork error!"); exit(1); } else if(pid > 0) { exit(0); } // [2] create a new session setsid(); // [3] set current path char szPath[1024]; if(getcwd(szPath, sizeof(szPath)) == NULL) { perror("getcwd"); exit(1); } else { chdir(szPath); printf("set current path succ [%s]\n", szPath); } // [4] umask 0 umask(0); // [5] close useless fd int i; //for (i = 0; i < MAXFILE; ++i) for (i = 3; i < MAXFILE; ++i) { close(i); } // [6] set termianl signal signal(SIGTERM, sigterm_handler); // open file and set rw limit if((fd = open("outfile", O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0) { perror("open"); exit(1); } printf("\nDaemon begin to work..., and use kill -9 PID to terminate\n"); // do sth in loop while(_running) { if (write(fd, buf, strlen(buf)) != strlen(buf)) { perror("write"); close(fd); exit(1); } usleep(1000*1000);// 1 s } close(fd); // print data if((fd = open("outfile", O_RDONLY)) < 0) { perror("open"); exit(1); } char szBuf[1024] = {0}; if(read(fd, szBuf, sizeof(szBuf)) == -1) { perror("read"); exit(1); } printf("read 1024 bytes:\n%s\n", szBuf); close(fd); return 0; } /* gcc -Wall -g -o test test.c ps ux | grep -v grep | grep test tail -f outfile kill -s SIGTERM PID */?
?
參考:
[1] 守護(hù)進(jìn)程
[2] 編寫Linux/Unix守護(hù)進(jìn)程
[3] Linux 信號(hào)signal處理函數(shù)
[4] The usage of sig_atomic_t in linux signal mask function
?
總結(jié)
以上是生活随笔為你收集整理的Linux下一个简单守护进程的实现 (Daemon)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QTP自动化测试自学手册V2.0版本
- 下一篇: Listview性能优化