Python实现Linux下守护进程的编写方法
本文实例讲述了Python实现Linux下守护进程的编写方法,分享给大家供大家参考,相信对于大家的Python程序设计会起到一定的帮助作用。具体方法如下:
1.调用fork()以便父进程可以退出,这样就将控制权归还给运行你程序的命令行或shell程序。需要这一步以便保证新进程不是一个进程组头领进程(processgroupleader)。下一步,‘setsid()',会因为你是进程组头领进程而失败。进程调用fork函数时,操作系统会新建一个子进程,它本质上与父进程完全相同。子进程从父进程继承了多个值的拷贝,比如全局变量和环境变量。两个进程唯一的区别就是fork的返回值。child(子)进程接收返回值为0,而父进程接收子进程的pid作为返回值。调用fork函数后,两个进程并发执行同一个程序,首先执行的是调用了fork之后的下一行代码。父进程和子进程既并发执行,又相互独立;也就是说,它们是“异步执行”的。
2.调用‘setsid()'以便成为一个进程组和会话组的头领进程。由于一个控制终端与一个会话相关联,而且这个新会话还没有获得一个控制终端,我们的进程没有控制终端,这对于守护程序来说是一件好事。
3.再次调用‘fork()'所以父进程(会话组头领进程)可以退出。这意味着我们,一个非会话组头领进程永远不能重新获得控制终端。
4.调用‘chdir("/")'确认我们的进程不保持任何目录于使用状态。不做这个会导致系统管理员不能卸装(umount)一个文件系统,因为它是我们的当前工作目录。[类似的,我们可以改变当前目录至对于守护程序运行重要的文件所在目录]
5.调用‘umask(0)'以便我们拥有对于我们写的任何东西的完全控制。我们不知道我们继承了什么样的umask。[这一步是可选的](译者注:这里指步骤5,因为守护程序不一定需要写文件)
6.调用‘close()'关闭文件描述符0,1和2。这样我们释放了从父进程继承的标准输入,标准输出,和标准错误输出。我们没办法知道这些文描述符符可能已经被重定向去哪里。注意到许多守护程序使用‘sysconf()'来确认‘_SC_OPEN_MAX'的限制。‘_SC_OPEN_MAX'告诉你每个进程能够打开的最多文件数。然后使用一个循环,守护程序可以关闭所有可能的文件描述符。你必须决定你需要做这个或不做。如果你认为有可能有打开的文件描述符,你需要关闭它们,因为系统有一个同时打开文件数的限制。
7.为标准输入,标准输出和标准错误输出建立新的文件描述符。即使你不打算使用它们,打开着它们不失为一个好主意。准确操作这些描述符是基于各自爱好;比如说,如果你有一个日志文件,你可能希望把它作为标准输出和标准错误输出打开,而把‘/dev/null'作为标准输入打开;作为替代方法,你可以将‘/dev/console'作为标准错误输出和/或标准输出打开,而‘/dev/null'作为标准输入,或者任何其它对你的守护程序有意义的结合方法。(译者注:一般使用dup2函数原子化关闭和复制文件描述符。
实现代码如下:
#Coremodules importatexit importos importsys importtime importsignal classDaemon(object): """ Agenericdaemonclass. Usage:subclasstheDaemonclassandoverridetherun()method """ def__init__(self,pidfile,stdin=os.devnull, stdout=os.devnull,stderr=os.devnull, home_dir='.',umask=022,verbose=1): self.stdin=stdin self.stdout=stdout self.stderr=stderr self.pidfile=pidfile self.home_dir=home_dir self.verbose=verbose self.umask=umask self.daemon_alive=True defdaemonize(self): """ DotheUNIXdouble-forkmagic,seeStevens'"Advanced ProgrammingintheUNIXEnvironment"fordetails(ISBN0201563177) """ try: pid=os.fork() ifpid>0: #Exitfirstparent sys.exit(0) exceptOSError,e: sys.stderr.write( "fork#1failed:%d(%s)\n"%(e.errno,e.strerror)) sys.exit(1) #Decouplefromparentenvironment os.chdir(self.home_dir) os.setsid() os.umask(self.umask) #Dosecondfork try: pid=os.fork() ifpid>0: #Exitfromsecondparent sys.exit(0) exceptOSError,e: sys.stderr.write( "fork#2failed:%d(%s)\n"%(e.errno,e.strerror)) sys.exit(1) ifsys.platform!='darwin':#ThisblockbreaksonOSX #Redirectstandardfiledescriptors sys.stdout.flush() sys.stderr.flush() si=file(self.stdin,'r') so=file(self.stdout,'a+') ifself.stderr: se=file(self.stderr,'a+',0) else: se=so os.dup2(si.fileno(),sys.stdin.fileno()) os.dup2(so.fileno(),sys.stdout.fileno()) os.dup2(se.fileno(),sys.stderr.fileno()) defsigtermhandler(signum,frame): self.daemon_alive=False signal.signal(signal.SIGTERM,sigtermhandler) signal.signal(signal.SIGINT,sigtermhandler) ifself.verbose>=1: print"Started" #Writepidfile atexit.register( self.delpid)#Makesurepidfileisremovedifwequit pid=str(os.getpid()) file(self.pidfile,'w+').write("%s\n"%pid) defdelpid(self): os.remove(self.pidfile) defstart(self,*args,**kwargs): """ Startthedaemon """ ifself.verbose>=1: print"Starting..." #Checkforapidfiletoseeifthedaemonalreadyruns try: pf=file(self.pidfile,'r') pid=int(pf.read().strip()) pf.close() exceptIOError: pid=None exceptSystemExit: pid=None ifpid: message="pidfile%salreadyexists.Isitalreadyrunning?\n" sys.stderr.write(message%self.pidfile) sys.exit(1) #Startthedaemon self.daemonize() self.run(*args,**kwargs) defstop(self): """ Stopthedaemon """ ifself.verbose>=1: print"Stopping..." #Getthepidfromthepidfile pid=self.get_pid() ifnotpid: message="pidfile%sdoesnotexist.Notrunning?\n" sys.stderr.write(message%self.pidfile) #Justtobesure.AValueErrormightoccurifthePIDfileis #emptybutdoesactuallyexist ifos.path.exists(self.pidfile): os.remove(self.pidfile) return#Notanerrorinarestart #Trykillingthedaemonprocess try: i=0 while1: os.kill(pid,signal.SIGTERM) time.sleep(0.1) i=i+1 ifi%10==0: os.kill(pid,signal.SIGHUP) exceptOSError,err: err=str(err) iferr.find("Nosuchprocess")>0: ifos.path.exists(self.pidfile): os.remove(self.pidfile) else: printstr(err) sys.exit(1) ifself.verbose>=1: print"Stopped" defrestart(self): """ Restartthedaemon """ self.stop() self.start() defget_pid(self): try: pf=file(self.pidfile,'r') pid=int(pf.read().strip()) pf.close() exceptIOError: pid=None exceptSystemExit: pid=None returnpid defis_running(self): pid=self.get_pid() print(pid) returnpidandos.path.exists('/proc/%d'%pid) defrun(self): """ YoushouldoverridethismethodwhenyousubclassDaemon. Itwillbecalledaftertheprocesshasbeen daemonizedbystart()orrestart(). """
感兴趣的读者可以调试运行一下本文实例代码,相信会有新的收获。