Files
orisys_arm/scripts/lib/real_procs.sh
T
2026-10-16 17:27:30 +08:00

140 lines
6.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =============================================================================
# scripts/lib/real_procs.sh —— 真机进程扫描(被 stop_real.sh / run_real.sh 共用)
#
# 为什么要有这个文件
# ----------------
# 仿真侧早就有 lib/sim_procs.sh(点名表 + install_sim/ 兜底 + 杀完复查),
# 真机侧**一直没有**对应物:只有 run_real.sh 里两行 pkill,
# pkill -f '/controller_manager/ros2_control_node'
# pkill -f 'arm_bringup.*stack.launch'
# 而真机栈里还有一族**独立于这两条存活**的进程。2026-09-28 实测抓到的一次:
# 上一轮栈的主体早就没了,只剩
# real_trajectory_adapter / named_pose_server / arm_status_node /
# joint_state_publisher
# 四条(全在 install/arm_control/lib/arm_control/ 下),两条 pkill 一条都匹配不到。
#
# 危害不是"占内存",而是**冒名顶替**:
# · 残留的 adapter 照样订阅 /trajectory、
# · 残留的 named_pose_server 照样提供 /set_named_pose、
# · 残留的 joint_state_publisher 照样发 /joint_states(mock 值的**静态**关节角),
# 于是新一轮的请求被 DDS 负载均衡到"残留的那个",/joint_states 与 /tf 有两份
# 来源互相盖。现象极具误导性:控制器 feedback 与 reference 完全一致(控制器没坏),
# 但 TF 显示末端**一动不动** —— 顺着现象查会一路查到 IK / TF / 时钟上。
#
# 所以这里做三件事:
# 1. run_real.sh 与 stop_real.sh 共用同一份扫描实现(不再各写一份会漂移的表);
# 2. 扫描 = 点名表 + `install/` 前缀兜底 —— 凡命令行含 ``<仓库>/install/``
# 的进程都算真机进程,以后再加新节点,不改这个文件也能被扫到;
# 3. **按 ROS_DOMAIN_ID 区分真机与仿真**:仿真强制 77(见 sim_env.sh),
# 真机默认 0。只按名字扫的话,停真机会顺手把正在跑的仿真的
# robot_state_publisher 一起杀掉(两边同名、都在 conda/ROS 下)。
#
# 用法(在 `set -uo pipefail` 之后 source):
# source "$(dirname "$0")/lib/real_procs.sh"
# mapfile -t targets < <(real_proc_targets) # 只输出 PID(已排除自身/祖先/仿真)
# real_proc_scan # "PID<TAB>域<TAB>命令行"
# =============================================================================
# 仿真的域(sim_env.sh 强制那个值)。真机扫到它 = 那是仿真进程,不是残留。
REAL_SIM_DOMAIN=77
_real_repo_root() {
cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd
}
# 兜底模式:真机安装空间。用绝对路径前缀,避免误伤 install_sim/(仿真那边)。
# 注意 "install_sim/" **不含** 子串 "install/"(_sim 把它截断了),所以这一条
# 天然不会扫到仿真进程。
#
# ⚠️ 路径要按 awk 正则转义:路径里可能有 . + - 之类字符,不转义的话
# "." 会匹配任意字符(匹配面变大 = 误杀无关进程)。
_real_install_dir() {
printf '%s/install/' "$(_real_repo_root)"
}
_real_regex_escape() {
printf '%s' "$1" | sed -e 's/[][\.^$*+?(){}|\\]/\\&/g'
}
# 点名表:列**不在 install/ 下**、但属于真机栈的进程。
# 它们的可执行文件来自 conda 环境或 ROS 发行版(/opt/ros/humble/lib/...),
# 命令行里没有仓库的 install/ 字样,兜底规则扫不到。
#
# ros2_control_node 控制器本体(它占着 RDK,RDK 对同一台臂是独占的)
# controller_manager.*spawner 孤儿 spawner,会永远等一个不存在的 controller_manager
# joint_state_publisher **mock 专用**节点;在真机上出现 = 上一轮 mock 栈的残留,
# 它发的静态关节角会盖掉真实值
# robot_state_publisher 发 TF 与 /robot_description;残留会让"改了描述却不生效"
# static_transform_publisher 发 latched 的 /tf_static,残留会一直把旧外参按在新树上
REAL_PROC_NAMED='ros2_control_node|controller_manager.*spawner|joint_state_publisher|robot_state_publisher|static_transform_publisher'
real_proc_patterns() {
printf '%s|%s' "$REAL_PROC_NAMED" "$(_real_regex_escape "$(_real_install_dir)")"
}
# 读某个进程的 ROS_DOMAIN_ID。读不到(僵尸/无权限/没设)输出空。
# `|| true`:调用方可能是 `set -e` 的脚本(run_real.sh),而 tr/sed 在
# /proc 读不到时会非零返回 —— 不兜住的话整个启动脚本会被这一行带停。
real_proc_domain() {
local pid="$1" v=""
v="$(tr '\0' '\n' < "/proc/${pid}/environ" 2>/dev/null \
| sed -n 's/^ROS_DOMAIN_ID=//p' | head -1)" || true
printf '%s' "${v:-}"
}
# 是不是仿真进程(域 == REAL_SIM_DOMAIN)。
# 读不到 environ 时**不当**成仿真:宁可停真机时多杀一个"域未知但确实在
# install/ 下"的进程,也不要漏掉真机残留(漏掉的代价见文件头)。
real_proc_is_sim() {
[ "$(real_proc_domain "$1")" = "${REAL_SIM_DOMAIN}" ]
}
# 扫描:输出 "PID<TAB>域<TAB>命令行"。
#
# ⚠️ 两个必须踩过的坑(与 sim_procs.sh 同源,别再犯):
# 1. `pkill -f <模式>` 会匹配到自己所在的 shell(它的命令行里就含那个模式),
# 表现成"命令立刻返回、什么都没杀"。
# 2. 模式串用**环境变量**传给 awk,不用 `-v pat=...`:用了 -v 的话 awk 自己的
# 命令行里就含整串模式,于是它匹配到自己,复查时多报一个幽灵 PID。
real_proc_scan() {
export PAT="$(real_proc_patterns)"
export ME="${REAL_PROC_SELF:-$$}"
ps -eo pid=,ppid=,args= | awk '
BEGIN { pat = ENVIRON["PAT"]; me = ENVIRON["ME"] }
$0 ~ pat {
if (NF < 3) next # 没有 args 字段(僵尸/内核线程)
pid = $1; ppid = $2
args = substr($0, index($0, $3))
if (ppid == me) next # 本脚本的直接子进程(如 $(...) 的 fork)
if (args ~ /real_procs\.sh/) next # 本库自己(含 grep/sed 的读数)
if (args ~ /stop_real\.sh/) next
if (args ~ /run_real\.sh/) next
if (args ~ /^\s*awk/) next
print pid "\t" args
}'
}
# 排除自己与全部祖先,跳过仿真进程,只输出 PID。
# 设 REAL_PROC_INCLUDE_SIM=1 可以连仿真进程一起列出(自查用,停止流程不用)。
real_proc_targets() {
local exclude=() p=$$ pid rest _d skip e
while [ "${p:-0}" -gt 1 ] 2>/dev/null; do
exclude+=("$p")
p=$(ps -o ppid= -p "$p" 2>/dev/null | tr -d ' ')
[ -z "$p" ] && break
done
while IFS=$'\t' read -r pid rest; do
[ -z "${pid:-}" ] && continue
skip=0
for e in "${exclude[@]}"; do
[ "$pid" = "$e" ] && skip=1 && break
done
[ "$skip" = "1" ] && continue
if [ "${REAL_PROC_INCLUDE_SIM:-0}" != "1" ] && real_proc_is_sim "$pid"; then
continue
fi
echo "$pid"
done < <(real_proc_scan)
}