Linux的守护进程

张彤 2021年10月23日 283次浏览

"守护进程"(daemon)就是一直在后台运行的进程(daemon),是服务(service)的载体。

概念

  • Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

  • 守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。

  • 一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。

  • 守护进程的名称通常以d结尾,比如sshd、xinetd、crond等,daemon

创建守护进程步骤

进程组 :

  • 每个进程也属于一个进程组
  • 每个进程组都有一个进程组号,该号等于该进程组组长的PID号 .
  • 一个进程只能为它自己或子进程设置进程组ID号

会话期:

  • 会话期(session)是一个或多个进程组的集合。

  • setsid()函数可以建立一个对话期:

  • 如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。

  1. 此进程变成该对话期的首进程
  2. 此进程变成一个新进程组的组长进程。
  3. 此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。
  4. 为了保证这一点,我们先调用fork()然后exit(),此时只有子进程在运行

遇到的问题

本文将以一个内存警告程序作为示例,进行演示。

# -*- coding: utf-8 -*-
# filename : monitor.py

import os
import time
import random

def get_timestamp():
    """
    """
    return time.strftime('%Y-%m-%d %X')


def get_free_info():
    """
    """
    tot_m, used_m, free_m = map(int, os.popen('free -m').readlines()[1].split()[1:4])
    #return round(1-(free_m/(tot_m*1.00)),2),free_m
    return round(used_m/(tot_m*1.00),2),free_m

def get_cpu_info():
    """
    """
    CPU_Pct=str(
                round(
                      float(
        os.popen('''grep 'cpu ' /proc/stat | awk '{usage=($2+$4)/($2+$4+$5)} END {print usage }' ''').readline()),2))
    return CPU_Pct

def warn_free():
    """
    """
    threshold = 10000
    z  = []
    while Ture:
        if get_free_info()[0] < 0.8 and get_free_info()[1] > 100:
            print("%s memory Except"%get_timestamp())
    time.sleep(random.randint(0,10))

if __name__ == '__main__':
    warn_free()

当我们在linux上部署这个程序的时候,使用命令

python3 ./monitor.py

这个时候,程序就跑起来了
当内存超标的时候,我们可以看到打印结果。
看上去非常Happy,但是当你退出xshell,或者当前界面的时候,问题就来了,你会发现,你一退出命令行窗口,紧接着,你的程序也被干掉了,进程号也没了。
那么如何让你的程序变成守护进程(deamon),进而形成服务(server)呢

前台和后台

  • 上面例子中的任务,我们称为前台任务(foreground job),它会独占命令行窗口,只有运行完了或者手动中止,才能执行其他命令。
  • 变成守护进程的第一步,就是把它改成"后台任务"(background job)。
python3 ./monitor.py &

只要在命令的尾部加上符号&,启动的进程就会成为"后台任务"。
如果要让正在运行的"前台任务"变为"后台任务",可以先按ctrl + z,然后执行bg命令(让最近一个暂停的"后台任务"继续执行)。

"后台任务"有两个特点

  1. 继承当前 session (对话)的标准输出(stdout)和标准错误(stderr)。因此,后台任务的所有输出依然会同步地在命令行下显示。
  2. 不再继承当前 session 的标准输入(stdin)。你无法向这个任务输入指令了。如果它试图读取标准输入,就会暂停执行(halt)。

SIGHUP信号

  1. 用户准备退出 session
  2. 系统向该 session 发出SIGHUP信号
  3. session 将SIGHUP信号发给所有子进程
  4. 子进程收到SIGHUP信号后,自动退出

上面的流程解释了,为什么"前台任务"会随着 session 的退出而退出
那么,"后台任务"是否也会收到SIGHUP信号
这由 Shell 的huponexit参数决定的。

shopt | grep huponexit
  • 大多数Linux系统,这个参数默认关闭(off)。因此,session 退出的时候,不会把SIGHUP信号发给"后台任务"。所以,一般来说,"后台任务"不会随着 session 一起退出。

nohup 命令

nohup python3 ./monitor.py &
  1. 阻止SIGHUP信号发到这个进程。
  2. 关闭标准输入。该进程不再能够接收任何输入,即使运行在前台。
  3. 重定向标准输出和标准错误到文件nohup.out。

systemctl

  • 除了专用工具以外,Linux系统有自己的守护进程管理工具 Systemd 。它是操作系统的一部分,直接与内核交互,性能出色,功能极其强大。我们完全可以将程序交给 Systemd ,让系统统一管理,成为真正意义上的系统服务。