實現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技術防止殭屍進程.

相關文章
相關標籤/搜索