树莓派启用看门狗防止死机
树莓派是一款性价比不错的设备。它可以当作一台性能够用的Linux主机,其最大额优势是功耗低,大部分人都可以可以接受树莓派的长时间运行。但是在我使用树莓派的一段时间内出现了几次系统崩溃的情况,并且因为使用它作为网关,所以一旦崩溃的话局域网内所有的设备就将无法上网,而每次非要到不能上网时才能发现。另外,树莓派放在远处,每次死机后都得走过去找到它断掉再开机,频繁在运行时断掉会对TF卡造成不好影响。后来在网上搜索到一种解决方案,那就是看门狗。很幸运树莓派自带硬件看门狗,不需要额外搭建电路,不过网上直接搜索到的教程大都已经过期,不过经过一番周折,终于搭建成功。
看门狗被广泛应用在自动化控制领域,大部分需要长时间不间断运行的设备几乎都配备有看门狗电路。当系统正常运行时,我们可以设定每分钟给看门狗发送一个脉冲信号,这个动作可以叫做“喂狗“,只要系统不出故障,就会自动不停得喂狗,而看门狗只要被定期投喂,就相安无事。一旦系统故障,投喂的动作就会中断,看门狗突然没东西吃了就会来咬你,让你清醒过来。让系统清醒的直接体现就是重新启动。(比方可能不恰当,但大概是这个意思)
如果我设置一个看门狗,当树莓派无法ping通路由器;内存占满;或者温度过高时自动重启,那么我就不用再人工干预了。
树莓派搭载的这颗博通CPU自带有硬件看门狗,用起来更加方便顺手,以下是流程。
启用硬件看门狗设备
树莓派的硬件看门狗默认是不启用的,首先需要在系统配置文件中写入相关字段来开启它。
- 编辑系统配置文件
sudo vi /boot/config.txt
- 把下面这一行加到整个文件的底部
activating the hardware watchdog
dtparam=watchdog=on
添加完成以后保存,并退出。然后重启树莓派,待重启完成后,查看设备列表会发现里面多出两个名字分别是’watchdog’和’watchdog0’的看门狗设备,这代表硬件已经启用成功
pi@raspi-tst:~ $ ls -al /dev/watchdog*
crw------- 1 root root 10, 130 May 19 07:09 /dev/watchdog
crw------- 1 root root 253, 0 May 19 07:09 /dev/watchdog0
看起来似乎有两个看门狗,但是实际上只有一个可以用,不过他们彼此之间有关联,不过这不影响。我们进入下一步。
安装看门狗软件
- 输入以下命令来安装看门狗
sudo apt-get install watchdog -y
在以前的版本中,看门狗的配置文件位于/etc/modprobe.d/watchdog.conf)
,但是现在今非昔比,一些老教程中已经过时,目前的配置文件在这个位置/etc/watchdog.conf
- 编辑配置文件
vi /etc/watchdog.conf
因为我使用树莓派当作网关,我的需求就是ping不通路由器时自动重启,此时的设置应该是添加一条ping 192.168.1.1
, 另外我还需要在内存沾满时自动重启,机器同时开启的进程太多时也重启。并且还要保证以上时间发生后15秒钟执行。于是我的配置可以是这样
ping=192.168.1.1
max-load-1 = 24
min-memory = 1
watchdog-device = /dev/watchdog
watchdog-timeout=15
如果你还有其他的需求,请对照以下这个表来选择,需要的功能就取消在其开头的注释符("#“号),不需要的功能就在前面添加上井号来注释掉
#以下是一些默认的配置范例
#ping = 172.31.14.1
#ping = 172.26.1.255
#interface = eth0
#file = /var/log/messages
#change = 1407
# 如果要使能,请去掉行前的注释标号,设置其中一个的值为0来禁用
# 这些值应设定为在正常使用期间不会重启你的设备的值
# (如果你的机器真的死机了,平均载入进程量应该超过25)
#max-load-m=n表示在m分钟内开超过n个进程的时候重启
max-load-1 = 24
#max-load-5 = 18
#max-load-15 = 12
# 注意这是内存分页的数量
# 请检查你的机器的分页大小以获取真实的大小
#最小剩余内存,保持默认即可
#min-memory = 1
#最小可分配内存
#allocatable-memory = 1
#repair-binary = /usr/sbin/repair
#repair-timeout =
#test-binary =
#test-timeout =
#选择看门狗设备,这里用默认值
watchdog-device = /dev/watchdog
# 已经编译进二进制文件的默认值
#这个文件里面的内容就是CPU温度(摄氏温标)的一千倍
temperature-device = /sys/class/thermal/thermal_zone0/temp
#设定为重置温度的一千倍(比如要满80度重启则设置成800000)
# max-temperature = 80000
# 已经编译进二进制文件的默认值
#admin = root
#interval = 1
#logtick = 1
#log-dir = /var/log/watchdog
# 这极大地减少了在你的机器真正加载完成前看门狗不被列入进程表的几率
realtime = yes
priority = 1
# 使能下面这行来检查rsyslogd是否仍然在运行
#pidfile = /var/run/rsyslogd.pid
- 注意:在配置文件中务必有
watchdog-timeout=15
这一行,如果没有这一行,会报以下错误:
cannot set timeout 60 (errno = 22 = ‘Invalid argument’)
它似乎是设置在某个地方的一个默认值,但它是错的,也可能是个BUG。因为树莓派MBC的最大时延是15秒。
启用看门狗
启动
看门狗有多种启动方式,这里使用systemd来启动
sudo systemctl start watchdog
查询状态
在启动完成后键入以下命令来查询状态:
sudo systemctl status watchdog
正常情况可以看到以下内容:
pi@raspi-server:~ $ sudo systemctl status watchdog
● watchdog.service - watchdog daemon
Loaded: loaded (/lib/systemd/system/watchdog.service; static)
Active: active (running) since Thu 2016-05-19 21:04:47 CEST; 33min ago
Process: 627 ExecStart=/bin/sh -c [ $run_watchdog != 1 ] || exec /usr/sbin/watchdog $watchdog_options (code=exited, status=0/SUCCESS)
Process: 623 ExecStartPre=/bin/sh -c [ -z "${watchdog_module}" ] || [ "${watchdog_module}" = "none" ] || /sbin/modprobe $watchdog_module (code=exited, status=0/SUCCESS)
Main PID: 632 (watchdog)
CGroup: /system.slice/watchdog.service
└─632 /usr/sbin/watchdog
May 19 21:04:47 raspi-server watchdog[632]: int=1s realtime=yes sync=no soft=no mla=24 mem=1
May 19 21:04:47 raspi-server watchdog[632]: ping: 192.168.1.1
May 19 21:04:47 raspi-server watchdog[632]: file: no file to check
May 19 21:04:47 raspi-server watchdog[632]: pidfile: no server process to check
May 19 21:04:47 raspi-server watchdog[632]: interface: no interface to check
May 19 21:04:47 raspi-server watchdog[632]: temperature: no sensors to check
May 19 21:04:47 raspi-server watchdog[632]: test=none(0) repair=none(0) alive=/dev/watchdog heartbeat=none to=roo...ce=no
May 19 21:04:47 raspi-server watchdog[632]: watchdog now set to 15 seconds
May 19 21:04:47 raspi-server watchdog[632]: hardware watchdog identity: Broadcom BCM2835 Watchdog timer
May 19 21:04:47 raspi-server systemd[1]: Started watchdog daemon.
Hint: Some lines were ellipsized, use -l to show in full.
停止看门狗
sudo systemctl stop watchdog
设置开机启动
在设置开机启动时如果你使用systemd中的方式:
sudo systemctl enable watchdog
你会看到以下内容,并且启动不成功
Synchronizing state for watchdog.service with sysvinit using update-rc.d...
Executing /usr/sbin/update-rc.d watchdog defaults
Executing /usr/sbin/update-rc.d watchdog enable
The unit files have no [Install] section. They are not meant to be enabled using systemctl.
Possible reasons for having this kind of units are:
1) A unit may be statically enabled by being symlinked from another unit's
.wants/ or .requires/ directory.
2) A unit's purpose may be to act as a helper for some other unit which has
a requirement dependency on it.
3) A unit may be started when needed via activation (socket, path, timer,
D-Bus, udev, scripted systemctl call, ...).
具体原因详见参考文献。解决方法有两种,
通过rc.local
实现开机启动。
sudo vi /etc/rc.local
在exit 0
之前加上以下两行内容,就像这样:
printf "Starting software Watchdog"
/usr/sbin/service watchdog start &
exit 0
这个”&“的意思是放入后台运行,不加的话可能出现莫名其妙的问题
通过Crontab
实现开机启动
crontab -e
添加这一行:
@reboot sudo /usr/sbin/service watchdog start
通过上述两种方式,看门狗都可以开机启动,你可以通过dmesg | grep watchdog
或者cat /var/log/syslog | grep watchdog
测试
在确认看门狗已经开启(很重要!!!)后,我们通过一个逻辑炸弹来检测看门狗是否能起作用。这个炸弹英文名为'Fork Bomb',可以快速耗尽机器内存使机器崩溃。
在命令行下键入以下内容:
: (){ :|:& };:
不出意外15秒后,树莓派会自动重启。看门狗成功实现功能!
参考文献
附录
附上两个版本的Fork Bomb,均测试通过并能使树莓派重启。
Shell Script
#!/bin/bash
echo "Starting shell fork bomb"
# prevent swapping to the SD card!
sudo systemctl stop dphys-swapfile.service
# start the bomb
: (){ :|:& };:
Python
#!/usr/bin/python
#-------------------------------------------------------------------------------
# Name: fork.bomb
# Purpose:
#
# Author: paulv
#
# Created: 09-05-2016
# Copyright: (c) paulv 2016
# Licence: <your licence>
#-------------------------------------------------------------------------------
import os
import subprocess
def main():
print "fork bomb starting"
# prevent swapping to the SD card!
subprocess.call(['sudo systemctl stop dphys-swapfile.service'], shell=True, \
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while True:
os.fork()
if __name__ == '__main__':
main()