实现Linux Daemon 进程

若是咱们远程登陆了远程的 Linux 服务器,运行了一些耗时较长的任务,如何让命令提交后不受本地关闭终端窗口/网络断开链接的干扰呢?html

守护进程

守护进程,也即一般所说的 Daemon 进程,是 Linux 下一种特殊的后台服务进程,它独立于控制终端而且周期性的执行某种任务或者等待处理某些发生的事件。守护进程的名称一般以 “d” 结尾,如 “httpd”、“crond”、“mysqld”等. Redis 能够经过修改配置文件以 Daemon方式运行.python

在 Linux 中,由终端登陆系统登入系统后会获得一个 shell 进程,这个终端便成为这个 shell 进程的控制终端(Controlling Terminal)。shell 进程启动的其余进程,因为复制了父进程的信息,所以也都同依附于这个控制终端。终端关闭,相应的进程都会自动关闭。守护进程脱离终端的目的,也便是不受终端变化的影响不被终端打断,固然也不想在终端显示执行过程当中的信息。mysql

若是不想进程受到用户、终端或其余变化的影响,就必须把它变成守护进程。linux

实现守护进程

经过一些特殊命令实现 Daemon 进程

 

nohup 

若是想让某一条长时间运行的命令不受终端退出的影响,可使用nohup命令.sql

The nohup utility invokes utility with its arguments and at this time sets the signal SIGHUP to be ignored.If the standard output is a termi-nal, the standard output is appended to the filenohup.outin the current directory.If standard error is a terminal, it is directed to the same place as the standard output.shell

咱们知道,当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其全部子进程.而 nohup 的功能就是让提交的命令忽略 hangup 信号编程

使用了 nohump 后,标准输出和标准错误缺省会被重定向到 nohup.out 文件中。通常咱们可在结尾加上"&"来将命令同时放入后台运行,也可用">filename2>&1"来更改缺省的重定向文件名。服务器

setsid()

setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离。因为会话过程对控制终端的独占性,进程同时与控制终端脱离。网络

经过编程,让进程直接以 Daemon 方式运行

一般实现一个 Daemon 很简单,几步就能够实现,咱们看一下Redis的实现.session

void daemonize(void) {
    int fd;

    if (fork() != 0) exit(0); /* parent exits */
    setsid(); /* create a new session */

    /* Every output goes to /dev/null. If Redis is daemonized but
     * the 'logfile' is set to 'stdout' in the configuration file
     * it will not log at all. */
    if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > STDERR_FILENO) close(fd);
    }
}
  1. 第一步,先fork 一个子进程,而后退出原有程序,这样子进程就变成一个孤儿进程.由 init作为他的父进程.从而在形式上脱离控制终端的控制。
  2. 调用setsid ,由新建立的子进程建立一个新的会话,并成为这个会话的 Lader.
  3. 将原有的stdin,stdout,stderr,都定向到 /dev/null.

以上三步是 Redis实现的 Daemon步骤.

此外,Redis 还将进程 ID 保存到 PID 文件里.这里介绍一下 PID 文件的做用.

  在linux系统的目录/var/run下面通常咱们都会看到不少的*.pid文件。并且每每新安装的程序在运行后也会在/var/run目录下面产生本身的pid文件。那么这些pid文件有什么做用呢?它的内容又是什么呢? 

(1) pid文件的内容:pid文件为文本文件,内容只有一行, 记录了该进程的ID。 
用cat命令能够看到。 
(2) pid文件的做用:防止进程启动多个副本。只有得到pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。 
 

 

调用chdir 改变当前目录为根目录. 

因为进程运行过程当中,当前目录所在的文件系统(如:“/mnt/usb”)是不能卸载的,为避免对之后的使用形成麻烦,改变工做目录为根目录是必要的。若有特殊须要,也能够改变到特定目录,如“/tmp”。

 重设文件权限掩码

fork 函数建立的子进程,继承了父进程的文件操做权限,为防止对之后使用文件带来问题,须要。文件权限掩码,设定了文件权限中要屏蔽掉的对应位。这个跟文件权限的八进制数字模式表示差很少,将现有存取权限减去权限掩码(或作异或运算),就可产生新建文件时的预设权限。调用 umask 设置文件权限掩码,一般是重设为 0,清除掩码,这样能够大大加强守护进程的灵活性。

下面是一个用 Python 实现一个Daemon 进程

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


class Daemon:
    """
     A generic daemon class.

     Usage: subclass the Daemon class and override the run() method
     """

    def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile

    def daemonize(self):
        """
        do the UNIX double-fork magic, see Stevens' "Advanced
        Programming in the UNIX Environment" for details (ISBN 0201563177)
        http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
        """
        try:
            pid = os.fork()

            if pid > 0:
                # exit first parent
                sys.exit(0)

        except OSError, e:
            sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)

        # decouple from parent environment
        os.chdir("/")

        os.setsid()

        os.umask(0)

        # do second fork
        try:
            pid = os.fork()

            if pid > 0:
                # exit from second parent
                sys.exit(0)

        except OSError, e:
            sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)

        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = file(self.stdin, 'r')
        so = file(self.stdout, 'a+')
        se = file(self.stderr, 'a+', 0)
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        # write pidfile
        atexit.register(self.delpid)
        pid = str(os.getpid())
        file(self.pidfile, 'w+').write("%s\n" % pid)

    def delpid(self):
        os.remove(self.pidfile)

    def start(self):
        """
        Start the daemon
        """
        # Check for a pidfile to see if the daemon already runs
        try:
            pf = file(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"
            sys.stderr.write(message % self.pidfile)
            sys.exit(1)

        # Start the daemon
        self.daemonize()
        self.run()

    def stop(self):
        """
        Stop the daemon
        """
        # Get the pid from the pidfile
        try:
            pf = file(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"
            sys.stderr.write(message % self.pidfile)
            return  # not an error in a restart

        # Try killing the daemon process
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError, err:
            err = str(err)
            if err.find("No such process") > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print str(err)
                sys.exit(1)

    def restart(self):
        """
        Restart the daemon
        """
        self.stop()
        self.start()

    def run(self):
        """
        You should override this method when you subclass Daemon. It will be called after the process has been
        daemonized by start() or restart().
        """

 

这段代码完整的实现了 Deamon 进程,相对于 Redis,修改了文件掩码,当前文件目录,还使用了 Double Fork技术防止僵尸进程.

相关文章
相关标签/搜索