linux下程式如何實現單例項執行
阿新 • • 發佈:2019-02-03
1、技術原理
無論是windows還是linux下,程式設計者都會遇到一個問題,那就是如何實現程式的單例項執行。比如,Windows自帶的播放軟體Windows Medea Player只能啟動一個例項。原因很簡單,如果同時啟動幾個例項,卻播放不同的檔案,那麼聲音和影象就會引起混亂。所以,我們要做的就是,程式啟動時檢測一下系統中是否已經存在一個完全一樣的例項,如果已經存在,則本次啟動的程式自動退出。那麼,從編碼的角度來分析,該如何實現這個效果呢?在linux系統中,我們一般有以下幾種方法:
- shell使用ps命令來判斷。最常見也是最簡單的方案,直接寫一個特定的shell指令碼來實現。不過存在可移植性差的問題,程序名字一旦改變之後,指令碼就失去作用無法監控。同時,還要額外附加這個指令碼檔案在系統中執行,實在有點多餘;
- 訊號量/共享記憶體。使用共享記憶體,通過shmget操作共享記憶體,然後寫入pid,這樣就不用生成可見的檔案。這個方案只存在一個很小的缺陷,需要配置共享記憶體的key,並保證不與系統其他應用衝突,一般來說,衝突概率非常小;
- 埠搶佔。應用於大多數的Linux網路應用。思路是系統保證每個埠的TCP只能有一個程序監聽,那麼如果程式啟動時,監聽一個核心的埠,第二個執行的例項就會監聽失敗,無法啟動。這個方案同樣很有效,省去了一個額外的配置檔案,不足之處是一般只用於帶網路的程式;
- 建立檔案,加鎖(建議性鎖)。這種做法最常見了,應用於大多數的Linux程式,如apache httpd, mysql。思路是配置一個pid檔案,當程式啟動時,對pid檔案加鎖,然後寫入本程序的pid,如果鎖失敗,說明有例項已經啟動了。這個方案非常可靠,唯一的不足是需要配置一個pid檔案,並且保證檔案目錄和檔案可寫;
- 程序列表檢測。對於運維常用的方法。由於運維不一定能控制程式的修改,所以考慮從外部解決。crontab指令碼,查詢執行的程序數,一旦發現程序數與預期不符,那麼killall,重啟程序。這個方案是旁路方案,比上面的方式更通用,還可以監視程序數,避免某些子程序core。這個方案沒有什麼缺陷,如果硬要找一個的話,不同的系統ps命令輸出可能不一樣,指令碼需要考慮移植。
2、原始碼實現
經過一番對比,優選其中的方案4,也是最大眾化的一個方案。下面就是詳細的程式碼實現:
- single_instance.c原始碼
/**************************************************************************************************
** **
** 檔名稱: single_instance.c **
** 版權所有: CopyRight @ LEON WorkStudio CO.LTD. 2017 **
** 檔案描述: 判斷系統中是否已經存在該程序的例項 **
** =========================================================================================== **
** 建立資訊: | 2017-9-14 | LEON | 建立本模組 **
** =========================================================================================== **
** 修改資訊: 單擊此處新增.... **
**************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <printf.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
/**************************************************************************************************
** 函式名稱: lockfile
** 功能描述: 對檔案加鎖
** 輸入引數: 無
** 輸出引數: 無
** 返回引數: 無
**************************************************************************************************/
static 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));
}
/**************************************************************************************************
** 函式名稱: proc_is_exist
** 功能描述: 判斷系統中是否存在該程序
** 輸入引數: procname: 程序名
** 輸出引數: 無
** 返回引數: 返回1表示系統中已經存在該程序了;返回0表示系統中不存在該程序
** 注意事項: 此處加鎖完後無需對檔案進行close,而是程序退出後由系統來釋放;否則無法起到保護的作用
**************************************************************************************************/
int proc_is_exist(const char *procname)
{
int fd;
char buf[16];
char filename[100];
sprintf(filename, "/var/run/%s.pid", procname);
fd = open(filename, O_RDWR | O_CREAT, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
if (fd < 0) {
printf("open file \"%s\" failed!!!\n", filename);
return 1;
}
if (lockfile(fd) == -1) { /* 嘗試對檔案進行加鎖 */
printf("file \"%s\" locked. proc already exit!!!\n", filename);
close(fd);
return 1;
} else {
ftruncate(fd, 0); /* 寫入執行例項的pid */
sprintf(buf, "%ld", (long)getpid());
write(fd, buf, strlen(buf) + 1);
return 0;
}
}
- single_instance.h原始碼
/**************************************************************************************************
** **
** 檔名稱: single_instance.h **
** 版權所有: CopyRight @ LEON WorkStudio CO.LTD. 2017 **
** 檔案描述: 判斷系統中是否已經存在該程序的例項 **
** =========================================================================================== **
** 建立資訊: | 2017-9-14 | LEON | 建立本模組 **
** =========================================================================================== **
** 修改資訊: 單擊此處新增.... **
**************************************************************************************************/
#ifndef SINGLE_INSTANCE_H
#define SINGLE_INSTANCE_H
#ifdef __cplusplus
extern "C"
{
#endif
int proc_is_exist(const char *procname);
#ifdef __cplusplus
}
#endif
#endif
3、使用說明
呼叫者只需在程式啟動時呼叫本函式,根據返回值進行判斷即可:
if (proc_is_exist(g_fk_process_name) == TRUE) { /* 單例項執行 */
print_sys("an \"%s\" already running in system. exit now...\n", g_fk_process_name);
return 0;
} else {
print_sys("\"%s\" starting...\n", g_fk_process_name);
}