前言
在嵌入式设备中,我们有时候有这样一个需求:需要一个程序(python,shell,c程序)来检测我们设备侧运行的程序是否异常退出(需要根据设备的角色,模式以及运行的环境来检测不同的程序)。因为linux嵌入式设备有些是没有python的(python写是最简单的)。所以我们考虑用c语言或者shell脚本来实现。
程序设计思路:
- 创建一个配置文件,配置文件中包含需要检测的程序,以及该程序运行的环境等,我的配置文件如下:
# space and tab are not allowed in names.
#
# - app_name: executable name
# - interval: check interval in second
# - role: in which role this check is enabled, aligned
# with MIB_MERCKU_MODE (weird name):
# - any: undefined - always enabled
# - gw: gw
# - node: node
# - mode: op_mode follows MIB definition
# - any: any
# - router: router
# - bridge: bridge
# - operation:program call method
#
#
# app_name interval(s) role mode operation
# This configuration file is used to detect program running
thttpd 30 gw router thttpd -C /www/thttpd.conf
elinksdk 30 gw any elinksdk
elinkclient 5 gw any elinkclient
routerd 30 any any routerd
smbd 30 gw router samba.sh restart
该配置文件包含了检测的引用程序名称、检测的间隔时间、设备的角色、设备当前的模式、以及检测死掉之后需要运行的脚本。
TODO: 强烈建议采用配置文件的方式。这样做可读性和可扩展性更高
- 配置文件创建好后,我们需要编写程序来检测程序是否运行,如果没有运行,则拉起来。我推荐使用shell脚本的方式。因为使用c语言编写的话没有shell方便(解析文件没有shell方便)。同时shell方便调试。检测的程序我们没有运行效率的要求,那么原则上什么方式简单我们就采用什么方式
检测程序如下
#!/bin/bash
. /lib/functions.sh
. /lib/services.sh
#读取到的格式为 app interval role mode operation
#thttpd 30 1 1 "thttpd -C /www/thttpd.conf"
TIMELYCHECK_CONFIG_FILE="/lib/etc/timelycheck.conf"
BASE_TIME_S=5
__read_timelycheck_conf()
{
while read line
do
if [[ "$line" =~ ^[0-9a-zA-Z]+ ]]; then
timelycheck_array[i]="$line"
let i=i+1
fi
done < $TIMELYCHECK_CONFIG_FILE
}
__check_app()
{
local app_name=$1
local interval=$2
local role=$3
local mode=$4
local operation=$5
if [[ "$app_name" == "" || "$interval" == "" || "$role" == "" || "$mode" == "" || "$operation" == "" ]]; then
echo "check_app paramter error"
return -1
fi
is_running=`ps | grep $app_name | grep -v grep`
if [[ "$is_running" == "" ]]; then
##########如果程序没有运行,则检查是否应该运行 NODE不会工作在主路由模式
###如果在节点和主路由器都要运行
if [[ "$role" == "any" ]]; then
if [[ "$mode" == "any" ]]; then
$operation & >/dev/null
elif [[ "$mode" == "router" ]] && ! is_bridge; then
$operation & >/dev/null
elif [[ "$mode" == "bridge" ]] && is_bridge; then
$operation & >/dev/null
fi
elif [[ "$role" == "gw" ]] && is_gw; then
if [[ "$mode" == "any" ]]; then
$operation & >/dev/null
elif [[ "$mode" == "router" ]] && ! is_bridge; then
$operation & >/dev/null
elif [[ "$mode" == "bridge" ]] && is_bridge; then
$operation & >/dev/null
fi
elif [[ "$role" == "node" ]] && is_gw; then
if [[ "$mode" == "any" ]]; then
$operation & >/dev/null
elif [[ "$mode" == "bridge" ]] && is_bridge; then
$operation & >/dev/null
fi
fi
fi
}
__read_timelycheck_conf
#声明一个关联数组,对应着 app和当前运行的时间,用于超时判断
declare -A assArray_app
for i in ${!timelycheck_array[@]}
do
idx_app_name=`echo ${timelycheck_array[i]} | awk '{print $1}'`
interval=`echo ${timelycheck_array[i]} | awk '{print $2}'`
assArray_app[$idx_app_name]=$interval
done
while true
do
for i in ${!timelycheck_array[@]}
do
app_name=`echo ${timelycheck_array[i]} | awk '{print $1}'`
interval=`echo ${timelycheck_array[i]} | awk '{print $2}'`
role=`echo ${timelycheck_array[i]} | awk '{print $3}'`
mode=`echo ${timelycheck_array[i]} | awk '{print $4}'`
operation=`echo ${timelycheck_array[i]} | awk '{for (i=5;i<=NF;i++){printf("%s ", $i)}}'`
cure_time=${assArray_app[$app_name]}
#判断是否满足检测时间,如果满足才检测
if [[ $interval -le $cure_time ]]; then
__check_app $app_name $interval $role $mode "$operation"
assArray_app[$app_name]=0
else
let assArray_app[$app_name]=$cure_time+$BASE_TIME_S
fi
done
sleep $BASE_TIME_S
done
程序解析
- 在程序入口处,我们循环读取了配置文件。这里使用了if的正则表达式,排除了以#开头的注释行。只获取有效数据
- 将读取到的有效数据解析,然后存储到一个关联数组中(关于关联数组,其实就类似于python中的字典)。关联数组中的key为检测程序的名字,val为当前app记录的时间(程序中会使用该时间来判断是否大于等于interval时间,如果是才会去检测对应的app)
- 进入while循环,分离出app_name,interval,role,mode和operation的值。判断app上次检测间隔的时间是否满足大于interval,如果是才会去检测
- 检测程序很简单,就是根据设备的信息来判断是否需要检测,如果需要,同时app又没有运行,则这行operation程序
TODO: sh是不支持关联数组以及数组的,这里必须采用bash。如果你的设备没有bash,那么就需要移植,或者改写该程序
sh脚本如下
- 如果我们的设备为了跑一个脚本而去移植bash,我觉得是得不偿失的。bash很大,会占用flash空间,导致固件变大,所以我改写了代码,下面是sh的实现方式
#!/bin/sh
. /lib/functions.sh
. /lib/services.sh
#读取到的格式为 app interval role mode operation
#thttpd 30 1 1 "thttpd -C /www/thttpd.conf"
TIMELYCHECK_CONFIG_FILE="/lib/etc/timelycheck.conf"
BASE_TIME_S=5
__read_timelycheck_conf()
{
arry_cnt=0
while read line
do
if ! echo $line | grep -E "^#"; then
eval timelycheck_arry$arry_cnt=\"$line\" #使用该方式来代替数组(变量名加下标的方式)
let arry_cnt=arry_cnt+1
fi
done < $TIMELYCHECK_CONFIG_FILE
}
__check_app()
{
local app_name=$1
local interval=$2
local role=$3
local mode=$4
local operation=$5
if [[ "$app_name" == "" || "$interval" == "" || "$role" == "" || "$mode" == "" || "$operation" == "" ]]; then
echo "check_app paramter error"
return -1
fi
is_running=`ps | grep $app_name | grep -v grep`
if [[ "$is_running" == "" ]]; then
#TODO: 如果发现程序有没有运行,先杀掉启动程序命令再去拉起,防止运行多个拉起的命令
cmd=`echo $operation | awk '{print $1}'`
cmd_pid=`ps | grep $cmd | grep -v grep | awk '{print $1}'`
[[ "$cmd_pid" != "" ]] && kill -9 $cmd_pid
##########如果程序没有运行,则检查是否应该运行 NODE不会工作在主路由模式
###如果在节点和主路由器都要运行
if [[ "$role" == "any" ]]; then
if [[ "$mode" == "any" ]]; then
$operation & >/dev/null
elif [[ "$mode" == "router" ]] && ! is_bridge; then
$operation & >/dev/null
elif [[ "$mode" == "bridge" ]] && is_bridge; then
$operation & >/dev/null
fi
elif [[ "$role" == "gw" ]] && is_gw; then
if [[ "$mode" == "any" ]]; then
$operation & >/dev/null
elif [[ "$mode" == "router" ]] && ! is_bridge; then
$operation & >/dev/null
elif [[ "$mode" == "bridge" ]] && is_bridge; then
$operation & >/dev/null
fi
elif [[ "$role" == "node" ]] && is_gw; then
if [[ "$mode" == "any" ]]; then
$operation & >/dev/null
elif [[ "$mode" == "bridge" ]] && is_bridge; then
$operation & >/dev/null
fi
fi
fi
}
__read_timelycheck_conf
let arry_cnt=arry_cnt-1
for i in $(seq 0 $arry_cnt)
do
#eval echo '$'{timelycheck_arry$i}
#TODO: 这里的格式为assArray_appName1=interval的格式 下标{1,2,3}和timelycheck_arry1 2 3对应
eval assArray_appcure_time${i}=`eval echo '$'{timelycheck_arry$i} | awk '{print $2}'`
done
while true
do
for i in $(seq 0 $arry_cnt)
do
app_name=`eval echo '$'{timelycheck_arry$i} | awk '{print $1}'`
interval=`eval echo '$'{timelycheck_arry$i} | awk '{print $2}'`
role=`eval echo '$'{timelycheck_arry$i} | awk '{print $3}'`
mode=`eval echo '$'{timelycheck_arry$i} | awk '{print $4}'`
operation=`eval echo '$'{timelycheck_arry$i} | awk '{for (i=5;i<=NF;i++){printf("%s ", $i)}}'`
cure_time=`eval echo '$'{assArray_appcure_time$i}`
#判断是否满足检测时间,如果满足才检测
if [[ $interval -le $cure_time ]]; then
__check_app $app_name $interval $role $mode "$operation"
eval assArray_appcure_time$i=0
else
let cure_time=cure_time+$BASE_TIME_S
eval assArray_appcure_time$i=$cure_time
fi
done
sleep $BASE_TIME_S
done