Linux文件描述符

Linux文件描述符

Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符(file descriptor,fd)1, 4,在windows下面,这玩意儿叫file handle,句柄。

文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4......2

可以简单理解成系统维护的文件描述符表是一个数组,下标就是索引(文件描述符),数组内容就是一个个指向文件的指针(如0 -> stdin,1 -> stdout,2-> stderr)。

掌握它,有助于深入理解 Linux 文件系统、I/O 操作,

以及进程间通信(如管道(pipe)套接字(Socket))的实现,可以去Ubuntu系统中简单完成下面的例子。<( ̄︶ ̄)↗GO!

优化界面的Blog:Linux文件描述符

用户程序与内核交互的基本过程

打开文件

当一个用户程序需要访问某个文件时,它会通过系统调用(如 open())请求内核打开该文件。

内核会根据文件路径在文件系统中查找文件,并为该文件分配一个文件描述符。这个文件描述符是一个整数,表示该文件在内核中的唯一标识符。

内核维护着一个叫做 文件表(file table)的数据结构,文件描述符实际上就是对这个表中的一个条目的引用。

使用文件描述符读写文件

用户程序使用文件描述符来进行后续的文件操作。例如:

读取文件:用户程序调用 read(fd, ...) 系统调用,内核通过文件描述符 fd 查找对应的文件,并从磁盘中读取数据,将数据返回给用户程序。

写入文件:用户程序调用 write(fd, ...) 系统调用,内核通过文件描述符 fd 查找文件,向文件中写入数据。

文件描述符使得内核能够识别哪个文件需要被操作,从而实现文件与程序的交互。

文件描述符与文件表

内核通过文件描述符和文件表来管理已打开的文件。每个进程都有一个 文件描述符表,它是一个数组,其中每个索引对应一个文件描述符。这个文件描述符指向内核的文件表项,每个文件表项包含文件的状态信息(例如当前文件指针、文件权限等)。

文件操作的内核层处理

用户程序和内核之间的交互通常通过系统调用来实现,文件描述符是这些系统调用的接口。内核会根据文件描述符执行相应的操作:

打开文件时,内核会创建或查找该文件的内核对象,并更新文件描述符。

对文件进行读写操作时,内核通过文件描述符在文件表中查找文件对象,然后执行 I/O 操作(例如读取磁盘或写入磁盘)。

关闭文件

当用户程序完成文件操作后,它会通过系统调用 close(fd) 来关闭文件描述符。

内核会释放文件描述符所占用的资源,关闭文件的文件表项,并更新进程的文件描述符表。

通过文件描述符交互的具体例子

假设一个程序需要从文件中读取数据并进行处理,下面的示例代码:

cpp 复制代码
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("hello.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buffer[128];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead == -1) {
        perror("read");
        close(fd);
        return 1;
    }

    write(STDOUT_FILENO, buffer, bytesRead);

    close(fd);
    return 0;
}

在这个例子中,文件描述符的作用可以从以下几个步骤看到:

  • 打开文件时,open() 系统调用将返回一个文件描述符(fd),这个文件描述符在内核中表示 example.txt 文件的句柄。
  • 读取文件时,read() 系统调用使用文件描述符 fd 来访问内核中的文件表项,执行 I/O 操作并将文件数据读取到 buffer 中。
  • 写入数据时,通过 write(STDOUT_FILENO, ...) 输出数据,STDOUT_FILENO 是标准输出的文件描述符(通常是 1)。
  • 关闭文件时,close(fd) 系统调用会释放文件描述符所占用的资源,告知内核文件已经关闭。

通过文件描述符,程序可以与操作系统内核进行有效的通信,完成文件系统和其他 I/O 操作。

Linux上查看文件描述符列表

在 Linux 上使用 vim 打开文件时,操作系统通过文件描述符与文件系统进行交互。下面是一个具体例子。

打开文件(使用 Vim)

首先,你使用 vim 打开一个文件(例如 helloworld.cpp):

bash 复制代码
vim helloworld.cpp

这时,Vim 会启动并打开 helloworld.cpp 文件。在内核中,Vim 会使用文件描述符来与文件系统交互,即读取和编辑 helloworld.cpp 文件的内容。


在新 Shell 中查找 Vim 进程的 PID

接着,你可以打开另一个终端(Shell),通过 pidof 命令获取正在运行的 Vim 进程的进程 ID(PID):

bash 复制代码
pidof vim

假设返回的 PID 是 40133,说明 Vim 进程的进程号是 40133


查看 Vim 进程的文件描述符

Linux 系统中的每个进程都有一个对应的 /proc/pid/fd 目录,里面列出了该进程打开的所有文件的文件描述符。你可以通过以下命令查看 Vim 进程所使用的文件描述符列表:

bash 复制代码
ll /proc/40133/fd

这里,40133 是你之前通过 pidof vim 命令获得的 Vim 进程的 PID。

ll 命令会列出该目录下的文件,其中每个文件都对应着一个打开的文件描述符(文件句柄)。输出会类似于:

