【问题标题】:linux detect system shutdown early in pythonlinux在python早期检测系统关闭
【发布时间】:2016-08-12 14:42:10
【问题描述】:

我一直在为我作为无头服务器运行的树莓派编写监视脚本。作为其中的一部分,我希望它对关闭事件做出反应。 我尝试使用signal 模块,它确实会做出反应并调用我的关机程序,但是它发生在关机程序的后期,我想尝试找到一种方法让它在关机请求后快速反应发出,而不是等待操作系统要求python退出。

这是在树莓派 1 B 上运行,使用最新的 jessie lite 映像 我正在使用 python 3,我的 python 脚本本身就是 init 脚本:

监视器:

#!/usr/bin/python3
### BEGIN INIT INFO
# Provides:          monitor
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start the monitor daemon
# Description:       Start the monitor daemon during system boot
### END INIT INFO

import os, psutil, socket, sys, time
from daemon import Daemon
from RPLCD import CharLCD
from subprocess import Popen, PIPE
import RPi.GPIO as GPIO

GPIO.setwarnings(False)

def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    output = output.decode('utf8')
    return float(output[output.index('=') + 1:output.rindex("'")])

class MyDaemon(Daemon):
    def run(self):
        lcd = CharLCD(pin_rs=7, pin_rw=4, pin_e=8, pins_data=[25, 24, 23, 18], numbering_mode=GPIO.BCM, cols=40, rows=2, dotsize=8)

        while not self.exitflag:
            gw = os.popen("ip -4 route show default").read().split()
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            try:
                s.connect((gw[2], 0))
                ipaddr = s.getsockname()[0]
                lcd.cursor_pos = (0, 0)
                lcd.write_string("IP:" + ipaddr)
                gateway = gw[2]
                lcd.cursor_pos = (1, 0)
                lcd.write_string("GW:" + gateway)
            except IndexError:
                lcd.cursor_pos = (0, 0)
                lcd.write_string("IP:No Network")
                lcd.cursor_pos = (1, 0)
                lcd.write_string("GW:No Network")

            host = socket.gethostname()
            lcd.cursor_pos = (0, 20)
            lcd.write_string("Host:" + host)

            for num in range(10):
                temp = get_cpu_temperature()
                perc = psutil.cpu_percent()
                lcd.cursor_pos = (1, 20)
                lcd.write_string("CPU :{:5.1f}% {:4.1f}\u00DFC".format(perc, temp))
                if (self.exitflag):
                    break
                time.sleep(2)
        lcd.clear()
##      lcd.cursor_pos = (13, 0)
        lcd.write_string("Shutting Down")

if __name__ == "__main__":
    daemon = MyDaemon('/var/run/monitor.pid')
    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()
        elif 'run' == sys.argv[1]:
            daemon.run()
        else:
            print("Unknown command")
            sys.exit(2)
        sys.exit(0)
    else:
        print("usage: %s start|stop|restart" % sys.argv[0])
        sys.exit(2)

daemon.py:

"""Generic linux daemon base class for python 3.x."""

import sys, os, time, signal

