如何避免GDB的SIGINT信号导致OpenOCD调试服务退出?
我完全懂你的困扰——明明直接在shell里后台启动OpenOCD再开GDB一切正常,放到脚本里就会因为按下中断键(触发SIGINT)导致OpenOCD意外退出。核心原因其实是脚本启动的子进程默认和脚本同属一个前台进程组,当你按下Ctrl+C发送SIGINT时,这个信号会被广播给整个进程组的所有进程,包括后台运行的OpenOCD。而直接在shell里运行时,后台进程会被分配到独立的进程组,自然不会收到终端的SIGINT。
下面给你几个可行的解决方案,从简单到彻底,按需选择:
方案1:用setsid让OpenOCD完全脱离当前进程组
setsid可以启动一个全新的会话,让OpenOCD彻底脱离当前终端的进程组,这样终端的任何信号都不会传递给它。修改后的脚本如下:
#! /bin/sh setsid openocd -f openocd-jlink.cfg < /dev/null > openocd.log 2>&1 & OPENOCD_PID=$! arm-none-eabi-gdb -ex "set architecture armv6-m" -ex "target extended-remote localhost:2331" obj/main.elf kill $OPENOCD_PID
这里的> openocd.log 2>&1是把OpenOCD的输出重定向到日志文件,避免它占用终端输出;< /dev/null是让它脱离标准输入,防止意外读取终端输入导致异常。
方案2:用disown让shell放弃对OpenOCD的控制
如果不想用setsid,也可以在启动OpenOCD后用disown把它从shell的作业列表中移除,这样shell就不会把终端信号传递给它:
#! /bin/sh openocd -f openocd-jlink.cfg < /dev/null > openocd.log 2>&1 & OPENOCD_PID=$! disown $OPENOCD_PID arm-none-eabi-gdb -ex "set architecture armv6-m" -ex "target extended-remote localhost:2331" obj/main.elf kill $OPENOCD_PID
不过这个方法依赖shell支持disown(bash、zsh都支持,纯sh环境可能要看具体实现),兼容性相比setsid稍弱一点。
方案3:nohup+setsid双重保障(最稳妥的守护化)
如果想要最彻底的隔离效果,可以结合nohup和setsid,同时重定向所有输入输出:
#! /bin/sh nohup setsid openocd -f openocd-jlink.cfg > openocd.log 2>&1 < /dev/null & OPENOCD_PID=$! arm-none-eabi-gdb -ex "set architecture armv6-m" -ex "target extended-remote localhost:2331" obj/main.elf kill $OPENOCD_PID
nohup会让进程忽略SIGHUP信号(比如终端关闭时触发的信号),加上setsid脱离进程组,能完全切断OpenOCD和当前终端的信号关联。
为什么你的原脚本没起作用?
你原脚本里嵌套了sh -c还加了trap '' SIGINT,但问题在于:脚本本身处于前台进程组,嵌套的子shell虽然捕获了SIGINT,但OpenOCD作为子shell的子进程,依然和脚本同属一个进程组——终端发送的SIGINT是广播给整个进程组的,不是只发给父进程,所以OpenOCD还是会收到信号退出。而直接在shell里运行openocd &时,shell会自动把后台进程放到新的进程组,自然不会被终端的SIGINT波及。
你可以自己验证:在脚本里启动OpenOCD后,用ps axj查看进程组ID(PGID),会发现它和脚本、GDB的PGID完全相同;而直接在shell里后台启动的话,OpenOCD的PGID就是自己的PID,和shell的PGID完全独立。
内容的提问来源于stack exchange,提问作者Samuel Dewan

