Shell中后台运行I/O等待程序并获取PID的阻塞问题排查
问题原因与解决方法
核心问题分析
$(func)阻塞的原因
当使用MYVAR=$(func)时,函数会在子shell中执行。你后台启动的进程(不管是read还是向串口输出的echo)会继承子shell的标准输入、输出、错误描述符。而$(...)机制会等待子shell的所有输出完成,由于后台进程仍持有子shell的管道相关文件描述符(比如stdout),管道的写端不会完全关闭,导致$(...)一直阻塞,直到后台进程终止。return返回PID被截断的原因
Shell的return命令仅支持返回0-255之间的整数,超出范围的PID会被自动取模256,因此必然会被截断为8位,无法正常传递完整PID。
解决方案
启动后台进程时,强制切断它与子shell的文件描述符关联,将其stdin、stdout、stderr全部重定向到/dev/null,这样子shell可以正常退出,$(...)就能顺利获取PID,后台进程也能独立运行。
修改后的函数示例
针对read等待场景:
func() { # 启动后台read,切断所有与子shell的IO关联 read < /dev/ttyUSB3 <&- >/dev/null 2>&1 & PID=$! echo "$PID" }
或者用nohup让进程彻底脱离终端(更稳定):
func() { nohup read < /dev/ttyUSB3 >/dev/null 2>&1 & PID=$! echo "$PID" }
针对实际串口输出场景:
func() { echo "something" > /dev/ttyUSB3 >/dev/null 2>&1 <&- & PID=$! echo "$PID" }
或nohup版本:
func() { nohup echo "something" > /dev/ttyUSB3 >/dev/null 2>&1 & PID=$! echo "$PID" }
重定向说明
>/dev/null 2>&1:将标准输出和错误全部丢弃,避免后台进程的输出干扰,同时切断与子shell stdout的管道关联。<&-:关闭标准输入,防止后台进程继承子shell的stdin(对于read场景,已经指定了输入源,关闭stdin更安全)。nohup:让进程脱离当前终端会话,即使父shell退出,后台进程也能继续运行,默认会将输出写入nohup.out,我们这里手动重定向到/dev/null避免生成该文件。
内容的提问来源于stack exchange,提问作者Anonymous
相关产品推荐
相关产品推荐

