一、原理

linux支持多进程间共享打开文件,即同一时刻允许多个进程同时打开同个文件,每个进程之间的读写操作互不影响。为了实现这一个机制,linux内核使用了三种数据结构来表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

1.1 内核数据结构

每个进程的进程表中有一个记录项,包含了当前进程所有打开的文件描述符,它包含了一个指向文件表项的指针和文件描述符标志。内核中,为所有打开的文件维持一张表,它包含了以下内容:

  • 当前文件打开的状态:以何种方式打开的该文件,只读、只写或是可读可写等。
  • 当前文件的偏移量:当前文件指针所处的位置。
  • 指向该文件节点表的指针:节点包含了当前文件的属性信息。

每个文件的信息被封装在一个v节点表项中,包含了当前文件的文件名、所有者以及inode等信息。

三者之间的状态关系为:

1.2 多进程共享同一个文件

对于多个进程打开的同一个文件,其状态关系为:

正因为每个文件描述符都有一个属于自己的文件表项,所以每个进程间的文件指针偏移相互独立,互相读写不干扰。但是打开同一个文件的时候v节点指针都指向同一个节点:

  • 每次完成write后,文件表项的当前文件指针偏移量也会立马加上写入的字节数。
  • 如果打开文件的时候加了O_APPEND参数,每次写入数据前会先把偏移量设置到文件末尾。
  • 通过lseek函数只修改当前文件偏移量,不进行任何I/O操作。

有一个要注意的是,每次fork进程后,子进程会复制父进程的文件描述符,两者相互独立。

二、dup和dup2

dup和dup2都可以用来复制一个现有的文件描述符,其用法如下:

#include <unistd.h>

int dup(int fd);
int dup2(int fd1, int fd2);

dup函数直接把复制后的文件描述符返回,返回的一定是当前文件描述符表中的最小数值。

对于dup2,可以通过fd2表示新描述符的值,如果fd2已经打开,系统会先关闭。如果fd1等于fd2,则直接返回不关闭。

复制过后的文件描述符共享一个文件表项,共享后的状态如下:

我们可以通过一个程序来验证这一个结论:

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    char buff[6] = { 0 };
    int fd_1, fd_2;

    fd_1 = open("data.txt", O_RDONLY);
    if (fd_1 == -1) {
        perror("open file error");
        return -1;
    }

    fd_2 = dup(fd_1);
    if (fd_2 == -1) {
        perror("dup error");
        return -1;
    }

    if (read(fd_1, buff, 5) == -1) {
        perror("read error at fd_1");
        return -1;
    }
    printf("fd_1 read: %s\n", buff);

    if (read(fd_2, buff, 5) == -1) {
        perror("read error at fd_2");
    }
    printf("fd_2 read: %s\n", buff);

    close(fd_1);
    close(fd_2);

    return 0;
}

上面的代码中通过fd_1打开文件data.txtfd_2复制fd_1,两个文件描述符文件从文件中读取5个字节数据并打印出来。

我们编译代码执行:

# 先写十个字节数据到文件
> echo "HelloWorld" > data.txt
> mkdir debug
# 编译
> gcc dup.c -o debug/dup
# 执行
>  ./debug/dup 
fd_1 read: Hello
fd_2 read: World

可以看到,fd_2读取的数据是从第5个字节开始,即从fd_1读完偏移处开始,两者确实共享了同一个文件表项。

最后修改:2020 年 02 月 06 日
如果觉得我的文章对你有用,请随意赞赏