bash 复制代码
total 0
dr-x------ 2 allen allen  0 Dec 29 15:58 ./
dr-xr-xr-x 9 allen allen  0 Dec 29 15:58 ../
lrwx------ 1 allen allen 64 Dec 29 15:58 0 -> /dev/pts/8
lrwx------ 1 allen allen 64 Dec 29 15:58 1 -> /dev/pts/8
l-wx------ 1 allen allen 64 Dec 29 15:58 19 -> /home/allen/.vscode-server/data/logs/20241229T150828/ptyhost.log
lrwx------ 1 allen allen 64 Dec 29 15:58 2 -> /dev/pts/8
l-wx------ 1 allen allen 64 Dec 29 15:58 20 -> /home/allen/.vscode-server/data/logs/20241229T150828/remoteagent.log
lrwx------ 1 allen allen 64 Dec 29 15:58 21 -> /dev/ptmx
lrwx------ 1 allen allen 64 Dec 29 15:58 22 -> /dev/ptmx
lrwx------ 1 allen allen 64 Dec 29 15:58 23 -> /dev/ptmx
lrwx------ 1 allen allen 64 Dec 29 15:58 4 -> /home/allen/CPP/.helloworld.cpp.swp

这里的输出解释如下:

  • 0,1,2:这是标准输入、标准输出和标准错误,它们通常会指向终端设备(如 /dev/pts/8)。这些文件描述符是系统默认打开的,用于处理进程的 I/O 操作。

  • 4:这个文件描述符指向你用 Vim 打开的文件 helloworld.cpp。可以看到,/home/allen/CPP/.helloworld.cpp.swp 是文件描述符 4 对应的目标文件。

说明:

  • 在 Linux 中,每个进程都会为打开的文件、管道、设备、套接字等分配一个文件描述符,文件描述符的值通常是从 0 开始递增的。
  • 标准输入(0)、标准输出(1)、标准错误(2)是系统自动打开的,而打开的文件 helloworld.cpp 在 Vim 进程中会被分配到文件描述符 3 及以后。

可以看到新打开的 helloworld.cpp 的文件描述符,竟然是4,而不是从3开始,这里面有一番学问,涉及 vim 的原理。因为vim这种编辑器的原理是先打开源文件并拷贝,然后关闭源文件再打开自己的副本,修改完文件保存的时候直接将副本重命名覆盖源文件。所以打开源文件的时候用的文件描述符3,然后打开自己的副本是时候就该用文件描述符4了,然后关闭源文件,文件描述符3就被释放了,我们查看的时候就只剩下了4,这里它指向的是vim创建的副本文件3(这里有更详细的解释,这里是一个通俗的理解)。


  1. 检查文件描述符的具体信息

可以通过查看符号链接来了解更多细节,例如,可以用 readlink 命令查看文件描述符指向的文件路径:

bash 复制代码
readlink /proc/40133/fd/4

深入理解 Linux 中的文件描述符及其背后的数据结构

要深入理解 Linux 中的文件描述符及其背后的数据结构,我们需要了解内核如何通过三个核心数据结构来管理文件描述符:

  1. 进程级的文件描述符表(Process File Descriptor Table)
  2. 系统级的打开文件描述符表(System-wide Open File Table)
  3. 文件系统的 i-node 表(File System i-node Table)

这三个数据结构4共同工作,使得 Linux 系统能够高效地管理文件 I/O 操作,并确保每个进程对文件的访问是独立且有序的。


1. 进程级的文件描述符表

每个运行中的进程都有一个 进程控制块(PCB) ,它包含了与进程相关的各种信息。在这个 PCB 中,文件描述符表 (也称为 文件描述符数组)是一个非常重要的数据结构。

  • 文件描述符表的功能:每个进程的文件描述符表记录着该进程所打开的文件描述符。文件描述符是一个整数,它对应着进程所打开的文件、套接字、管道等资源。

  • 表的结构:文件描述符表是一个数组,每个文件描述符对应数组中的一个位置。例如,标准输入、标准输出、标准错误默认分别对应文件描述符 0、1、2,而其他文件则由内核为每个进程分配一个较大的文件描述符,如 3、4、5 等。

  • 进程独立性:进程级文件描述符表是进程私有的。不同进程之间是独立的,进程 A 使用文件描述符 3 打开的文件,进程 B 如果也打开了一个文件,可能也会被分配文件描述符 3。它们的文件描述符对应的是不同的文件资源。

进程级文件描述符表的关键点

  • 每个进程都维护一个自己的文件描述符表。
  • 文件描述符表的每个条目对应一个打开的文件或资源。
  • 文件描述符表存储的只是文件描述符与内核内部文件对象的引用。

2. 系统级的打开文件描述符表

系统级的 打开文件描述符表 是内核维护的一个全局数据结构,用来管理系统中所有进程共享的文件资源。每当一个进程打开文件时,内核会在此表中创建一项记录,表示这个文件被打开。

