zoukankan      html  css  js  c++  java
  • 执行时关闭标识位 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
    ",fd);
            execl( "/bin/touch", "touch", "test.txt", (char*)NULL );
            return 0;
        }
        else
        {       
            printf("The file test.txt open success ! The fd = %d
    ", fd);
        }
    
        printf("fork!
    ");
    
    
        // 什么也不写,相当于系统默认 fcntl(fd, F_SETFD, 0) ,即用 execl 执行子进程时,
        // 不打开“执行时关闭”标识位 FD_CLOEXEC,此时子进程可以向 test.txt 写入字符串
    char *s="The Parent Process Writed !
    ";
    
        pid_t pid = fork();
        if(pid == 0)                                        /* Child Process */
        {       
            printf("***** exec child *****
    ");
            execl("child", "./child", &fd, NULL);
            printf("**********************
    ");
        }
    
        // 等待子进程执行完毕
        wait(NULL);
        ssize_t writebytes = write(fd,s,strlen(s));
        if ( writebytes == -1 )
        {
             printf("The Parent Process Write To fd : %d Failed !
    ", 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
    ",argc);
        
        if ( argv[1] == NULL )
        {
            printf("There is no Parameter !
    ");
            return 0;
        }
    
        int fd = *argv[1];
        printf("child fd = %d
    ",fd);
        
        char *s = "The Child Process Writed !
    ";
        ssize_t writebytes = write(fd, (void *)s, strlen(s));
        if ( writebytes == -1 )
        {
            printf("The Child Process Write To fd : %d Failed !
    ", fd);
        }
        
        close(fd);
        return 0;
    }

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

    The Child Process Writed !
    The Parent Process Writed !

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

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

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

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

    The Parent Process Writed !

    更标准的写法是:

    …………前面代码省略
    printf("fork!
    ");
     
    // 和 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 !
    ";
    …………后面代码省略

    推荐后面一种写法。

    如果在后面重新进行设置 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
    ",fd);
            execl( "/bin/touch", "touch", "test.txt", (char*)NULL );
            return 0;
        }
        else
        {       
            printf("The file test.txt open success ! The fd = %d
    ", fd);
        }
    
        printf("fork!
    ");
    
    
        // 系统默认 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 !
    ";
    
        pid_t pid = fork();
        if(pid == 0)                                        /* Child Process */
        {       
            printf("***** exec child *****
    ");
            
            // execl("child", "./child", &fd, NULL);
            // 注意下面,子进程不用 exec 函数,而是改成直接写入处理
            // 此时文件描述符标识位 FD_CLOEXEC 不再起作用
            // 即使设置这个标识位,子进程一样可以写入
            char *s = "The Child Process Writed !
    ";
            ssize_t writebytes = write(fd, (void *)s, strlen(s));
            if ( writebytes == -1 )
            {
                printf("Child Process Write To fd : %d Failed !
    ", fd);
            }       
            
            printf("**********************
    ");
            // 注意这里结束子进程,但不要关闭文件描述符,否则父进程无法写入
            exit(0);
        }
    
        // 等待子进程执行完毕
        wait(NULL);
        ssize_t writebytes = write(fd,s,strlen(s));
        if ( writebytes == -1 )
        {
                printf("The Parent Process Write To fd : %d Failed !
    ", fd);
        }
    
        close(fd);
        return 0;
    }

    注意修改后的地方:

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

    在前面仍然要设置标志:

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

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

    The Child Process Writed !
    The Parent Process Writed !

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

  • 相关阅读:
    YbtOJ:NOIP2020 模拟赛B组 Day10
    洛谷11月月赛Ⅱ-div.2
    P1494 [国家集训队]小Z的袜子
    [模板]莫队/P3901 数列找不同
    P4145 上帝造题的七分钟2 / 花神游历各国
    P4109 [HEOI2015]定价
    P4168 [Violet]蒲公英
    分块
    P3378 【模板】堆(code)
    网络基础——网络层
  • 原文地址:https://www.cnblogs.com/sunrisezhang/p/4113500.html
Copyright © 2011-2022 走看看