一些程序,特別是很多的daemon,需要確保同一時刻只有一個程序實例在系統中運行。完成這項任務的一個常見方法是:使用記錄鎖。
讓daemon創建一個文件並在該文件上放置一把寫鎖。daemon在其執行期間一直持有這個文件鎖並在即將終止之前刪除這個文件。如果啓動了daemon的另一個實例,那麼它在獲取該文件上的寫鎖時就會失敗,其結果是它會意識到daemon的另一個實例肯定正在運行,然後終止。(很多網絡服務器採用了另一種常規做法,即將服務器綁定的衆所周知的socket端口號已經被使用時,就認爲該服務器實例已經處於運行狀態了)
unix的/var/run目錄常常是存放此類鎖文件的位置。或者可以在daemon的配置文件中加一行來指定文件的位置。
通常,daemon會將其進程ID寫入鎖文件,因此這個文件在命名時候,通常將.pid作爲擴展名(如rsyslog會創建文件/var/run/rsyslog.pid)。這對於那些需要找出daemon的進程ID的應用程序講是比較有用的。它可以允許執行額外的檢查,像可以使用kill(pid, 0)來檢查進程ID是否存在(在較早的不提供文件加鎖的UNIX實現上,這是一種不完美但很實用的方法,用於檢查一個daemon實例是否在運行或前一個實例在終止之前是否沒有成功刪除這個文件)
我們這裏使用勸告式記錄鎖測試。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <stdlib.h>
void errExit() { fprintf(stderr, "%s\n", strerror(errno)); exit(1); }
int lockfile(int fd) { struct flock fl;
fl.l_type = F_WRLCK; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; return (fcntl(fd, F_SETLK, &fl)); }
int main() { int fd; fd=open("./onlyone", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); //create temp file
if(fd==-1) errExit();
if(lockfile(fd)==-1)//lock temp file errExit(); //daemon(0,1);
for(;;); } |
啓動一個該實例後,再啓動一個該實例時候,fcntl(fd, F_SETLK,&fl)加鎖的時候將返回-1,置errno爲EACCES or EAGAIN,strerror(errno)爲Resource temporarily unavailable,資源暫時不可用。
接下來我們將main中的daemon()去掉註釋,以將該程序轉爲後臺運行。結果試着啓動3次程序,並沒有報錯出來。ps –ef| grep 程序名。發現有3個實例在後臺運行。
想到daemon化,是通過兩次fork實現的,而fork後,子進程並不會繼承fcntl()記錄鎖,所以daemon()後鎖沒繼承下來。這個約束是有道理的,因爲記錄鎖的作用是阻止多個進程同時寫同一個文件。如果子進程通過fork繼承父進程的鎖,則父進程和子進程就可以同時寫同一文件。這裏我們可以將加鎖動作放到daemon之後,解決這個問題。另外fcntl()加鎖是在SUSv3中進行了標準化的。而flock()並沒有標準化,各個類UNIX實現語義可能不一樣,像Linux的flock(),在使用其創建鎖時,子進程會繼承一個引用同一把鎖的引用並且能夠釋放這把鎖,從而導致父進程也會失去這把鎖。