简体   繁体   English

为什么在fork之后关闭文件描述符会影响子进程?

[英]Why does closing file descriptors after fork affect the child process?

I want to run programs in linux by a button click an therefore I wrote a function execute : 我想通过按钮单击一个在linux中运行程序,因此我编写了一个函数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;
        }
    }
}

There are two problems: 有两个问题:

  1. Closing the file descriptors causes some problems, for example Thunderbird crashes or VLC runs without sound. 关闭文件描述符会导致一些问题,例如Thunderbird崩溃或VLC运行时没有声音。 More exactly: closing of stdout(1) and stderr(2) causes these problems. 更确切地说:关闭stdout(1)stderr(2)会导致这些问题。 As I understand, closing file descriptor before exec only prevents them from been duplicated (there is no need to send informations from child process to parent process). 据我所知,在exec之前关闭文件描述符只能防止它们被复制(不需要从子进程向父进程发送信息)。 Why does this affect the child process? 为什么这会影响子进程? Replacing close() by setting the flag FD_CLOEXEC doesn't change anything. 通过设置标志FD_CLOEXEC替换close()不会改变任何东西。 Also setting the FD_CLOEXEC flag before fork doesn't solve the problem. 在fork之前设置FD_CLOEXEC标志也无法解决问题。 Is there a better way to prevent inheritance of file descriptors? 有没有更好的方法来阻止文件描述符的继承?

  2. The return value of waitpid is often 0, even if the program call fails, I think because there are two (asynchrone) processes. waitpid的返回值通常为0,即使程序调用失败,我认为因为有两个(异步)进程。 usleep(50) solves this problem for my needs, but I hope there are better solutions for this problem. usleep(50)为我的需求解决了这个问题,但我希望有更好的解决方案来解决这个问题。

I'm using vfork, but the same problems occur by using fork. 我正在使用vfork,但使用fork会出现同样的问题。

First, in 2014, never use vfork but simply fork(2) . 首先,在2014年,永远不要使用vfork而只需使用fork(2) (Since vfork(2) is obsolete since POSIX 2001 and removed in POSIX 2008). (因为vfork(2)自POSIX 2001以来已经过时并在POSIX 2008中被删除)。

Then, the simplest way to close most of file descriptors is just 然后,关闭大多数文件描述符的最简单方法就是

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

(hint: if a fd is invalid, close(fd) would fail and we ignore the failure; and you start from 3 to keep open 0== stdin , 1== stdout , 2== stderr ; so in principle all the close above would fail). (提示:如果fd无效, close(fd)将失败并且我们忽略失败;并且你从3开始保持打开0 == stdin ,1 == stdout ,2 == stderr ;所以原则上所有close以上会失败)。

However, well behaved and well-written programs should not need such a loop on closing (so it is a crude way to overcome previous bugs). 然而,表现良好且编写良好的程序在关闭时不应该需要这样的循环(因此它是克服先前错误的粗略方法)。

Of course, if you know that some file descriptor other than stdin, stdout, stderr is valid and needed to the child program_call (which is unlikely) you'll need to explicitly skip it. 当然,如果你知道除了stdin,stdout,stderr之外的某些文件描述符是有效的并且需要子program_call (这不太可能),你需要显式跳过它。

and then use FD_CLOEXEC as much as possible. 然后尽可能使用FD_CLOEXEC

It is unlikely that your program would have a lot of file descriptors without you knowing them. 如果你不了解它们,你的程序就不太可能有很多文件描述符。

Maybe you want daemon(3) or (as commented by vality ) posix_spawn . 也许你想要守护进程(3)或(如vality评论) posix_spawn

If you need to explicitly close STDIN_FILENO (ie 0), or STDOUT_FILENO (ie 1), or STDERR_FILENO (ie 2) you'll better open("/dev/null", ... and dup2 them after - before calling exec , because most programs expect them to exist. 如果需要显式关闭STDIN_FILENO (即0)或STDOUT_FILENO (即1)或STDERR_FILENO (即2),最好在调用exec之前open("/dev/null", ...和dup2之后 -因为大多数程序都希望它们存在。

First problem: There is no way to prevent inheritance of file descriptors except you close them yourself or set FD_CLOEXEC , check this 第一个问题:没有办法阻止文件描述符的继承,除非你自己关闭它们或设置FD_CLOEXEC ,检查这个

Second problem: You got The return value of waitpid is often 0 , because you sepecfied WNOHANG in waitpid . 第二个问题:你得到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.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM