1 现象:问题描述
海外某局点升级某SCP的bill外挂程序后,产生很多僵尸进程。
2 关键过程:根本原因分析
将从现场收集到的数据反映,僵尸进程的父进程都为同一个后台常驻进程,且该进程由升级前的一次运行进程改为了后台常驻进程。但是开发者反映,这只是一个单进程程序,没有生成任何子进程。程序中没有任何fork的调用。通过紧张的分析,检查代码、分析程序中所有的系统调用,最后问题定位到程序检测磁盘占用的函数中:
bool CheckResource()
{
…
#ifdef _LINUX_UNIX
sprintf(command, "df -k %s | grep %s | awk '{ print $4 }'", pData, pData);
#endif
g_Log.WriteLog(LOG_DEBUG, "command:%s", command);
FILE * fp = NULL;
fp = popen(command, "r");
if (NULL == fp)
{
g_Log.WriteLog(LOG_ERROR, "CheckResource function popen command %s error", command);
return false;
}
//读取标题行
if (NULL == fgets(line, sizeof(line), fp))
{
fclose(fp);
g_Log.WriteLog(LOG_ERROR, "CheckResource function fgets command %s result error", command);
return false;
}
fclose(fp);
…
}
我们可以看到,fp是通过popen调用生成的FILE指针,开发人员想当然的使用了fclose关闭fp文件描述指针。通过验证以及咨询HP工程师,由于popen会通过生成子进程执行sh命令,因此需要通过pclose来执行类似waitpid的功能,使得子进程可以完全退出而不会留下僵尸进程。
该程序升级前后调用此函数时都产生了僵尸,但升级前的版本由于执行一次就退出了没有被发现。而升级后为后台常驻进程,每天执行多次该函数,因此才被发现。
3 结论:解决方案及效果
将相应的fclose调用该为pclose。
if (NULL == fgets(line, sizeof(line), fp))
{
pclose(fp);
g_Log.WriteLog(LOG_ERROR, "CheckResource function fgets command %s result error", command);
return false;
}
pclose(fp);
4 经验总结:预防措施和规范建议
对于FILE* 的文件指针不能想当然的使用fclose来关闭,需要看此指针是如何打开的。注意fopen/fclose以及 popen/pclose的配对使用。对于我们程序中使用的系统调用都不可大意,需要明确了解其功能和使用方法。
5 备注
6 考核点
popen与pclose必须配对使用。
7 试题
1、 关于使用调用popen说法正确的是:C
A) 使用popen后得到的FILE * 文件指针有系统负责释放资源,我们没有必要关注。
B) 使用popen后得到的FILE *文件指针可以使用fclose来释放资源,因为fclose就是用来关闭FILE* 文件指针的。
C) popen后得到的FILE *文件指针必须使用pclose来关闭,而不能使用fclose。
D) popen后得到的FILE *文件指针可以直接使用 delete 释放
阅读(360) | 评论(0) | 转发(0) |