守护进程, 孤儿进程, 僵尸进程与waitpid

GNU精神
• 阅读 5725

守护进程是在一类脱离终端在后台执行的程序, 通常以d结尾, 随系统启动, 其父进程(ppid)通常是init进程

一般要让当前程序以守护进程形式运行, 在命令后加&并重定向输出即可

$ python someprogram.py > /dev/null 2>&1 &

或者使用nohup也可以
这是直接运行程序的方式, 如果是用具体语言代码的形式来实现呢, 首先看一下守护进程的实现方式

  1. 创建子进程, 父进程退出
    父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程

  2. 在子进程中创建新会话

    1. 更改工作目录到/, 以便umount一个文件系统

    2. 重设文件权限掩码, 以便拥有完全的写的权限, 即重设继承来的默认文件权限值

    3. 调用setuid, 让当前进程成为新的会话组长和进程组长

  3. 执行第二次fork
    关闭文件描述符, 一般是输入/输出和错误输出, 重定向到/dev/null

py代码
https://gist.github.com/jamiesun/3097215

上面守护进程的生成步骤中涉及到了孤儿进程
任何孤儿进程产生时都会立即为系统进程init自动接收为子进程,这一过程也被称为“收养”. 但由于创建该进程的进程已不存在,所以仍应称之为“孤儿进程”

与之相关的一个概念就是 僵尸进程了. 当子进程退出时, 父进程需要wait/waitpid系统调用来读取子进程的exit status, 然后子进程被系统回收. 如果父进程没有wait的话, 子进程将变成一个"僵尸进程", 内核会释放这个子进程所有的资源,包括打开的文件占用的内存等, 但在进程表中仍然有一个PCB, 记录进程号和退出状态等信息, 并导致进程号一直被占用, 而系统能使用的进程号数量是有限的(可以用ulimit查看相关限制), 如果产生大量僵尸进程的话, 将因为没有可用的进程号而导致系统不能产生新的进程

因此很多自带重启功能的服务实现就是用wait/waitpid实现的.
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束
比如tornado中fork多进程就是这样, 监控子进程的运行状态, 当其意外退出时自动重启子进程

def fork_processes(num_processes, max_restarts=100):
    """Starts multiple worker processes.

    If ``num_processes`` is None or <= 0, we detect the number of cores
    available on this machine and fork that number of child
    processes. If ``num_processes`` is given and > 0, we fork that
    specific number of sub-processes.

    Since we use processes and not threads, there is no shared memory
    between any server code.

    Note that multiple processes are not compatible with the autoreload
    module (or the ``autoreload=True`` option to `tornado.web.Application`
    which defaults to True when ``debug=True``).
    When using multiple processes, no IOLoops can be created or
    referenced until after the call to ``fork_processes``.

    In each child process, ``fork_processes`` returns its *task id*, a
    number between 0 and ``num_processes``.  Processes that exit
    abnormally (due to a signal or non-zero exit status) are restarted
    with the same id (up to ``max_restarts`` times).  In the parent
    process, ``fork_processes`` returns None if all child processes
    have exited normally, but will otherwise only exit by throwing an
    exception.
    """
    global _task_id
    assert _task_id is None
    if num_processes is None or num_processes <= 0:
        num_processes = cpu_count()
    if ioloop.IOLoop.initialized():
        raise RuntimeError("Cannot run in multiple processes: IOLoop instance "
                           "has already been initialized. You cannot call "
                           "IOLoop.instance() before calling start_processes()")
    gen_log.info("Starting %d processes", num_processes)
    children = {}

    def start_child(i):
        pid = os.fork()
        if pid == 0:
            # child process
            _reseed_random()
            global _task_id
            _task_id = i
            return i
        else:
            children[pid] = i
            return None
    for i in range(num_processes):
        id = start_child(i)
        if id is not None:
            return id
    num_restarts = 0
    while children:
        try:
            pid, status = os.wait()
        except OSError as e:
            if errno_from_exception(e) == errno.EINTR:
                continue
            raise
        if pid not in children:
            continue
        id = children.pop(pid)
        if os.WIFSIGNALED(status):
            gen_log.warning("child %d (pid %d) killed by signal %d, restarting",
                            id, pid, os.WTERMSIG(status))
        elif os.WEXITSTATUS(status) != 0:
            gen_log.warning("child %d (pid %d) exited with status %d, restarting",
                            id, pid, os.WEXITSTATUS(status))
        else:
            gen_log.info("child %d (pid %d) exited normally", id, pid)
            continue
        num_restarts += 1
        if num_restarts > max_restarts:
            raise RuntimeError("Too many child restarts, giving up")
        new_id = start_child(id)
        if new_id is not None:
            return new_id
    # All child processes exited cleanly, so exit the master process
    # instead of just returning to right after the call to
    # fork_processes (which will probably just start up another IOLoop
    # unless the caller checks the return value).
    sys.exit(0)

