執行時關閉標識位 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 取代直接寫1
int 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 的子進程是沒有影響的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM