原文來自靜雅齋,轉載請註明出處。javascript
守護進程對於Unix運維來講應該是不陌生的,全部的提供服務的進程基本上都是守護進程,一般也能夠稱爲服務。它們由init進程啓動,而且沒有控制終端,是一種執行平常事務的進程。
在Unix系統下,有不少守護進程,在基於BSD的系統下運行下列命令java
ps -axj複製代碼
-a選項顯示全部進程,包括其餘用戶的進程,-x顯示沒有控制終端的進程狀態,-j顯示與做業有關的信息:會話ID、進程組ID、控制終端以及終端進程組ID。在基於SystemV的系統中,對應命令是ps -efj
,具體如何須要查看本身的ps命令說明,固然還有一些系統只容許超級用戶查看到其餘的用戶進程,普通用戶不能查看其餘用戶進程。
咱們知道,除了用戶進程之外,還有不少系統進程,好比守護進程,對於大部分Unix環境來講,使用的都是SystemV風格的init啓動方式,首先是Grub引導內核啓動,而後內核會查找/sbin/init
程序而且啓動它,init程序會根據一系列的配置文件啓動不一樣的腳本,最終啓動守護進程。固然,目前最新的操做系統基本上都是systemd,因此是不一樣的,可是基本原理仍是相同的,好比一樣都是root權限運行,全部守護進程沒有控制終端,shell
編寫守護進程的時候須要遵循一些規則,以避免出現各類問題編程
/
目錄。由於從父進程繼承過來的屬性可能會致使文件系統沒法卸載,因此咱們須要使用chdir()
函數。/dev/null
來保證不會有標準輸入輸出。#include "include/apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
void daemonize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
umask(0);
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
err_quit("%s: can't get file limit", cmd);
if ((pid = fork()) < 0)
err_quit("%s: can't fork", cmd);
else if (pid != 0)
exit();
setsid();
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
err_quit("%s: can't ignore SIGHUP", cmd);
if ((pid = fork()) < 0)
err_quit("%s: can't fork", cmd);
else if (pid != 0)
exit();
if (chdir("/") < 0)
err_quit("%s: can't change directory to /", cmd);
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; ++i)
close(i);
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
exit(1);
}
}複製代碼
上面這個函數實際上就是以前所說必須遵循的規則的寫法,只須要經過main函數調用這個函數就能使進程變爲守護進程,固然,因爲權限問題,實際上並非實際的寫法。服務器
守護進程的一個問題就是日誌問題,咱們知道,任何的程序必然須要有方式記錄下本身的活動日誌,對於大部分狀況來講,都是經過標準輸入標準輸出標準錯誤的形式記錄日誌,可是守護進程沒有控制終端,不能寫到標準錯誤上,並且咱們也不會但願它寫到終端上,其中一個解決方法是寫到一個單獨的文件中,可是這樣會讓運維人員很是頭痛,由於程序一多就會很是混亂,因此就須要一個集中式的守護進程來記錄日誌。
syslog是BSD伯克利開發的,普遍運用於BSD系列的系統中,後來成爲了Unix標準之一。固然,目前因爲systemd的實質性接管,因此syslog的做用正在被systemd蠶食,這裏不是討論的重點。
syslog的架構很簡單,可是頗有效,syslogd做爲系統服務啓動,而後偵聽/dev/log
socket、/dev/klog
socket和UDP514端口。其中/dev/log
用於接收本地用戶進程的日誌信息,UDP514端口接收網絡上的日誌信息,/dev/klog
則是監聽內核的日誌信息。
因爲這種架構,因此開發者可使用三種方法產生日誌信息網絡
/dev/klog
讀取信息/dev/log
syslogd在啓動的時候回讀取配置文件,通常在/etc/syslog.conf
,裏面決定了消息應當被髮送到何處,甚至有可能重要信息會被在管理員控制檯上打印。架構
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);複製代碼
openlog函數讓開發者指定一個ident參數,也就是標識符,之後,這個ident將被加到每則日誌消息中。option參數則是指定了各類選項位屏蔽。
|option|說明|
|------|---|
|LOG_CONS|若日誌信息不能經過Unix Domain數據報,則將該消息寫入控制檯|
|LONG_NDELAY|當即打開至syslogd守護進程的Unix Domain數據報套接字,不要等到第一條信息已經被記錄時候再打開。一般,在記錄第一條信息以前,不打開套接字|
|LOG_NOWAIT|不要等待在將消息記錄日誌過程當中可能以建立的子進程,由於在syslog調用wait時,應用程序可能已得到了子進程的狀態。這種處理阻止了與捕捉SIGCHLD信號的應用程序以前產生的衝突|
|LOG_ODELAY|在第一條消息被記錄以前延遲打開連接|
|LOG_PERROR|除將日誌消息發送給syslogd之外,還將其寫入到標準錯誤|
|LOG_PID|記錄每條信息都要包含進程ID|
openlog的facility參數值則包含了不少可選值,可是很是遺憾的是,只有少部分是能被跨平臺使用的。具體能夠參見各平臺的Unix手冊。
syslog函數則會產生一條日誌,其priority參數是facility和level的組合,format參數則是格式化字符串,基本和vsprintf函數同樣。
setlogmask函數用於設置進程的記錄優先級屏蔽字。它返回調用它以前的屏蔽字,也就是能夠用來存儲着或者瞭解以前的屏蔽字狀態,各條消息除非已在記錄優先級屏蔽字中進行了設置,不然不會被記錄。併發
不少狀況下,守護進程只是一個進程,由於不須要併發地進行操做,並且這樣頗有可能致使資源競爭,因此在不少狀況下,守護進程只會實如今任意時刻只存在守護進程一個副本,因此爲了保證只存在一個副本,就須要一種機制來保證。而文件和記錄鎖就是這樣一種保證方式,實際上,不僅僅是單實例守護進程,幾乎全部的守護進程都採用了這種方式。運維
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
extern int lockfile(int);
int already_running(void)
{
int fd;
char buf[16];
fd = open(LOCKFILE, O_RDWR | O_CREATE, LOCKMODE);
if (fd < 0) {
syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(error));
exit(1);
}
if (lockfile(fd) < 0) {
if (errno == EACCES || errno == EAGAIN) {
close(fd);
return(1);
}
syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno));
exit(1);
}
ftruncate(fd, 0);
sprintf(buf, "%ld", (long)getpid());
write(fd, buf, strlen(buf) + 1);
return(0);
}複製代碼
實際上上面的行爲很是常見,守護進程啓動的時候試圖建立一個文件而且將進程ID寫入其中,若是該文件加鎖,則lockfile函數將會失敗,而且返回,代表已經有守護進程正在運行。不然將文件長度截斷爲0,將進程ID寫入其中。socket
/var/run
目錄中。不過須要注意,守護進程須要超級用戶權限才能建立文件。鎖文件名字通常是name.pid/etc
目錄中。