python实现DEAMON守护进程

1 守护进程

1.1 守护进程

守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。

在这里,我们在Linux2.6内核的centos中,ps -ef |awk '{print $1"\t “$2”\t “$3”\t "$8}'看到:PPID=0的进程有两个,分别是PID=1的/sbin/init进程和PID=2的[kthreadd]进程。

root@develop:~# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Apr16 ?        00:00:03 /sbin/init
root         2     0  0 Apr16 ?        00:00:00 [kthreadd]
root         3     2  0 Apr16 ?        00:00:00 [ksoftirqd/0]

其中,[kthreadd]为内核进程,由它fork出来的子进程都是内核进程,并且内核守护进程的名字出现在方括号中,对于需要在进程上下文执行工作但却不被用户层进程(init)上下文调用的每一个内核组件,通常有它自己的内核守护进程。
而对于init进程,它是一个由内核在引导装入时启动的用户层次的命令,属于用户级守护进程,主要负责启动各运行层次特定系统服务。这些服务通常是在它们自己拥有的守护进程的帮助下实现的。用户层守护进程缺少控制终端可能是守护进程调用了setsid的结果。大多数用户层守护进程都是进程组的组长进程以及会话的首进程,而且是这些进程组和会话中的唯一进程。

守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。此外,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建屏蔽字等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。

1.2 守护进程的特性

1.在后台运行
2.与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
3.启动方式特殊,它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,可以由crond启动,还可以由用户终端(通常是shell)执行。
总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。

注意,所有守护进程都以超级用户(用户ID为0)的优先权运行。没有一个守护进程具有控制终端,终端名称设置为问号(?)、终端前台进程组ID设置为-1。缺少控制终端是守护进程调用了setsid的结果。除update以外的所有守护进程都是进程组的首进程,对话期的首进程,而且是这些进程组和对话期中的唯一进程。最后,应当引起注意的是所有这些守护进程的父进程都是init进程。

2 编程规范

详细参见: 《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实现守护进程

import sys,os,time,platform
from signal import SIGTERM


def deamonize(stdout='/dev/null', stderr=None, stdin='/dev/null', pidfile=None, startmsg = 'started with pid %s'):
	'''do the unix double-fork magic to set deamon'''
	# do first 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)
	
	# deapart from parent environment
	os.chdir("/")
	os.setsid()
	os.umask(0)

	# do second fork
	try:
		pid = os.fork()
		if pid > 0: sys.exit(0)
	except OSError, e:
		sys.stderr.write("fork #2 failed: %d	%s\n"%(e.errno, e.strerror))
		sys.exit(1)

	# open file description
	if not stderr:stderr=stdout
	si = file(stdin, 'r')
	so = file(stdout, 'a+')
	se = file(stderr. 'a+', 0)
	pid = str(os.getpid())
	sys.stderr.write("\n%s\n"%startmsg%pid)
	if pidfile:
		file(pidfile, 'w+').write("%s\n"%pid)
	
	# redirect standard file descriptors ,remember flush it before duplicate.
	sys.stdout.flush()
	sys.stderr.flush()
	os.dup2(si.fileno(), sys.stdin.fileno())
	os.dup2(so.fileno(),sys.stdout.fileno())
	os.dup2(se.fileno(),sys.stderr.fileno())

def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null', pidfile='pid.txt', startmsg = 'started with pid %s'):
	if len(sys.argv) <= 1:
		print("usage: %s [start|stop|restart|status]"% sys.argv[0]
		sys.exit(2)
	action=sys.argv[1]
	try:
		pf = file(pidfile, 'r')
		pid = int(pf.read().strip())
		pf.close()
	except IOError:
		pid = None
	
	if action == 'stop' or action == 'restart':
		if not pid:
			mess = " could not stop, pid file '%s' missing. \n"
			sys.stderr.write(mess %pidfile)
			if 'stop' == action:
				sys.exit(1)
			action = 'start'
			pid = None
		else:
			try:
			 while True:
			 	os.kill(pid,SIGTERM)
			 	time.sleep(2)
			 except OSError, err:
			 	err = str(err)
			 	if err.find("No such process") > 0:
			 		os.remove(pidfile)
			 		if 'stop' == action:
			 			sys.exit(0)
			 		action = 'start'
			 		pid = None
			 	else:
			 		print(str(err))
			 		sys.exit(1)
	if 'start' == action:
		if pid:
			mess = "Start aborted since pid file '%s' exists.\n"
			sys.stderr.write(mess%pidfile)
			sys.exit(1)
		deamon(stdout, stderr,stdin,pidfile, startmsg)
		return 
	
	if 'status' == action:
		if not pid:
			sys.stderr.write("Status:stopped\n")
		else:
			sys.stderr.write("Status:Running\n")
		sys.exit(0)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你抱着的是只熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值