为什么在fork之后关闭文件描述符会影响子进程? [英] Why does closing file descriptors after fork affect the child process?

查看:648
本文介绍了为什么在fork之后关闭文件描述符会影响子进程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过单击按钮在Linux中运行程序,因此我编写了一个函数execute:

I want to run programs in linux by a button click an therefore I wrote a function 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. 关闭文件描述符会导致一些问题,例如Thunderbird崩溃或VLC运行无声音.更确切地说:stdout(1)stderr(2)的关闭会导致这些问题.据我了解,在exec之前关闭文件描述符只能防止它们重复(不需要从子进程向父进程发送信息).为什么这会影响子进程?通过设置标志FD_CLOEXEC替换close()不会改变任何内容.在fork之前设置FD_CLOEXEC标志也不能解决问题.有没有更好的方法来防止文件描述符的继承?

  1. Closing the file descriptors causes some problems, for example Thunderbird crashes or VLC runs without sound. More exactly: closing of stdout(1) and stderr(2) causes these problems. 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). Why does this affect the child process? Replacing close() by setting the flag FD_CLOEXEC doesn't change anything. Also setting the FD_CLOEXEC flag before fork doesn't solve the problem. Is there a better way to prevent inheritance of file descriptors?

即使程序调用失败,waitpid的返回值通常为0,我认为是因为有两个(异步)进程. usleep(50)可以根据我的需要解决此问题,但是我希望对此问题有更好的解决方案.

The return value of waitpid is often 0, even if the program call fails, I think because there are two (asynchrone) processes. usleep(50) solves this problem for my needs, but I hope there are better solutions for this problem.

我正在使用vfork,但是使用fork也会发生相同的问题.

I'm using vfork, but the same problems occur by using fork.

推荐答案

首先,在2014年,请勿使用vfork,而只需使用 vfork(2)已过时,因为POSIX 2001和在POSIX 2008中删除).

First, in 2014, never use vfork but simply fork(2). (Since vfork(2) is obsolete since POSIX 2001 and removed in POSIX 2008).

然后,关闭大多数文件描述符的最简单方法就是

Then, the simplest way to close most of file descriptors is just

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

(提示:如果fd无效,则close(fd)将会失败,我们将忽略该失败;然后您从3开始保持打开状态0 == stdin , 1 == stdout ,2 == stderr ;因此,原则上所有上述close都将失败).

(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).

但是,行为良好且编写良好的程序在关闭时不需要这样的循环(因此,这是克服先前错误的一种粗略方法).

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).

当然,如果您知道除stdin,stdout,stderr之外的某些文件描述符是有效的,并且对子program_call而言是必需的(这不太可能),则需要显式跳过它.

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.

,然后尽可能多地使用FD_CLOEXEC.

and then use FD_CLOEXEC as much as possible.

如果您不知道它们,程序中就不会有很多文件描述符.

It is unlikely that your program would have a lot of file descriptors without you knowing them.

也许您想要 daemon(3)或(正如 vality 所评论的那样)

Maybe you want daemon(3) or (as commented by vality) posix_spawn.

如果您需要显式关闭STDIN_FILENO(即0)或STDOUT_FILENO(即1)或STDERR_FILENO(即2),则最好在open("/dev/null", ...和dup2之后关闭它们-在调用exec之前,因为大多数程序都希望它们存在.

If you need to explicitly close STDIN_FILENO (i.e. 0), or STDOUT_FILENO (i.e. 1), or STDERR_FILENO (i.e. 2) you'll better open("/dev/null",... and dup2 them after - before calling exec, because most programs expect them to exist.

这篇关于为什么在fork之后关闭文件描述符会影响子进程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