首页 > 代码库 > 执行时关闭标识位 FD_CLOEXEC 的作用

执行时关闭标识位 FD_CLOEXEC 的作用

首先先回顾 apue 中对它的描述:

① 表示描述符在通过一个 exec 时仍保持有效(书P63,3.14节 fcntl 函数,在讲 F_DUPFD 时顺便提到)

② 对打开文件的处理与每个描述符的执行时关闭(close-on-exec)标志值有关。

见图 3-1 节中对 FD_CLOEXEC 的说明,进程中每个打开描述符都有一个执行时关闭标志。若此标志设置,

则在执行 exec 时关闭该描述符,否则该描述符仍打开。除非特地用 fcntl 设置了该标志,否则系统的默认

操作是在执行 exec 后仍保持这种描述符打开。(书P190,8.10节 exec 函数)

 

概括为:

① FD_CLOEXEC 是“文件描述符”的标志

② 此标志用来控制在执行 exec 后,是否关闭对应的文件描述符

(关闭文件描述符即不能对文件描述符指向的文件进行任何操作)

 

下面以一个例子进行说明,包含两个独立程序,一个用来表示父进程,另一个表示它的子进程

 

父进程 parent.c:

// parent.c#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <string.h>int main(){            int fd = open("test.txt",O_RDWR|O_APPEND);    if (fd == -1)    {               printf("The file test.txt open failed ! The fd = %d\n",fd);        execl( "/bin/touch", "touch", "test.txt", (char*)NULL );        return 0;    }    else    {               printf("The file test.txt open success ! The fd = %d\n", fd);    }    printf("fork!\n");    // 什么也不写,相当于系统默认 fcntl(fd, F_SETFD, 0) ,即用 execl 执行子进程时,    // 不打开“执行时关闭”标识位 FD_CLOEXEC,此时子进程可以向 test.txt 写入字符串char *s="The Parent Process Writed !\n";    pid_t pid = fork();    if(pid == 0)                                        /* Child Process */    {               printf("***** exec child *****\n");        execl("child", "./child", &fd, NULL);        printf("**********************\n");    }    // 等待子进程执行完毕    wait(NULL);    ssize_t writebytes = write(fd,s,strlen(s));    if ( writebytes == -1 )    {         printf("The Parent Process Write To fd : %d Failed !\n", fd);    }    close(fd);    return 0;}

 

 

子进程 child.c

//child.c#include <stdio.h>#include <unistd.h>#include <string.h>int main(int argc, char *argv[]){    printf("argc = %d\n",argc);        if ( argv[1] == NULL )    {        printf("There is no Parameter !\n");        return 0;    }    int fd = *argv[1];    printf("child fd = %d\n",fd);        char *s = "The Child Process Writed !\n";    ssize_t writebytes = write(fd, (void *)s, strlen(s));    if ( writebytes == -1 )    {        printf("The Child Process Write To fd : %d Failed !\n", fd);    }        close(fd);    return 0;}

 

此时观察 test.txt ,得到结果

The Child Process Writed !The Parent Process Writed !

 

因为代码中没做任何操作,系统默认是不设置“执行时关闭标识位”的。

现在在代码中进行设置这个标志:

 

…………前面代码省略
printf("fork!\n");
 
fcntl(fd, F_SETFD, 1);
 
char *s="The Parent Process Writed !\n";
…………后面代码省略

 

此时再观察 test.txt,发现只能看到父进程的输出了:

The Parent Process Writed !

 

更标准的写法是:

…………前面代码省略printf("fork!\n");
 
// 和 fcntl(fd, F_SETFD, 1) 等效,但这是标准写法,即用 FD_CLOEXEC 取代直接写1int tFlags = fcntl(fd, F_GETFD);fcntl(fd, F_SETFD, tFlags | FD_CLOEXEC );char *s="The Parent Process Writed !\n";
…………后面代码省略

推荐后面一种写法。

如果在后面重新进行设置 fcntl(fd, F_SETFD, 0) ,即可重新看到子进程的输出(读者可以自己尝试)。

 

那么问题来了,如果子进程不使用 exec 函数执行的这种方式呢?

那么理论上设置这个标志是无效的。

 

// parent.c#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <stdlib.h>int main(){            int fd = open("test.txt",O_RDWR|O_APPEND);    if (fd == -1)    {               printf("The file test.txt open failed ! The fd = %d\n",fd);        execl( "/bin/touch", "touch", "test.txt", (char*)NULL );        return 0;    }    else    {               printf("The file test.txt open success ! The fd = %d\n", fd);    }    printf("fork!\n");    // 系统默认 fcntl(fd, F_SETFD, 0) ,即用 execl 执行子进程时,    // 不打开“执行时关闭”标识位 FD_CLOEXEC    //fcntl(fd, F_SETFD, 1);    //fcntl(fd, F_SETFD, 0);    // 和 fcntl(fd, F_SETFD, 1) 等效,但这是标准写法,即用 FD_CLOEXEC 取代直接写1    int tFlags = fcntl(fd, F_GETFD);    fcntl(fd, F_SETFD, tFlags | FD_CLOEXEC );    char *s="The Parent Process Writed !\n";    pid_t pid = fork();    if(pid == 0)                                        /* Child Process */    {               printf("***** exec child *****\n");                // execl("child", "./child", &fd, NULL);        // 注意下面,子进程不用 exec 函数,而是改成直接写入处理        // 此时文件描述符标识位 FD_CLOEXEC 不再起作用        // 即使设置这个标识位,子进程一样可以写入        char *s = "The Child Process Writed !\n";        ssize_t writebytes = write(fd, (void *)s, strlen(s));        if ( writebytes == -1 )        {            printf("Child Process Write To fd : %d Failed !\n", fd);        }                       printf("**********************\n");        // 注意这里结束子进程,但不要关闭文件描述符,否则父进程无法写入        exit(0);    }    // 等待子进程执行完毕    wait(NULL);    ssize_t writebytes = write(fd,s,strlen(s));    if ( writebytes == -1 )    {            printf("The Parent Process Write To fd : %d Failed !\n", fd);    }    close(fd);    return 0;}

 

注意修改后的地方:

if(pid == 0)                                        /* Child Process */{               printf("***** exec child *****\n");                // execl("child", "./child", &fd, NULL);        // 注意下面,子进程不用 exec 函数,而是改成直接写入处理        // 此时文件描述符标识位 FD_CLOEXEC 不再起作用        // 即使设置这个标识位,子进程一样可以写入        char *s = "The Child Process Writed !\n";        ssize_t writebytes = write(fd, (void *)s, strlen(s));        if ( writebytes == -1 )        {            printf("The Child Process Write To fd : %d Failed !\n", fd);        }                       printf("**********************\n");        // 注意这里结束子进程,但不要关闭文件描述符,否则父进程无法写入        exit(0);}

在前面仍然要设置标志:

int tFlags = fcntl(fd, F_GETFD);fcntl(fd, F_SETFD, tFlags | FD_CLOEXEC );

重新编译,观察结果,发现子进程又可以重新写文件了:

The Child Process Writed !The Parent Process Writed !

证明设置这个标志,对不用 exec 的子进程是没有影响的。

执行时关闭标识位 FD_CLOEXEC 的作用