该表中的每项记录包含以下信息

  • 当前文件偏移量 :每个文件都有一个当前的读取或写入位置。当进程执行 read()write() 操作时,内核会根据该文件的偏移量进行相应的读写操作。在每次读取时,偏移量会自动更新,也可以通过 lseek() 系统调用显式地修改偏移量。

  • 打开文件时的标识 :由 open() 系统调用的 flags 参数指定,如只读、只写、读写等。内核在打开文件时会记录这些标识,用于后续的访问控制。

  • 文件访问模式 :当进程通过 open() 打开文件时,内核会记录文件的访问模式(如只读模式 O_RDONLY,只写模式 O_WRONLY,读写模式 O_RDWR)以及其他访问权限(如 O_APPENDO_NONBLOCK 等)。

  • 与信号驱动相关的设置:某些文件(如终端设备)可以通过信号驱动模式进行 I/O 操作。这些设置会记录在系统级的打开文件表中,以便内核在合适的时机处理信号。

  • 与文件的 i-node 关联 :系统级的打开文件描述符表项会保存指向文件系统 i-node 表项的指针。i-node 表项包含了该文件的元数据(如文件大小、权限、时间戳等)。

关键点

  • 每个进程打开文件时,系统级打开文件表会创建相应的记录。
  • 所有进程共享系统级的打开文件描述符表,通过这个表来管理文件的偏移量和访问模式等信息。

3. 文件系统的 i-node 表

i-node索引节点(Index Node)的缩写,是文件系统用来存储文件元数据的一种数据结构。每个文件都有一个对应的 i-node,i-node 不存储文件的内容,而是存储与文件相关的各种属性和元数据。

i-node 表包含以下内容

  • 文件类型:例如普通文件、目录文件、符号链接、套接字、FIFO 等。

  • 文件权限:表示文件的访问权限(如读、写、执行权限)。

  • 文件大小:文件的实际大小(字节数)。

  • 时间戳 :包括文件的创建时间、修改时间和访问时间(如 ctimemtimeatime 等)。

  • 指向文件数据块的指针:i-node 会存储指向文件实际数据块的指针(对于小文件直接存储指针,对于大文件使用间接块)。这些指针帮助操作系统在磁盘上定位文件内容。

  • 文件锁列表:如果文件被加锁,i-node 会存储一个指向锁信息的指针。

  • 引用计数:记录有多少个进程或文件描述符正在使用该文件。如果引用计数为 0,则表示该文件可以被删除。

i-node 的关键点

  • i-node 存储文件的元数据,而不存储文件的实际内容。
  • 每个文件在文件系统中都有唯一的 i-node。
  • 文件的内容由磁盘上的数据块来存储,而 i-node 中存储的是指向这些数据块的指针。

文件描述符如何协同工作

文件描述符表、系统级的打开文件描述符表和 i-node 表相互协作来管理文件资源:

  1. 进程级文件描述符表 存储进程所打开的文件描述符,它是进程私有的。当进程通过 open() 打开一个文件时,内核会在进程的文件描述符表中分配一个文件描述符,并且该文件描述符指向 系统级的打开文件描述符表 中的一个记录。

  2. 系统级的打开文件描述符表 存储所有打开文件的状态信息,如文件偏移量、访问模式等,并且每个表项都会指向对应文件的 i-node

  3. i-node 表 存储文件的元数据(如权限、大小等)以及文件内容所在的磁盘位置。每个打开的文件都会通过 i-node 来访问文件的实际数据。

当进程进行文件操作时(如 read()write()),操作首先通过进程级的文件描述符表查找对应的文件描述符,然后在系统级的打开文件描述符表中查找该文件的状态信息,并通过 i-node 访问文件的实际数据。


总结

  • 进程级的文件描述符表:每个进程独立维护,记录当前进程打开的文件描述符。
  • 系统级的打开文件描述符表:所有进程共享,记录文件的状态信息和 i-node 引用。
  • 文件系统的 i-node 表:记录文件的元数据和实际数据的位置信息。

这三个数据结构协作,使得 Linux 系统能够高效且灵活地管理文件 I/O 操作,确保进程之间的文件访问独立且有序,并且能够在多进程环境中正确地管理文件资源。

参考

1、[理解Linux的文件描述符FD与Inode](https://zhuanlan.zhihu.com/p/143430585)

2、[理解linux中的file descriptor(文件描述符)](https://wiyi.org/linux-file-descriptor.html)

3、[彻底弄懂 Linux 下的文件描述符(fd)](https://yushuaige.github.io/2020/08/14/%E5%BD%BB%E5%BA%95%E5%BC%84%E6%87%82%20Linux%20%E4%B8%8B%E7%9A%84%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%EF%BC%88fd%EF%BC%89/)

4、[Linux文件描述符到底是什么?](https://c.biancheng.net/view/3066.html)

相关推荐
用户9718356334663 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪5 小时前
linux 拷贝文件或目录到指定的位置
linux
大树8821 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠21 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质21 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush421 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52021 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz21 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理