问题:项目开发中,碰到一个很奇怪的问题:当tcsh启动的子程序退出之后,tcsh本身无法退出,并占用大量CPU资源。
背景:应用程序在fork之后,用tcsh启动另一个子进程,执行特定任务。进程之间使用sockepair(pipe)进行进程间通讯;为简化编程,将子进程的socket fd重定向为stdin和stdout。
具体症状:
Strace tcsh程序:
tcsh不断打印上述系统调用,可判断tcsh陷入了死循环,无法退出。其中
write(17, "[1] 30274\n", 10) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
lseek(16, 0, SEEK_END) = -1 ESPIPE (Illegal seek)
初步这两个系统调用是引发死循环的关键。为什么write到17文件描述符。(如果有经验,估计在这一步就可知道问题的根源。)
Lsof tcsh进程:
tcsh没有0,1,2的文件描述符,只有3个指向同一个unix地址的socket地址,基本断定: 16为stdin,17为stdout,18为stderr,19未知。
问题就变成:tcsh不断尝试往stdout输入特定信息![1] 30274这个信息,应该是tcsh所起子进程的PID。
这样就基本清楚了:tcsh收到子进程的状态信息([1] PID),并将收到的信息输出。由于stdout已被重定向成unix socket,在输出时,发生SIGPIPE (Broken pipe)事件。 SIGPIPE的默认行为是终止程序,显然tcsh没终止,并不断尝试输出。为什么tcsh不终止?难道tcsh内部实现屏蔽了该信号?为了解决这个问题,有两个办法:看tcsh的源码;看是否自己的程序改变了tcsh的信号处理。
最终,从自己的程序入手,发现果然是自己的程序改变了tcsh的信号配置。
A child created via fork(2) inherits a copy of its parent's signal dispositions. During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged.
问题的结论:
1) 自己的程序忽略了SIGPIPE;
2) fork之后,重定向了stdin和stdout;
3) tcsh启动子程序,捕获到子进程的状态(后台启动返回PID信息),并尝试输出到stdout(socket),因socket已被关闭,写失败并导致SIGPIPE。因SIGPIPE已被忽略,导致tcsh不断重试,无法退出!
重现程序:
gcc -o pipe pipe_broke.c
./pipe /usr/bin/strace -e trace=write,lseek -o strace.log csh -c "sleep 10 & "
Note: bash has no this problem! csh -> tcsh
本文转自 zhenjing 博客园博客,原文链接:http://www.cnblogs.com/zhenjing/archive/2011/05/26/tcsh_dead_loop.html,如需转载请自行联系原作者