1、守护进程的基本编码规范html
详细参见:《AdvancedProgrammingin The Unix Environment》Section 13.3 Page 583python
本小节将介绍一些守护进程的基本编码规范,这些规范将阻止守护进程与当前环境产生一些没必要要的交互。本节将经过一个函数daemonize实现这些规范。
1. 首先要作的被称为 umask,这一步骤会将文件建立掩码重置为0。这一步的缘由是守护进程继承(inherited)获得的文件掩码有可能会拒绝某些特定的文件操做权限。若是守护进程想要建立文件,那有可能它须要设置特定的文件操做权限。例如,若是守护进程想要建立容许组读和写(group-readand group-write)权限的文件,但继承获得的文件建立掩码屏蔽了这个权限,则建立操做不会成功。
2. 调用 fork 并使父进程退出(exit)。这一步骤的目的在于。首先,若是守护进程是经过一个简单的shell命令创建的,那么在父进程终止的时候shell会认为命令已经结束了继而结束守护进程。其次,子进程继承获得父进程的groupID同时也得到了一个新的进程号,因此咱们必须得保证子进程不能担任groupleader的角色,这是下一步setsid 操做的前提。
注:此步骤是使得进程在后台运行。
3. 调用 setsid 建立一个新的会话。有三个步骤将会执行:(a)进程将成为这个新会话的sessionleader角色,(b)此进程将会成为一个新的进程组的groupleader,(c)此进程将不会有控制终端。
注:此步骤将使得进程脱离控制终端、登陆会话以及进程组。
在基于SystemV的系统中,有建议再一次调用fork 并使父进程退出。而新产生的进程将会成为真正的守护进程。这一步骤将保证守护进程不是一个sessionleader,进而阻止它获取一个控制终端。或者另外一种阻止守护进程获取控制终端的方案是任意时刻打开一个终端设备的时候确保指定O_NOCTTY。
注:此步骤将禁止进程从新打开控制终端。
4. 将当前的工做目录切换到系统根目录下。由于从父进程集成来的当前工做目录多是一个被挂载的文件系统。由于守护进程一般是直到系统重启的时候才会退出,若是守护进程的工做目录在一个挂载的文件系统上,那么这个文件系统就不能被卸载(unmounted)。
有的守护进程可能会将当前的工做目录切换到一些特定的路径,在这些路径下它们将完成它们的工做。例如,lineprinter spoolingdaemons 一般将工做目录切换为spool目录。
5. 一些没必要要的文件描述符将会被关闭。这个步骤将阻止守护进程保持从父进程集成到的任何已经打开的文件描述符(也多是shell或其余进程)。咱们可使用 open_max 函数或 getrlimit 函数来查找当前优先级最高的文件描述符并关闭此描述符之下的全部其余描述符。
注:保持打开的描述符将会占用系统资源并可能使某些文件不能被卸载。
6. 有一些守护进程将打开文件描述符0, 1, 2 指向 /dev/null ,这样一来全部试图从标准输入、输出及错误读取守护进程信息的操做都不能成功。由于守护进程当前已经不与任何终端设备相关联,没有地方显示其输出或接受用户的输入。即便守护进程是从一个交互式session建立的,守护进程也将运行在后台,任何终端的登陆与终止将不会影响守护进程。若是有其余用户经过当前的终端登陆,咱们也不但愿守护进程的输出出如今终端上,而且该用户的任何输入也不会被守护进程接收。
总结起来就是:
1) 第一次fork将会建立父-子进程,同时使得父进程退出保证守护进程可以运行在后台。
2) 经过setsid步骤使得进程与控制终端、登陆会话以及进程组脱离。
3) 第二次fork将确保进程从新打开控制终端,而且产生子-孙进程,而子进程退出后孙进程将成为真正的守护进程。
4) 其余还有一些诸如工做目录设置、关闭文件描述符、设置文件建立掩码之类的操做。git
2、python守护进程的一个例子github
详见:https://gist.github.com/jamiesun/3097215,样例代码以下redis
#! /usr/bin/env python2.7 #encoding:utf-8 #@description:一个python守护进程的例子 #@tags:python,daemon import sys import os import time import atexit from signal import SIGTERM class Daemon: """ A generic daemon class. Usage: subclass the Daemon class and override the run() method """ def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile def daemonize(self): """ do the UNIX double-fork magic, see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError, e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # decouple from parent environment os.chdir("/") os.setsid() os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, 'r') so = file(self.stdout, 'a+') se = file(self.stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # write pidfile atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile,'w+').write("%s\n" % pid) def delpid(self): os.remove(self.pidfile) def start(self): """ Start the daemon """ # Check for a pidfile to see if the daemon already runs try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = "pidfile %s already exist. Daemon already running?\n" sys.stderr.write(message % self.pidfile) sys.exit(1) # Start the daemon self.daemonize() self.run() def stop(self): """ Stop the daemon """ # Get the pid from the pidfile try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) return # not an error in a restart # Try killing the daemon process try: while 1: os.kill(pid, SIGTERM) time.sleep(0.1) except OSError, err: err = str(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) sys.exit(1) def restart(self): """ Restart the daemon """ self.stop() self.start() def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been daemonized by start() or restart(). """ class MyDaemon(Daemon): def run(self): while True: time.sleep(60) print 'daemon runing' if __name__ == "__main__": daemon = MyDaemon("/var/run/demodaemon.pid") if len(sys.argv) >= 2: if 'start' == sys.argv[1]: daemon.start() elif 'stop' == sys.argv[1]: daemon.stop() elif 'restart' == sys.argv[1]: daemon.restart() else: print "Unknown command" sys.exit(2) sys.exit(0) else: print "usage: %s start|stop|restart" % sys.argv[0] sys.exit(2)
3、编写的针对primeton ESB SERVER程序的守护进程样例shell
监控程序的进程是否存在,程序的端口是否正常ruby
#!/usr/bin/python # encoding:utf-8 # @description:一个python守护进程 # @tags:python,daemon import atexit import os from signal import SIGTERM import socket import sys import time class ESBDaemon: # 构造函数 def __init__(self, pidfile, ip, port, findCmd, runCmd, stopCmd, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): # 须要获取调试信息,改成stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr',以root身份运行 self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile self.ip = ip self.port = port self.findCmd = findCmd self.runCmd = runCmd self.stopCmd = stopCmd def daemonize(self): # 第一次fork将会建立父-子进程,同时使得父进程退出保证守护进程可以运行在后台 try: pid = os.fork() if pid > 0: # 退出父进程 sys.exit(0) except OSError, e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # 与父进程的环境解耦 # 经过setsid步骤使得进程与控制终端、登陆会话以及进程组脱离 os.chdir("/") os.setsid() os.umask(0) # 第二次fork将确保进程从新打开控制终端,而且产生子-孙进程,而子进程退出后孙进程将成为真正的守护进程 # 建立子进程 try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # 其余还有一些诸如工做目录设置、关闭文件描述符、设置文件建立掩码之类的操做 # 重定向文件描述符 sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, 'r') so = file(self.stdout, 'a+') se = file(self.stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # 建立processid文件 atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile, 'w+').write("%s\n" % pid) def delpid(self): os.remove(self.pidfile) # 可用于检测程序是否正常,如检测redis是否正常,即检测redis的6379端口是否正常 def check_aliveness(self): sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.settimeout(1) try: sk.connect((self.ip, self.port)) return True except Exception: return False finally: sk.close() return False def writeFile(self, msg): f = file("/var/run/esb.log", 'a+') f.write(msg + time.strftime('%Y-%m-%d %H:%M:%S') + "\r\n") # write text to file f.close() def start(self): # 检查pid文件是否存在以探测是否存在进程 try: pf = file(self.pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = "pidfile %s already exist. Daemon already running?\n" sys.stderr.write(message % self.pidfile) self.writeFile(message % self.pidfile) sys.exit(1) # 启动监控 self.writeFile("启动监控") self.daemonize() self.run() def stop(self): self.writeFile("中止程序") # 从pid文件中获取pid try: pf = file(self.pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) self.writeFile(message % self.pidfile) return # 重启不报错 # 杀死进程 try: while 1: os.kill(pid, SIGTERM) time.sleep(0.1) os.system(self.stopCmd) except OSError, err: err = str(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) self.writeFile(str(err)) sys.exit(1) def restart(self): self.writeFile("重启程序") self.stop() self.start() def run(self): while True: try: self.writeFile("检查程序是否正常") esb = os.popen(self.findCmd).read().strip() portNormal=self.check_aliveness() if esb == '0' and (not portNormal): self.writeFile("程序进程不存在 :" + str(esb) + " " + str(portNormal)) os.system(self.runCmd) time.sleep(30) except OSError, err: self.writeFile(str(err)) if __name__ == "__main__": daemon = ESBDaemon( "/var/run/dfsdaemon.pid", "172.168.27.153", 6200, 'ps -fe | grep "primeton" | grep "server" | grep -v "grep" | wc -l', 'nohup /home/primeton/esb/startServer.sh > /home/primeton/esb/server.log 2>&1 &', '/home/primeton/esb/stopServer.sh' ) if len(sys.argv) >= 2: if 'start' == sys.argv[1]: daemon.start() elif 'stop' == sys.argv[1]: daemon.stop() elif 'restart' == sys.argv[1]: daemon.restart() else: print "Unknown command" sys.exit(2) sys.exit(0) else: print "usage: %s start|stop|restart" % sys.argv[0] sys.exit(2)
4、对应的shell版本bash
监控程序的进程是否存在,程序的端口是否正常session
#/bin/sh host="172.168.27.153" port=6200 appName="server" while true; do count=`ps -fe | grep "primeton" | grep "$appName" | grep -v "grep" | wc -l` nport=`echo ""|telnet $host $port 2>/dev/null|grep "\^]"|wc -l` echo "程序进程数:" $count "端口是否通:" $nport if [ "$count" != "1" -a $nport -eq 0 ]; then echo "从新启动程序" rm -rf /home/primeton/esb/server/EOS/_srv/work/mq_data/localhost/kr-store/data nohup /home/primeton/esb/startServer.sh > /home/primeton/esb/server.log 2>&1 & fi sleep 2 done
5、参考资料app
http://slaytanic.blog.51cto.com/2057708/742049
http://blog.csdn.net/dysj4099/article/details/18219411
https://gist.github.com/jamiesun/3097215