class Daemon:
    """A generic daemon class.
    Usage: subclass the daemon class and override the run() method."""
    def __init__(self, pidfile):
        self.pidfile = pidfile
        self.exitflag = False
        signal.signal(signal.SIGINT, self.exit_signal)
        signal.signal(signal.SIGTERM, self.exit_signal)

    def daemonize(self):
        """Deamonize class. UNIX double fork mechanism."""
        try: 
            pid = os.fork() 
            if pid > 0:
                # exit first parent
                sys.exit(0) 
        except OSError as err: 
            sys.stderr.write('fork #1 failed: {0}\n'.format(err))
            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 as err: 
            sys.stderr.write('fork #2 failed: {0}\n'.format(err))
            sys.exit(1) 

        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = open(os.devnull, 'r')
        so = open(os.devnull, 'a+')
        se = open(os.devnull, 'a+')

        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        pid = str(os.getpid())
        with open(self.pidfile,'w+') as f:
            f.write(pid + '\n')

    def start(self):
        """Start the daemon."""
        # Check for a pidfile to see if the daemon already runs
        try:
            with open(self.pidfile,'r') as pf:
                pid = int(pf.read().strip())
        except IOError:
            pid = None

        if pid:
            message = "pidfile {0} already exist. Daemon already running?\n"
            sys.stderr.write(message.format(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:
            with open(self.pidfile,'r') as pf:
                pid = int(pf.read().strip())
        except IOError:
            pid = None

        if not pid:
            message = "pidfile {0} does not exist. Daemon not running?\n"
            sys.stderr.write(message.format(self.pidfile))
            return # not an error in a restart

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

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

    def exit_signal(self, sig, stack):
        self.exitflag = True
        try:
            os.remove(self.pidfile)
        except FileNotFoundError:
            pass

    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()."""

所以简而言之,有什么方法可以让我在关机时尽早检测到关机,无论它是如何调用的,并且最好能够从 python 中检测到重启

【问题讨论】:

  • 基于systemd的系统上的启动和关闭顺序是通过分析单元文件(AfterBeforeWantsRequiresConflicts等)中的依赖关系来处理的.)。您可能需要复制由 jessie 自动生成的单元文件(在 /run/systemd/system 中 - 与 RedHat/CentOS 不同,Debian jessie 仍然没有完全转换为 systemd - 从某种意义上说它仍然只是在 /etc/init.d 中包含所有内容,但是自动为它们生成 LSB 单元文件)到/etc/systemd/system 并适当地修改它。
  • 你提到了/run/systemd/system 在我的 pi 上是空的,/etc/systemd/system 不包含与我的服务相关的任何文件我应该在其他地方查看吗?
  • 我没有发现您没有运行完整的 Debian jessie - 我不熟悉“精简版”...但是在完整版中,@987654336 有三个位置@ 查找单元文件 - 按顺序,这些是 /etc/systemd/system/run/systemd/system/lib/systemd/system,其想法是 /etc/... 具有本地配置和修改的内容,/run/... 是所有自动生成的位,并且/lib/... 是安装包中的内容。精简版可能已经调整了路径等 - 检查文档,我猜......
  • 我的理解是“lite”版本去掉了 GUI/桌面的东西以节省空间,我不明白为什么 lite 版本会改变 init 系统路径?但我会调查一下,这可能是一个 rasbpian 特定的事情
  • 为了清楚起见,我确实在/etc/systemd/system 中找到了文件,但与我的服务无关,您确定它们是自动创建的吗?我可以看到,当安装了一个包时,可能会为它创建一个包,但由于这是我写的东西,我还没有为它创建一个单元文件

标签: python linux python-3.x shutdown


【解决方案1】:

不要反应。 时间表

Unixoid 系统具有完善的机制,用于在启动和关闭时启动和停止服务。只需添加其中之一即可在系统关闭时停止;您通常甚至可以定义调用这些关闭脚本的顺序。

现在,我不知道您的 Linux 使用哪些系统。您可能正在使用其中的任何一个

  • SysV 风格的初始化脚本(经典)
  • systemd(相对较新)
  • 新贵(如果您正在运行 Canonical 的错误实验之一)

无论哪种方式,您的系统上都有很多此类服务文件的示例;如果您正在运行 systemd,systemctl 将显示当前加载了哪些服务,并显示您应该查看哪些文件作为您自己的服务进行复制和添加。如果您正在运行 SysV 样式的 init,请查看 /etc/init.d 以获取大量脚本。

您会发现很多关于如何为特定运行级别/系统目标添加和启用初始化脚本或 systemd 服务文件的信息。

【讨论】:

  • 它正在使用 systemd,它已被安排,但我不知道如何安排它尽快发生,脚本位于 /etc/init.d 中。我已经通过运行update-rc.d monitor defaults 然后update-rc.d monitor enable 将其添加到启动中,我确实找到了一个参考,说我可以在启用后指定一个序列号,如下所示:update-rc.d monitor enable 5 并且默认值为 20,但是当我查看帮助时update-rc.d 打印的文本似乎表明数字是运行级别而不是序列号
猜你喜欢
  • 2011-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 2014-01-28
  • 1970-01-01
相关资源
最近更新 更多