Linux守护进程设计规范及python实现

luoluo牛的gearman worker用到了守护进程,补充下知识。

原文地址:http://blog.csdn.net/dysj4099/article/details/18219411

----------------------

首先,守护进程是一类在后台执行,生命周期较长的进程,它一般随系统启动运行,在系统关闭的时候停止。翻译了一下《Advanced Programming in The Unix Environment  Section》的第13.3小节,此小节是一个关于守护进程的设计规范,罗列了守护进程设计实现的几大原则:

 

翻译《AdvancedProgrammingin The Unix Environment》Section 13.3 Page 583

 

守护进程编码规范:

        本小节将介绍一些守护进程的基本编码规范,这些规范将阻止守护进程与当前环境产生一些不必要的交互。本节将通过一个函数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) 其他还有一些诸如工作目录设置、关闭文件描述符、设置文件创建掩码之类的操作。

 

最后看一个python实现的例子,代码来自:http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #!/usr/bin/env python  
  2.   
  3. import sys, os, time, atexit  
  4. from signal import SIGTERM   
  5.   
  6. class Daemon:  
  7.     """ 
  8.     A generic daemon class. 
  9.      
  10.     Usage: subclass the Daemon class and override the run() method 
  11.     """  
  12.     def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):  
  13.         self.stdin = stdin  
  14.         self.stdout = stdout  
  15.         self.stderr = stderr  
  16.         self.pidfile = pidfile  
  17.       
  18.     def daemonize(self):  
  19.         """ 
  20.         do the UNIX double-fork magic, see Stevens' "Advanced  
  21.         Programming in the UNIX Environment" for details (ISBN 0201563177) 
  22.         http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 
  23.         """  
  24.         try:   
  25.             pid = os.fork()   
  26.             if pid > 0:  
  27.                 # exit first parent  
  28.                 sys.exit(0)   
  29.         except OSError, e:   
  30.             sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))  
  31.             sys.exit(1)  
  32.       
  33.         # decouple from parent environment  
  34.         os.chdir("/")   
  35.         os.setsid()   
  36.         os.umask(0)   
  37.       
  38.         # do second fork  
  39.         try:   
  40.             pid = os.fork()   
  41.             if pid > 0:  
  42.                 # exit from second parent  
  43.                 sys.exit(0)   
  44.         except OSError, e:   
  45.             sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))  
  46.             sys.exit(1)   
  47.       
  48.         # redirect standard file descriptors  
  49.         sys.stdout.flush()  
  50.         sys.stderr.flush()  
  51.         si = file(self.stdin, 'r')  
  52.         so = file(self.stdout, 'a+')  
  53.         se = file(self.stderr, 'a+'0)  
  54.         os.dup2(si.fileno(), sys.stdin.fileno())  
  55.         os.dup2(so.fileno(), sys.stdout.fileno())  
  56.         os.dup2(se.fileno(), sys.stderr.fileno())  
  57.       
  58.         # write pidfile  
  59.         atexit.register(self.delpid)  
  60.         pid = str(os.getpid())  
  61.         file(self.pidfile,'w+').write("%s\n" % pid)  
  62.       
  63.     def delpid(self):  
  64.         os.remove(self.pidfile)  
  65.   
  66.     def start(self):  
  67.         """ 
  68.         Start the daemon 
  69.         """  
  70.         # Check for a pidfile to see if the daemon already runs  
  71.         try:  
  72.             pf = file(self.pidfile,'r')  
  73.             pid = int(pf.read().strip())  
  74.             pf.close()  
  75.         except IOError:  
  76.             pid = None  
  77.       
  78.         if pid:  
  79.             message = "pidfile %s already exist. Daemon already running?\n"  
  80.             sys.stderr.write(message % self.pidfile)  
  81.             sys.exit(1)  
  82.           
  83.         # Start the daemon  
  84.         self.daemonize()  
  85.         self.run()  
  86.   
  87.     def stop(self):  
  88.         """ 
  89.         Stop the daemon 
  90.         """  
  91.         # Get the pid from the pidfile  
  92.         try:  
  93.             pf = file(self.pidfile,'r')  
  94.             pid = int(pf.read().strip())  
  95.             pf.close()  
  96.         except IOError:  
  97.             pid = None  
  98.       
  99.         if not pid:  
  100.             message = "pidfile %s does not exist. Daemon not running?\n"  
  101.             sys.stderr.write(message % self.pidfile)  
  102.             return # not an error in a restart  
  103.   
  104.         # Try killing the daemon process      
  105.         try:  
  106.             while 1:  
  107.                 os.kill(pid, SIGTERM)  
  108.                 time.sleep(0.1)  
  109.         except OSError, err:  
  110.             err = str(err)  
  111.             if err.find("No such process") > 0:  
  112.                 if os.path.exists(self.pidfile):  
  113.                     os.remove(self.pidfile)  
  114.             else:  
  115.                 print str(err)  
  116.                 sys.exit(1)  
  117.   
  118.     def restart(self):  
  119.         """ 
  120.         Restart the daemon 
  121.         """  
  122.         self.stop()  
  123.         self.start()  
  124.   
  125.     def run(self):  
  126.         """ 
  127.         You should override this method when you subclass Daemon. It will be called after the process has been 
  128.         daemonized by start() or restart(). 
  129.         """  

Daemon.daemonize()方法就是执行了上述的步骤。如果需要使用此守护进程类,请继承Daemon类并自行实现run方法。

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #!/usr/bin/env python  
  2.   
  3. import sys, time  
  4. from daemon import Daemon  
  5.   
  6. class MyDaemon(Daemon):  
  7.     def run(self):  
  8.         while True:  
  9.             time.sleep(1)  
  10.   
  11. if __name__ == "__main__":  
  12.     daemon = MyDaemon('/tmp/daemon-example.pid')  
  13.     if len(sys.argv) == 2:  
  14.         if 'start' == sys.argv[1]:  
  15.             daemon.start()  
  16.         elif 'stop' == sys.argv[1]:  
  17.             daemon.stop()  
  18.         elif 'restart' == sys.argv[1]:  
  19.             daemon.restart()  
  20.         else:  
  21.             print "Unknown command"  
  22.             sys.exit(2)  
  23.         sys.exit(0)  
  24.     else:  
  25.         print "usage: %s start|stop|restart" % sys.argv[0]  
  26.         sys.exit(2)  

参考资料:

http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/

http://www.zhouxiongzhi.net/tech/double-fork-when-creating-daemon/

http://blog.163.com/yungang_z/blog/static/175153133201232462140622/




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值