Linux嵌入式系统守护进程中使用popen()和pclose()获取Shell命令正确退出码的异常问题
嘿,我一眼就揪出问题根源了——你在守护进程初始化时设置了signal(SIGCHLD, SIG_IGN),这直接打断了pclose()获取子进程退出状态的逻辑。
问题本质
当你把SIGCHLD信号的处理方式设为SIG_IGN时,Linux内核会自动收割所有子进程的退出状态,不会留下僵尸进程。但这个设置有个副作用:popen()创建的Shell子进程退出后,内核直接回收了它的状态信息,pclose()内部调用的waitpid()根本找不到这个子进程的状态记录,所以只能返回-1,对应的错误码是ECHILD(你可以在pclose_wrapper里加一行syslog(LOG_ERR, "pclose failed: %s", strerror(errno))验证,会看到"No child processes"的提示)。
解决方案
最直接且高效的修复方法就是调整SIGCHLD的信号处理:
方案1:恢复默认的SIGCHLD处理
把守护进程初始化代码里的:
signal(SIGCHLD, SIG_IGN);
替换成:
signal(SIGCHLD, SIG_DFL);
这样pclose()就能正常调用waitpid()获取子进程的退出状态了。不用担心僵尸进程的问题——pclose()会负责等待自己创建的子进程,退出时会自动回收状态,不会留下僵尸。
方案2:如果必须保留SIGCHLD为SIG_IGN(不推荐)
如果你的业务逻辑必须忽略SIGCHLD(比如要避免其他子进程产生僵尸),那你需要手动追踪popen()创建的子进程PID,再用waitpid()获取状态。但这种方法比较麻烦,因为popen()不会直接返回PID,你需要通过管道的文件描述符(用fileno()获取)结合/proc文件系统去查找,远不如方案1简单。
额外优化建议
你当前的代码在守护化之前就调用了openlog(),之后又关闭了所有文件描述符。虽然看起来日志正常工作,但最好把setlogmask()和openlog()移到守护化代码的末尾(也就是关闭所有fd之后),避免syslog的socket被意外关闭。
验证修改效果
修改后重新编译运行,守护模式下pclose()应该会返回正确的退出码了——比如你测试的命令会返回0,和非守护模式表现一致。
内容的提问来源于stack exchange,提问作者N0x

