开发过程中有时会遇到需要在程序中执行三方程序或者shell脚本,一般会通过system(), popen(), exec簇来完成该功能。我们知道以上方法会通过fork创建子进程后在子进程中执行相应指令。如图1为某个示例流程,具体的程序执行流程如图2所示,线程mypthreadFunction1首先open了一个设备文件PCMDEV,线程mypthreadFunction2中通过fork方法创建了一个子进程并通过execl执行helloword打印程序,线程mypthreadFunction1中随后会去反复开关PCMDEV设备,根据图2中右所示我们看到父线程在open设备文件PCMDEV时一直返回设备忙被占用,但是创建的子进程helloword中并未对PCMDEV设备做任何的读写开关操作.
图1 示例流程
图2 执行流程
父进程打开设备文件PCMDEV时返回设备被占用时,我们使用lsof命令确认此时占用PCMDEV设备的进程,如图3所示,PCMDEV设备居然正是被我们fork创建的子进程和helloword进程占用才导致父进程open设备文件PCMDEV一直失败。
图3 占用PCMDEV设备的进程
参考fork函数的用户手册,fork在创建子进程时会继承父进程打开的文件描述符,system(), popen()也会存在相同的动作。
The child inherits copies of the parent’s set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two file descriptors share open file status flags, file offset, and signal-driven I/O attributes (see the description of F_SETOWN and F_SETSIG in fcntl(2)).
为了让创建的子进程释放掉从父进程中继承的文件描述符,可以在fork之后execl之间使用如下代码主动关闭子进程下的文件描述符,
static void release_fileDesp(void)
{
pid_t pid = getpid();
char fileDespPath[128] = { 0 };
DIR *fdDir = NULL;
int ret = -1;
int fd = 0;
struct dirent *entry = NULL;
snprintf((char *)fileDespPath, sizeof(fileDespPath) - 1, "/proc/%d/fd", pid);
fdDir = opendir((const char *)fileDespPath);
if (NULL == fdDir)
{
return;
}
while (1)
{
entry = readdir(fdDir);
if (NULL == entry)
{
break;
}
if ((0 == (strcmp(entry->d_name, "."))) || (0 == strcmp(entry->d_name, "..")))
{
continue;
}
fd = atoi(entry->d_name);
if ((0 == fd) || (1 == fd) || (2 == fd)) // 标准输入,标准输出,标准错误
{
continue;
}
ret = close(fd);
if (ret != 0)
{
break;
}
}
closedir(fdDir);
return;
}