为什么在fork之后closures文件描述符会影响subprocess?

我想在linux下运行程序,按一下button,因此我写了一个函数execute

 void execute(const char* program_call, const char* param ) { pid_t child = vfork(); if(child == 0) // child process { int child_pid = getpid(); char *args[2]; // arguments for exec args[0] = (char*)program_call; // first argument is program_call args[1] = (char*)param; // close all opened file descriptors: const char* prefix = "/proc/"; const char* suffix = "/fd/"; char child_proc_dir[16]; sprintf(child_proc_dir,"%s%d%s",prefix,child_pid, suffix); DIR *dir; struct dirent *ent; if ((dir = opendir (child_proc_dir)) != NULL) { // get files and directories within directory while ((ent = readdir (dir)) != NULL) { // convert file name to int char* end; int fd = strtol(ent->d_name, &end, 32); if (!*end) // valid file descriptor { close(fd); // close file descriptor // or set the flag FD_CLOEXEC //fcntl( fd, F_SETFD, FD_CLOEXEC ); } } closedir (dir); } else { cerr<< "can not open directory: " << child_proc_dir <<endl; } // replace the child process with exec*-function execv(program_call,args); _exit(2); } else if (child == -1) // fork error { if (errno == EAGAIN) { cerr<<“To much processes"<<endl; } else if (errno == ENOMEM) { cerr<<“Not enough space available."<<endl; } } else // parent process { usleep(50); // give some time if ( errno == EACCES) { cerr<<“Permission denied or process file not executable."<<endl; } else if ( errno == ENOENT) { cerr<<"\n Invalid path or file."<<endl; } int child_status; if ( waitpid(child, &child_status, WNOHANG | WUNTRACED) < 0) // waitpid failed { cerr<<"Error - Execution failed"<<endl; } else if ( WIFEXITED( child_status ) && WEXITSTATUS( child_status ) != 0) { cerr<<“Child process error - Execution failed"<<endl; } } } 

有两个问题:

  1. closures文件描述符会导致一些问题,例如Thunderbird崩溃或VLC无声运行。 更确切地说, stdout(1)stderr(2)closures导致了这些问题。 据我所知,在exec之前closures文件描述符只能防止它们被复制(不需要将信息从subprocess发送到父进程)。 为什么这会影响subprocess? 通过设置标志FD_CLOEXECreplaceclose()不会改变任何东西。 在fork之前也设置FD_CLOEXEC标志不能解决问题。 有没有更好的方法来防止文件描述符的inheritance?

  2. waitpid的返回值通常是0,即使程序调用失败,我认为是因为有两个(asynchronous)进程。 usleep(50)解决了这个问题,但我希望这个问题有更好的解决scheme。

我正在使用vfork,但使用fork也会发生同样的问题。

第一个问题:没有办法阻止文件描述符的继承,除非你自己关闭它们或设置FD_CLOEXEC ,检查这个

第二个问题:你得到The return value of waitpid is often 0 ,因为你在waitpid指定了WNOHANG

 waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. On error, -1 is returned. 

首先,在2014年,从来不使用vfork而是简单地fork(2) 。 (因为vfork(2)自POSIX 2001以来已经过时,并在POSIX 2008中被删除)。

那么,关闭大部分文件描述符的最简单的方法就是

 for (int fd=3; fd<256; fd++) (void) close(fd); 

(提示:如果fd是无效的, close(fd)会失败,我们忽略失败;你从3开始保持打开0 == stdin ,1 == stdout ,2 == stderr ;所以原则上所有close上面会失败)。

然而,表现良好,编写良好的程序在关闭时不应该需要这样的循环(所以这是克服以前的错误的粗略方法)。

当然,如果你知道stdin,stdout,stderr之外的一些文件描述符是有效的,并且需要给子程序program_call (这不太可能),你需要明确地跳过它。

然后尽可能地使用FD_CLOEXEC

如果你不知道它们,你的程序不可能有很多的文件描述符。

也许你想要守护进程(3)或者(作为vality的注释 ) posix_spawn 。

如果你需要明确地关闭STDIN_FILENO (即0)或STDOUT_FILENO (即1)或STDERR_FILENO (即2),最好在调用exec之前open("/dev/null", …和dup2 )因为大部分程序都希望它们存在。