脚本进程保活
# 05.脚本进程保活
# 目录介绍
- 01.核心设计理念
- 1.1 守护进程模式
- 1.2 进程生命周期
- 1.3 整体设计思路
- 02.脚本守护进程
- 2.1 理解脚本进程
- 2.2 脚本如何变进程
- 2.3 进程直观验证
- 2.4 守护脚本进程模型
- 2.5 与应用进程区别
- 03.脚本进程优缺点
- 3.1 脚本进程优势
- 3.2 脚本进程劣势
- 04.脚本守护实践
- 4.1 简单守护循环
- 4.2 添加日志系统
- 4.3 防止多实例运行
- 4.4 环境变量支持
- 4.5 优雅终止应用
- 4.6 异常重启检测
- 4.7 日志轮换与清理
- 4.8 完整整合脚本
- 4.9 设计模式应用
- 05.稳定性的设计
- 5.1 异常重启检测
- 5.2 退出码语义化
- 5.3 日志和监控体系
# 01.核心设计理念
# 1.1 守护进程模式
设计思想: 持续监控目标应用,确保服务的高可用性。
while true; do
# 启动应用
# 监控退出状态
# 决定是否重启
done
2
3
4
5
# 1.2 进程生命周期
启动阶段:
- 单例模式:通过PID文件确保只有一个watchdog实例运行
- 环境准备:设置库路径、创建日志目录
- 历史清理:清理旧进程、旧日志
运行阶段:
- 异常检测:监控频繁重启(60秒内3次)
- 优雅重启:先TERM信号,再KILL信号
- 状态记录:详细的日志记录
退出阶段:
- 资源清理:删除PID文件和临时文件
# 1.3 整体设计思路
- 守护脚本需要定期检查目标Qt应用是否在运行。
- 如果应用不在运行,则启动它。
- 为了避免频繁检查导致系统负担,可以设置一个合理的检查间隔。
- 同时,可能还需要考虑脚本本身的可靠性,使用循环加休眠的方式。
# 02.脚本守护进程
# 2.1 理解脚本进程
Shell 脚本启动时,操作系统通过解释器(如bash)创建一个新的进程,该进程拥有所有进程特性(PID、资源隔离、信号响应等)。脚本中的命令在此进程内顺序执行,其启动的子进程会形成进程树。守护脚本的本质是 创建一个永不退出的父进程,由其负责监控和重启子进程。
# 2.2 脚本如何变进程
执行权限:当运行 ./script.sh 或 bash script.sh时,操作系统首先检查文件是否有可执行权限(x)。
解释器介入:
- 若直接执行 ./script.sh,系统会读取脚本首行的 Shebang(#!/bin/bash),启动对应的解释器(如 /bin/bash)。
- 若通过 bash script.sh执行,则直接由指定的解释器处理。
进程创建:
# 操作系统实际执行的动作:
/bin/bash ./script.sh # 创建一个新的bash进程,并将脚本作为参数传递
2
此时系统会分配独立的 PID、内存空间和文件描述符,形成一个完整的进程。
# 2.3 进程直观验证
首先启动进程脚本
[root@RV1126_RV1109:/]# chmod +x /userdata/palmApp/watchdog_palmapp.sh
[root@RV1126_RV1109:/]# echo "My PID: $$"
My PID: 3871
[root@RV1126_RV1109:/]# ./userdata/palmApp/watchdog_palmapp.sh &
2
3
4
然后查询进程脚本的信息,如下所示:
[root@RV1126_RV1109:/]# ps -aux | grep palm
root 590 0.0 0.1 2392 1580 ? S 11:58 0:00 /bin/sh /userdata/palmApp/watchdog_palmapp.sh
root 641 0.0 0.0 2392 424 pts/0 S+ 14:03 0:00 grep palm
root 644 3.1 7.2 425536 66932 ? SLl 11:58 3:55 /userdata/yt-palm/yt-palm -platform linuxfb
[root@RV1126_RV1109:/]# ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 0 827 1 20 0 2392 436 poll_s Ss+ ttyFIQ0 0:00 -/bin/sh
0 0 905 3871 20 0 2256 296 - R+ pts/0 0:00 ps l
0 0 3871 751 20 0 2392 1680 wait Ss pts/0 0:00 /bin/sh -l
[root@RV1126_RV1109:/]# cd /tmp/palmapp_watchdog.pid
/bin/sh: cd: can't cd to /tmp/palmapp_watchdog.pid
[root@RV1126_RV1109:/]# cat /tmp/palmapp_watchdog.pid
590
[root@RV1126_RV1109:/]#
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.4 守护脚本进程模型
在守护脚本场景中:
# qt_app_daemon.sh 运行时:
守护脚本进程 (PID: 2000)
├─ 循环监控逻辑
└─ 启动的Qt应用 (PID: 2001) # 通过 nohup 或 & 启动的子进程
2
3
4
守护进程自身:通过 while true保持常驻
目标应用进程:由脚本进程创建的子进程(通过 pgrep可监控)
# 2.5 与应用进程区别
| 特性 | Shell 脚本进程 | 编译型程序(如C++) |
|---|---|---|
| 执行方式 | 由解释器(bash/sh)动态解释执行 | 直接执行机器码 |
| 内存占用 | 更高(需加载解释器) | 更低 |
| 启动速度 | 较慢(需解析脚本) | 较快 |
| 进程表现 | 进程名为解释器(如 bash) | 进程名为程序本身 |
| 调试支持 | set -x 跟踪执行 | 需要GDB等工具 |
# 03.脚本进程优缺点
# 3.1 脚本进程优势
- 简单即稳定:脚本逻辑通常很简单——“检查进程是否存在,不存在则启动”。没有复杂的内部状态,这意味着它自身崩溃的概率极低。
- 资源占用极低:脚本本身通常只占用了极少的CPU和内存资源,它的大部分时间都在
sleep,这意味着它极不容易因资源耗尽而被系统杀死。 - 依赖少:核心命令如
pgrep、nohup、&都是Unix/Linux系统中最基础、最稳定的工具。
# 3.2 脚本进程劣势
- 缺乏专业进程管理功能:应对粗暴杀死:如果Qt应用被
kill -9杀死,脚本可以重启它。但如果脚本本身被kill -9,那么整个守护机制就彻底失效了。 - 依赖外部环境(非常关键):文件路径和权限:脚本中写的绝对路径可能因系统部署不同而失效;没有写入日志目录或PID文件目录的权限会导致脚本报错退出。
- 错误处理薄弱:脚本默认会忽略它启动的进程的错误。如果Qt应用因为缺少库、配置文件错误等无法启动,脚本只会默默地不断尝试,并在日志中留下大量错误记录,但无法自主通知运维人员。
目前实际脚本,对厂商设备,脚本进程,应用进程分析
- 针对1,守护进程每次设备重启,会首先执行脚本进程,然后脚本进程会开启刷掌应用进程。且脚本进程不会被无端杀死。
- 针对2,对于部署环境,日志路径,定在在刷掌应用的路径下,已经有读写权限。
- 针对3,刷掌应用暂时多方位测试,不会报出崩溃,重启,然后在崩溃操作。且在脚本中,连续3次则会判断为异常,防止无限重启循环,识别系统性问题。
# 04.脚本守护实践
目标:创建一个守护脚本,用于监控并重启一个Qt应用(yt-palm),同时具备日志管理、防止重复启动、异常重启检测等功能。
- 基础框架:无限循环重启应用,无额外功能。
- 添加日志记录:记录启动、退出和重启事件。
- 防止多个守护脚本实例:通过PID文件实现单例。
- 日志清理:定期清理旧日志(保留7天)。
- 异常重启检测:60秒内重启3次则判定异常。
- 优雅终止应用:先尝试普通kill,再强制kill。
- 特殊退出码处理:当应用返回255时,守护脚本退出。
# 4.1 简单守护循环
基础框架 - 最简单的守护循环。最基本的守护循环结构,要满足应用启动和退出码检查,重启延迟控制。
#!/bin/sh
# 基础配置
APP_NAME="yt-palm"
BASE_DIR="/userdata/yt-palm"
RESTART_DELAY=5
# 核心守护循环
while true; do
# 启动应用
if [ -f "${BASE_DIR}/${APP_NAME}" ]; then
# 赋予执行权限
chmod +x "${BASE_DIR}/${APP_NAME}"
# 执行应用
"${BASE_DIR}/${APP_NAME}" -platform linuxfb
EXIT_CODE=$?
else
EXIT_CODE=127 # 文件不存在
fi
# 检查退出码
if [ "$EXIT_CODE" -eq 255 ]; then
break # 正常退出,停止守护
fi
# 等待后重启
sleep $RESTART_DELAY
done
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 4.2 添加日志系统
创建日志目录和文件:实现日志函数统一记录,然后在一些关键的节点,添加日志记录。
#!/bin/sh
# ...保留基础配置...
# 新增日志配置
LOGFILE="/userdata/yt-palm/log/palmapp_watchdog.log"
mkdir -p $(dirname "$LOGFILE")
# 日志函数
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [watchdog] $1" >> "$LOGFILE"
}
# 核心守护循环
while true; do
log "Launching $APP_NAME..."
if [ -f "${BASE_DIR}/${APP_NAME}" ]; then
chmod +x "${BASE_DIR}/${APP_NAME}"
"${BASE_DIR}/${APP_NAME}" -platform linuxfb
EXIT_CODE=$?
log "$APP_NAME exited with code $EXIT_CODE"
else
log "Application not found"
EXIT_CODE=127
fi
if [ "$EXIT_CODE" -eq 255 ]; then
log "Normal exit detected, stopping watchdog."
break
fi
log "Restarting in $RESTART_DELAY seconds..."
sleep $RESTART_DELAY
done
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 4.3 防止多实例运行
PID文件机制原理:检查并终止旧实例,当前进程ID记录,退出时资源清理。
#!/bin/sh
# ...保留已有配置...
# 新增PID文件配置
WATCHDOG_PID="/tmp/palmapp_watchdog.pid"
# 检查并清理旧进程
if [ -f "$WATCHDOG_PID" ]; then
OLD_PID=$(cat "$WATCHDOG_PID" 2>/dev/null)
if [ -n "$OLD_PID" ]; then
kill -9 "$OLD_PID" 2>/dev/null
log "Killed previous watchdog process: $OLD_PID"
fi
rm -f "$WATCHDOG_PID"
fi
# 记录当前PID
echo $$ > "$WATCHDOG_PID"
log "Started watchdog with PID: $$"
# ...保留守护循环...
# 脚本退出时清理PID文件
rm -f "$WATCHDOG_PID"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4.4 环境变量支持
export命令的正确使用:库路径(LD_LIBRARY_PATH)设置,图形环境变量配置。
#!/bin/sh
# ...保留已有配置...
# 新增环境变量设置
export LD_LIBRARY_PATH="${BASE_DIR}/libs:$LD_LIBRARY_PATH"
# ...保留守护循环...
2
3
4
5
6
7
8
# 4.5 优雅终止应用
pidof命令获取进程ID:kill -0检查进程是否存在,优雅终止的重要性。
#!/bin/sh
# ...保留已有配置...
# 在守护循环内修改应用启动部分
if [ -f "${BASE_DIR}/${APP_NAME}" ]; then
# 先尝试终止可能存在的旧实例
PID=$(pidof ${APP_NAME})
if [ -n "$PID" ]; then
# 先发送普通终止信号
kill $PID
# 等待2秒
sleep 2
# 检查是否仍然运行
if kill -0 $PID 2>/dev/null; then
# 强制终止
kill -9 $PID
fi
fi
chmod +x "${BASE_DIR}/${APP_NAME}"
"${BASE_DIR}/${APP_NAME}" -platform linuxfb
EXIT_CODE=$?
else
# ...保持不变...
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4.6 异常重启检测
异常模式检测算法:文件操作技巧(tail/mv),临时文件的使用。
#!/bin/sh
# ...保留已有配置...
# 新增重启记录配置
RESTART_LOG="/tmp/palmapp_restart_times.log"
TMP_RESTART_LOG="/tmp/tmp_restart.log"
# 在守护循环开始处添加
while true; do
# 检查是否在60秒内重启3次
if [ $(wc -l < "$RESTART_LOG") -ge 3 ] && [ $DIFF -lt 60 ]; then
log "Rebooted 3 times within 60 seconds. Abnormal restart detected."
# 这里可以添加警报或特殊处理
fi
# ...原有应用启动逻辑...
done
# 脚本退出时清理
rm -f "$RESTART_LOG"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4.7 日志轮换与清理
日志轮换的重要性:awk高级文本处理,主要是为了防止日志文件太多了,只保留最近7天的日志信息。
#!/bin/sh
# ...保留已有配置...
# 新增日志清理配置
TMPLOG="/tmp/tmp_watchdog_log"
# 在脚本开始处添加日志清理
if [ -f "$LOGFILE" ]; then
# 保留最近7天的日志记录
TODAY=$(date +%s)
SEVEN_DAYS_AGO=$((TODAY - 7 * 24 * 60 * 60))
# 使用awk解析日志时间戳
awk -v limit=$SEVEN_DAYS_AGO '
{
if (NF == 0) next; # 跳过空行
# 提取日志时间部分
timestamp = $1 " " $2 " " $3 " " $4 " " $6;
# 转换为时间戳
cmd = "date -d \"" timestamp "\" +%s";
cmd | getline t;
close(cmd);
# 保留7天内日志
if (t >= limit) print $0;
}' "$LOGFILE" > "$TMPLOG" && mv "$TMPLOG" "$LOGFILE"
log "Cleaned logs older than 7 days"
fi
# ...后续原有代码...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 4.8 完整整合脚本
#!/bin/sh
LOGFILE=/userdata/yt-palm/log/palmapp_watchdog.log
RESTART_DELAY=5
APP_NAME=yt-palm
SHELL_NAME=watchdog_palmapp.sh
BASE_DIR=/userdata/yt-palm
TMPLOG="/tmp/tmp_watchdog_log"
RESTART_LOG="/tmp/palmapp_restart_times.log"
TMP_RESTART_LOG="/tmp/tmp_restart.log"
WATCHDOG_PID="/tmp/palmapp_watchdog.pid"
# 在脚本开始时设置一次环境变量
export LD_LIBRARY_PATH=${BASE_DIR}/libs:$LD_LIBRARY_PATH
# 检查并清理已存在的watchdog进程(通过PID文件)
if [ -f "$WATCHDOG_PID" ]; then
OLD_PID=$(cat "$WATCHDOG_PID" 2>/dev/null)
if [ -n "$OLD_PID" ]; then
kill -9 "$OLD_PID" 2>/dev/null
echo "$(date) [watchdog] Kill shell app old process: $OLD_PID" >> "$LOGFILE"
fi
rm -f "$WATCHDOG_PID"
fi
# 记录当前watchdog进程ID
echo $$ > "$WATCHDOG_PID"
# 在记录PID到文件后,会同时记录到日志中。这样可以方便追踪watchdog进程的启动情况。
echo "$(date) [watchdog] Started shell app with PID: $$" >> "$LOGFILE"
mkdir -p ${BASE_DIR}/log
# 清理日志,保留最近7天的记录
if [ -f "$LOGFILE" ]; then
# 保留最近7天的日志记录
TODAY=$(date +%s)
SEVEN_DAYS_AGO=$((TODAY - 7 * 24 * 60 * 60))
echo "$(date) [watchdog] Filter log older than: $(date -d @$SEVEN_DAYS_AGO)"
# 使用awk脚本解析时间戳进行过滤
awk -v limit=$SEVEN_DAYS_AGO '
{
if (NF == 0) next; # 空行直接丢弃
timestamp = $1 " " $2 " " $3 " " $4 " " $6;
cmd = "date -d \"" timestamp "\" +%s";
cmd | getline t;
close(cmd);
if (t >= limit) print $0;
}' "$LOGFILE" > "$TMPLOG" && mv "$TMPLOG" "$LOGFILE";
fi
# 核心逻辑
while true; do
# 60S连续启动3次,判定为异常
NOW=$(date +%s)
echo $NOW >> "$RESTART_LOG"
tail -n 3 "$RESTART_LOG" > "$TMP_RESTART_LOG"
mv "$TMP_RESTART_LOG" "$RESTART_LOG"
FIRST=$(head -n 1 "$RESTART_LOG")
[ -z "$FIRST" ] && FIRST=$NOW
DIFF=$((NOW - FIRST))
# 60秒内连续启动3次判定为异常
if [ $(wc -l < "$RESTART_LOG") -ge 3 ] && [ $DIFF -lt 60 ]; then
echo "$(date) [watchdog] Rebooted 3 times within 60 seconds. Abnormal restart detected." >> "$LOGFILE"
fi
echo "$(date) [watchdog] Launching palmapp..." >> "$LOGFILE"
# 应用启动逻辑
if [ -f ${BASE_DIR}/${APP_NAME} ]; then
# kill -9过于暴力,可能导致数据丢失。如果pidof返回空值,会导致kill -9报错
# kill -9 $(pidof ${APP_NAME})
PID=$(pidof ${APP_NAME})
if [ -n "$PID" ]; then
kill $PID
echo "$(date) [watchdog] Kill Palm app process $PID" >> "$LOGFILE"
sleep 2
if kill -0 $PID 2>/dev/null; then
kill -9 $PID
echo "$(date) [watchdog] Kill Palm app process $PID -9" >> "$LOGFILE"
fi
fi
# 给权限
chmod +x ${BASE_DIR}/${APP_NAME}
echo "$(date) [watchdog] palmapp start, is launched" >> "$LOGFILE"
# 执行应用
${BASE_DIR}/${APP_NAME} -platform linuxfb
EXIT_CODE=$?
else
echo "$(date) [watchdog] Palm app not found" >> "$LOGFILE"
# 标准的"命令未找到"错误码
EXIT_CODE=127
fi
echo "$(date) [watchdog] Palm app exited with code $EXIT_CODE" >> "$LOGFILE"
# 正常退出(退出码255)时停止watchdog。那么在退出应用,则需要使用255这个code码
# 异常退出(退出码非255)时重启watchdog
if [ "$EXIT_CODE" -eq 255 ]; then
echo "$(date) [watchdog] Normal exit detected, stopping watchdog." >> "$LOGFILE"
break
fi
echo "$(date) [watchdog] Abnormal exit detected, restarting in $RESTART_DELAY seconds..." >> "$LOGFILE"
sleep $RESTART_DELAY
done
rm -f "$WATCHDOG_PID"
rm -f "$RESTART_LOG"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# 4.9 设计模式应用
- 单例模式 - PID文件确保唯一实例
- 观察者模式 - 监控应用状态变化
- 策略模式 - 根据退出码选择不同处理策略
- 模板方法模式 - 标准化的启动-监控-重启流程
# 05.稳定性的设计
# 5.1 异常重启检测
设计思想: 防止无限重启循环,识别系统性问题。
# 60秒内连续启动3次判定为异常
if [ $(wc -l < "$RESTART_LOG") -ge 3 ] && [ $DIFF -lt 60 ]; then
echo "$(date) [watchdog] Rebooted 3 times within 60 seconds. Abnormal restart detected."
fi
2
3
4
# 5.2 退出码语义化
设计思想: 通过退出码区分正常关闭和异常崩溃。
if [ "$EXIT_CODE" -eq 255 ]; then
# 正常退出,停止watchdog
break
else
# 异常退出,继续重启
sleep $RESTART_DELAY
fi
2
3
4
5
6
7
# 5.3 日志和监控体系
记录启动、重启、异常事件。一定要注意:日志轮转防止磁盘占满。
结构化日志:格式: 时间戳 [组件] 操作描述
echo "$(date) [watchdog] Started shell app with PID: $$" >> "$LOGFILE"
日志轮转,设计思想: 防止日志文件无限增长,保持系统性能。
# 保留最近7天的日志记录
awk -v limit=$SEVEN_DAYS_AGO '...' "$LOGFILE" > "$TMPLOG"
2