原文来自静雅斋,转载请注明出处。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
目录中。