参考: http://bbs.chinaunix.net/thread-4071026-1-1.html

点赞
收藏
评论区
推荐文章
芝士年糕 芝士年糕
3年前
如何在 Linux 命令行中终止进程?
如果你想在linux上停止某个进程,你会怎么操作?如果命令/进程在前台运行,您可以使用CtrlC终端快捷方式,但是,如果进程不可见(在后台运行),您可以使用专用命令“杀死它”。“终止进程”是指在执行过程中停止进程,如果您知道进程ID(PID),则可以使用kill命令,如下所示:kill在上面的语法中,signal指的是要发送终止的终止信号,
Easter79 Easter79
4年前
think
Supervisor的安装与使用入门在linux或者unix操作系统中,守护进程(Daemon)是一种运行在后台的特殊进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。由于在linux中,每个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相
Easter79 Easter79
4年前
supervisor运行golang守护进程
Supervisor是一个C/S系统,它可以在类UNIX系统上控制系统进程,由python编写,它提供了大量的功能来实现对进程的管理。程序的多进程启动,可以配置同时启动的进程数,而不需要一个个启动程序的退出码,可以根据程序的退出码来判断是否需要自动重启程序所产生日志的处理进程初始化的环境,包括目录,用户,umask,
Wesley13 Wesley13
4年前
MongoDB后台运行
文章目录命令方式(推荐)命令行和配置文件方式命令行:配置文件:命令方式(推荐)如果想在后台运行,启动时只需添加fork函数即可。fork:以守护进程的方式运行MongoDB。指定日志输出路径,而不是输出到命令行bin/mongodbfork
Stella981 Stella981
4年前
Linux进程守护——Supervisor 使用记录
0、旁白Supervisor是个父进程,你要守护的进程会以Supervisor的子进程形式存在,所以老子才可以管儿子官网链接:http://supervisord.org/(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fsupervisord.org%2F)【5、参数
Stella981 Stella981
4年前
Linux查找所有正在运行的守护进程(daemon)
pseoppid,pid,sid,stat,tty,comm |awk'{if($2$3&&$5"?"){print$0};}'首先,要注意,守护进程(daemon)和后台进程(backgroundprocess)有区别。守护进程是一种后台进程,但是,同时,它必须具备以下特性:1\.没
Stella981 Stella981
4年前
Linux常用的命令和脚本
一、如何杀死linux系统中的僵尸进程,僵尸进程已经死亡了,所以没有办法杀死他们,但是可以通过杀死其父进程来清除僵尸进程:killHUP$(psAostat,ppid|grepe'\zZ\'|awk'
Stella981 Stella981
4年前
Python实现守护进程
概念守护进程(Daemon)也称为精灵进程是一种生存期较长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。unix系统有很多守护进程,大多数服务器都是用守护进程实现的,例如inetd守护进程。需要了解的相关概念进程(process)
Stella981 Stella981
4年前
Golang并发解读
进程与线程概念在面向进程设计的系统中,进程(process)是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。进程是程序(指令和数据)的真正运行实例。用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。线程(th
Stella981 Stella981
4年前
Docker 备忘
C/S架构:  客户端发出命令给服务器端(内含守护进程),守护进程执行命令后将结果传回给客户端。(可以远程访问,可以本地访问)容器内部操作:  !(https://oscimg.oschina.net/oscnet/26aeb55b82e3279e96156320bf832c90a92.png)!(https:
胖大海 胖大海
3年前
linux 僵尸进程处理
僵尸进程:就是已经结束了的进程,但是没有从进程表中删除,如果过多僵尸进程导致其他重要任务没有PID可用,进而导致系统崩溃。这是真实可能发生的,它有一定的概率,特别当存在一个编码糟糕的程序开始大量产生僵尸进程的时候,在这种情况下,找到并杀死僵尸进程是一个明智的做法。如何找到僵尸进程top命令用ps命令和grep命令寻找僵尸进程:psAostat,