咸鱼白的窝
一些奇奇怪怪的东西
聊聊文件描述符、fork()和O_CLOEXEC
父与子的关系总是难以理解

引言

本文只是记录了一次看上去意义不大的尝试与总结,如果看下去,可能会浪费您宝贵的5分钟时间。

今日正为下周的分享会做准备,本打算讲讲当我们在终端键入./helloworld程序后都发生了什么事,理到__start初始化时,突然想起前不久研究的关于splice调用中易出现的一个bug,在当初的测试中,一旦我在子进程中修改了stdoutmode后,即使我在terminal中运行没有修改过的程序,也不会出bug了,子进程的文件描述符状态变化会影响到父进程shell吗?(事实证明是我这个菜鸡记性太差,该回头好好复习Linux系统编程了)

文件描述符与打开文件表

《Linux/Unix系统编程手册》的图5-2诠释了这种现象的本质原因,如图:

进程的每一个文件描述符都会指向由内核维护的打开文件表(open file table)中的一个打开文件句柄(open file handle),一个打开文件句柄存储着与一个打开文件相关的所有信息(文件偏移量,状态标志等)。

父进程调用fork后,子进程会继承其所有已打开的文件描述符(不包括父进程在fork之后打开的文件描述符),即父进程与子进程的这些文件描述符指向了相同的打开文件句柄

上图进程A的fd2和进程B的fd2都指向了打开文件句柄73,这种情况通常都是在父进程fork出子进程后出现的,也有可能是某一进程使用UNIX域套接字将一个文件描述符传递给了另一个进程(第二种情况可参见《Linux/Unix系统编程手册》61.13.3-传递文件描述符,此处不做赘述)。

用O_CLOEXEC进行的一次尝试

关于O_CLOEXEC模式打开的文件描述符,我也不做什么过多解释了,大家只要知道进程调用exec时会关闭O_CLOEXEC模式打开的文件描述符就行了。

接下来,只不过是我在理清了上面的问题后做的一个无聊尝试罢了。

如果我们在父进程中把stdout设为O_CLOEXEC,fork出的子进程exec后还能正常输出吗?

我用这两段不怎么规范的垃圾代码试了一下:

//test1.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(void){
    fcntl(STDOUT_FILENO, F_SETFD, FD_CLOEXEC);
    printf("test1\n");
    int stat;
    int pid = fork();
    if (pid == 0){
        execv("./test2", NULL);
    }
    else{
        printf("father\n");
        pid = wait(&stat);
    }
    
    return 0;
}
//test2.c
#include <stdio.h>

int main(void){
    printf("test2\n");
    return 0;
}

事实证明,确实不能正常输出了。

所以这个探究有啥意义吗?好像没有诶,不过为啥一定要有意义才去探究呢?

同样重要的细节补充

对了,顺便一提,假如按我关于文件描述符与打开文件表的说法,test1设置了O_CLOEXEC状态后,理论上也会影响到test1的父进程shell,后续该终端的子进程应该都没法输出了,但事实上并没有出现预想中的情况,该终端的子进程依旧可以正常输出,其stdout的O_CLOEXEC状态并没有被设置,为啥?

《Linux/Unix系统编程手册》中做出了提示:O_CLOEXEC标志为进程和文件描述符所私有,对其修改不会影响到同一进程或不同进程中的其他文件描述符。

就是这样。


Last modified on 2021-02-07