python进程守护

参考:
- Vamei http://www.cnblogs.com/vamei
- Python守护进程daemon实现


如何创建一个进程

实际上,当计算机开机的时候,内核(kernel)只建立了一个init进程。Linux内核并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。fork是一个系统调用。进程存活于内存中。每个进程都在内存中分配有属于自己的一片空间 (address space)。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。

老进程成为新进程的父进程(parent process),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。所以说,所有的进程也构成一个以init为根的树状结构。

ork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。实际上,子进程总可以查询自己的PPID来知道自己的父进程是谁,这样,一对父进程和子进程就可以随时查询对方。

通常在调用fork函数之后,程序会设计一个if选择结构。当PID等于0时,说明该进程为子进程,那么让它执行某些指令,比如说使用exec库函数(library function)读取另一个程序文件,并在当前的进程空间执行 (这实际上是我们使用fork的一大目的: 为某一程序创建进程);而当PID为一个正整数时,说明为父进程,则执行另外一些指令。由此,就可以在子进程建立之后,让它执行与父进程不同的功能。

守护进程编写思路

详细参见: 《AdvancedProgrammingin The Unix Environment》Section 13.3 Page 583
1、调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)。如前所述,由继承得来的文件模式创建屏蔽字可能会被设置为拒绝权限。我们可以根据我们的具体需求设定特定的权限。
2、调用fork,然后使父进程exit。这样做,使得当我们以./的shell命令启动守护进程时,父进程终止会让shell认为此命令已经执行完毕,而且,这也使子进程获得了一个新的进程ID。此外,让父进程先于子进程exit,会使子进程变为孤儿进程,这样子进程成功被init这个用户级守护进程收养。
3、调用setsid创建一个新会话。这在setsid函数中有介绍,调用setsid,会使这个子进程成为(a)新会话的首进程,(b)成为一个新进程组的组长进程,(c)切断其与控制终端的联系,或者就是没有控制终端。至此,这个子进程作为新的进程组的组长,完全脱离了其他进程的控制,并且没有控制终端。
4、将当前工作目录更改为根目录(或某一特定目录位置)。这是为了保证守护进程的当前工作目录在一个挂载的文件系统中,该文件系统不能被卸载。
5、关闭不再需要的文件描述符。根据具体情况来定。
6、某些守护进程可以打开/dev/null使其具有文件描述符0、1、2,这使任何一个试图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果。
7、忽略SIGCHLD信号
这一步并非必须的,只对需要创建子进程的守护进程才有必要,很多服务器守护进程设计成通过派生子进程来处理客户端的请求,如果父进程不对SIGCHLD信号进行处理的话,子进程在终止后变成僵尸进程,通过将信号SIGCHLD的处理方式设置为SIG_IGN可以避免这种情况发生。
8、用日志系统记录出错信息
因为守护进程没有控制终端,当进程出现错误时无法写入到标准输出上,可以通过调用syslog将出错信息写入到指定的文件中。该接口函数包括openlog、syslog、closelog、setlogmask,具体可参考13.4节出错记录。
9、守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。

总结守护进程编程规则

  • 1.在后台运行,调用fork ,然后使父进程exit
  • 2.脱离控制终端,登录会话和进程组,调用setsid()使进程成为会话组长
  • 3.禁止进程重新打开控制终端
  • 4.关闭打开的文件描述符,调用fclose()
  • 5.将当前工作目录更改为根目录。
  • 6.重设文件创建掩码为0
  • 7.处理SIGCHLD 信号

Python代码实现



#!/usr/bin/env python  
# coding:utf-8  
import os,sys,time  

def daemon_init(stdin='/dev/null',stdout='/dev/null',stderr='/dev/null'):
    sys.stdin = open(stdin,'r')
    sys.stdout = open(stdout,'a+')
    sys.stderr = open(stderr,'a+')

    try:
        pid = os.fork()
        if pid > 0: # judge if pid is parent id
            os._exit(0) # kill parent id
    except  OSError as e:
        sys.stderr.write("first fork failed!!"+e.strerror)
        os._exit(1)
   # 子进程, 由于父进程已经退出,所以子进程变为孤儿进程,由init收养  
'''setsid使子进程成为新的会话首进程,和进程组的组长,与原来的进程组、控制终端和登录会话脱离。'''  
     os.setsid()
     '''防止在类似于临时挂载的文件系统下运行,例如/mnt文件夹下,这样守护进程一旦运行,临时挂载的文件系统就无法卸载了,这里我们推荐把当前工作目录切换到根目录下'''
     os.chdir("/")
     '''设置用户创建文件的默认权限,设置的是权限“补码”,这里将文件权限掩码设为0,使得用户创建的文件具有最大的权限。否则,默认权限是从父进程继承得来的'''  
     os.umask(0)

     try:
        pid = os.fork() #第二次进行fork,为了防止会话首进程意外获得控制终端 
        if pid>0:
            os._exit(0) #父进程退出
     except OSError as e:
        sys.stderr.write("second fork failed"+e.strerror)
     sys.stdout.write("Daemon has been created! with pid: %d\n" % os.getpid())  
     sys.stdout.flush()  #由于这里我们使用的是标准IO,回顾APUE第五章,这里应该是行缓冲或全缓冲,因此要调用flush,从内存中刷入日志文件。  

def main():  
    print '========main function start!============' #在调用daemon_init函数前是可以使用print到标准输出的,调用之后就要用把提示信息通过stdout发送到日志系统中了  
    daemon_init('/dev/null','/tmp/daemon.log','/tmp/daemon.err')    # 调用之后,你的程序已经成为了一个守护进程,可以执行自己的程序入口了  
    time.sleep(10) #daemon化自己的程序之后,sleep 10秒,模拟阻塞  


if __name__ == '__main__':  
    main()  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值