版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
linux守护进程及其特性
守护进程最重要的特性是后台执行。
在这一点上DOS下的常驻内存程序TSR与之类似。其次,守护进程必须与其执行前的环境隔离开来。这些环境包含未关闭的文件描写叙述符。控制终端。会话和进程组,工作文件夹以及文件创建掩模等。
linux守护进程与普通进程的区别
- 默认情况下,进程是在前台运行的,这时就把shell占据了(有很多日志打印输出),我们无法进行其他操作。所以对于没有交互的进程,很多时候我们希望将其在后台启动,可以在启动参数的时候加一个&实现这个目的。
- 守护进程已经完全脱离终端控制了,而后台进程并未完全脱离终端,在终端未关闭前还是会往终端输出结果。
- 守护进程在关闭终端控制台时不会受影响,而后台程序会随用户退出而停止,需要在以nohup command &格式运行才能避免影响。
- 守护进程的会话组和当前目录,文件描述符都是独立的,后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变。
Python编写守护进程程序思路
- fork子进程,父进程退出
通常,我们执行服务端程序的时候都会通过终端连接到服务器,成功连接后会加载shell环境,终端和shell都是进程,shell进程是终端进程的子进程,通过ps命令可以很容易的查看到。在这个shell环境下一开始执行的程序都是shell进程的子进程,自然会受到shell进程的影响。在程序里fork子进程后,父进程退出,对了shell进程来说,这个父进程就算执行完了,而产生的子进程会被init进程接管,从而也就脱离了终端的控制。 - 修改子进程的工作目录
子进程在创建的时候会继承父进程的工作目录,比如Nginx就有它的默认工作目录 /etc/nginx/conf.d/default.conf - 创建进程组
使用setsid后,子进程就会成为新会话的首进程(session leader);子进程会成为新进程组的组长进程;子进程没有控制终端。 - 修改umask
由于umask会屏蔽权限,所以设定为0,这样可以避免读写文件时碰到权限问题。 - fork孙子进程,子进程退出
经过上面几个步骤后,子进程会成为新的进程组老大,可以重新申请打开终端,为了避免这个问题,fork孙子进程出来。 - 重定向孙子进程的标准输入流、标准输出流、标准错误流到/dev/null
因为是守护进程,本身已经脱离了终端,那么标准输入流、标准输出流、标准错误流就没有什么意义了。所以都转向到/dev/null,就是都丢弃的意思。
代码实现
这里我以类的方式实现,方便以后直接使用
首先创建一个日志记录器
def creat_handler():
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# logs/log--需要修改成自己定义的路径&文件名
# 创建日志记录器,指明日志保存的路径、每个日志文件的最大大小、保存的日志文件个数上限
file_log_handler = RotatingFileHandler(LOG_FILE_PATH, maxBytes=1024 * 1024 * 100, backupCount=10)
# 创建日志记录的格式 日志等级 输入日志信息的文件名 行数 日志信息
formatter = logging.Formatter('%(levelname)s %(filename)s:%(lineno)d - %(asctime)s - %(name)s - %(message)s')
# 为刚创建的日志记录器设置日志记录格式
file_log_handler.setFormatter(formatter)
logger.addHandler(file_log_handler)
return logger
定义创建守护进程类
class Daemon(object):
"""python模拟linux的守护进程"""
def __init__(self, pidfile, base_path, 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.base_path = base_path
def _daemonize(self):
try:
pid = os.fork() # 第一次fork,生成子进程,脱离父进程
if pid > 0:
sys.exit(0) # 退出主进程
except OSError as e:
logger.error('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror))
sys.exit(1)
os.chdir("/") # 修改工作目录
os.setsid() # 设置新的会话连接
os.umask(0) # 重新设置文件创建权限
try:
pid = os.fork() # 第二次fork,禁止进程打开终端
if pid > 0:
sys.exit(0)
except OSError as e:
logger.error('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror))
sys.exit(1)
# 重定向文件描述符
sys.stdout.flush()
sys.stderr.flush()
si = open(self.stdin, 'r')
so = open(self.stdout, 'a+')
se = open(self.stderr, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# 注册退出函数,根据文件pid判断是否存在进程
atexit.register(self.delpid)
pid = str(os.getpid())
open(self.pidfile, 'w+').write('%s\n' % pid)
def delpid(self):
os.remove(self.pidfile)
def start(self):
# 检查pid文件是否存在以探测是否存在进程
try:
pf = open(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'
logger.warning(message % self.pidfile)
sys.exit(message % self.pidfile)
# 启动监控
self._daemonize()
self._run()
def stop(self):
# 从pid文件中获取pid
try:
pf = open(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'
logger.error(message % self.pidfile)
return
# 杀死进程
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError as err:
err = str(err)
if err.find('No such process') > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
sys.exit(str(err))
def restart(self):
self.stop()
self.start()
def _run(self):
""" 运行自定义函数"""
pass
使用示例
继承Daemon,重写run方法
class MyDaemon(Daemon):
def _run(self):
while True:
os.system("echo 'hello world' >> a.txt")
time.sleep(1)
书写启动方式
if __name__ == "__main__":
daemon = MyDaemon('/tmp/process.pid', BASE_PATH, stdout='/tmp/stdout.log')
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)
启动、停止、重启
python xxx.py start
python xxx.py stop
python xxx.py restart