【发布时间】: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的系统上的启动和关闭顺序是通过分析单元文件(After、Before、Wants、Requires、Conflicts等)中的依赖关系来处理的.)。您可能需要复制由 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