首先先回顧 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 的子進程是沒有影響的。