执行时关闭标识位 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 的子进程是没有影响的。

时间: 2024-12-22 18:50:10

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

java内存结构(执行时数据区域)

java虚拟机规范规定的java虚拟机内存事实上就是java虚拟机执行时数据区,其架构例如以下: 当中方法区和堆是由全部线程共享的数据区. Java虚拟机栈.本地方法栈和程序计数器是线程隔离的数据区. (1).程序计数器: 是一块较小的内存空间,其作用能够看作是当前线程所运行的字节码的行号指示器,字节码解析器工作时通过改变程序计数器的值来选取下一条须要运行的字节码指令. 程序的分支.循环.跳转.异常处理以及线程恢复等基础功能都是依赖程序计数器来完毕. Java虚拟机的多线程是通过线程轮流切换并分

函数执行时的作用域链和活动对象是怎么形成的及与闭包的关系

函数执行时的作用域链和活动对象是如何形成的及与闭包的关系1.javascript解析器启动时就会初始化建立一个全局对象global object,这个全局对象就 拥有了一些预定义的全局变量和全局方法,如Infinity, parseInt, Math,所有程序中定义的全局变量都是这个全局对象的属性.在客户端javascript中,Window就是这个javascript的全局对象. 2.当javascript执行一个function时,会生成一个对象,称之为call object,functio

tcp协议的六个标识位

6个标识位: URG 紧急指针,告诉接收TCP模块紧要指针域指着紧要数据. ACK 置1时表示确认号(为合法,为0的时候表示数据段不包含确认信息,确认号被忽略. PSH 置1时请求的数据段在接收方得到后就可直接送到应用程序,而不必等到缓冲区满时才传送. RST 置1时重建连接.如果接收到RST位时候,通常发生了某些错误. SYN 置1时用来发起一个连接. FIN 置1时表示发端完成发送任务.用来释放连接,表明发送方已经没有数据发送了. 其中URG不能和PSH标志位同时使用. URG为紧急数据标志

CentOS安装redis-audit 但执行时出错未解决 记录一下安装过程

网上很多安装过程都太老了,测试很多方法终于成功了,但执行时还是出错,哪位熟悉的可以告知一下. yum install -y ruby rubygems ruby-devel git gcc gem sources --remove http://rubygems.org/gem sources -a https://ruby.taobao.org/gem sources -lgem install bundler git clone https://github.com/snmaynard/re

iOS执行时工具-cycript

cycript是大神saurik开发的一个很强大的工具,能够让开发人员在命令行下和应用交互,在执行时查看和改动应用.它确实能够帮助你破解一些应用,但我认为这个工具主要还是用来学习其它应用的设计(主要是UI的设计及实现). 这个工具使用了Objective-C和Javascript的混合模式,能够实时的和应用交互甚至改动应用.它的网址请猛戳这里.在官网上能够下载到完整的软件包.使用的方式有两种,一种是在越狱的设备上通过MobileSubstrate加装,这样能够在全部的应用里使用:还有一种是通过静

各进程端口号修改以及特殊文件标识位的介绍

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue"; color: #454545 } span.s1 { font: 12.0px "Helvetica Neue" } span.s2 {

IOS -执行时 (消息传递再探究)

一 消息查找优化 至此.我们已经明确了Objective-c中大致的消息传递过程,我们发现假设每次函数调用都经历上面的过程(.那函数调用的效率就会非常低,尤其是当类的继承层次非常多的时候.它须要一层层的查找其效率将会更低,为了加快查找调用的速度,Objective-c对消息查找做了优化. 从前一节的类对象我们知道它含有一个?struct objc_cache *cache成员,这个缓存就是为了提高查找的效率的. 每一个类都有自己的缓存,同一时候包含继承的方法和在该类中定义的方法. 当我们在查找I

Shell执行时显示指令本身&amp;&amp;显示shell指令

shell脚本如何显示所执行的每一条命令,例如执行一个bash脚本时,只看到了各个命令的执行结果,但是没有看到具体的命令 #!/bin/bash ls #end 这个脚本执行时,并不打印命令'ls' 怎么样才能让其先打印出所执行的命令,在执行命令呢? 1.方法一 在脚本中设置-x参数,让命令执行时打印其命令本身和参数,如: #!/bin/bash set -x ls #end 运行结果 + ls 2.方法二 除了方法一的作用外,输出还会原封不动的打印执行后的每一条指令,如: #!/bin/bas

Android学习笔记(十四)——在执行时加入碎片(附源代码)

在执行时加入碎片 点击获取源代码 将UI切割为多个可配置的部分是碎片的优势之中的一个,但其真正强大之处在于可在执行时动态地把它们加入到活动中. 1.使用上一篇创建的Fragments项目,在main.xml文件里凝视掉两个<fragment>元素: 2.在FragmentActivity.java中加入以下的代码: FragmentManager fragmentManager = getSupportFragmentManager();//向活动加入碎片 FragmentTransactio