【Operating System】文件描述符(File Descriptor)

Posted by 西维蜀黍 on 2019-02-22, Last Modified on 2022-12-10

文件描述符(File Descriptor)

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值。

操作系统内核为每个进程都维护一张**文件描述符表(file descriptor table)**以记录着所有的文件描述符

文件描述符(file descriptors)可描述一个文件、数据资源(比如网络socket)或者I/O设备。

当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于 UNIX、Linux 这样的操作系统。


文件描述符最初被用于 Unix 中,此后被大部分现代类 Unix 使用,包括Linux, macOS X 和 BSD。在 Windows 中,文件描述符被称为文件句柄(File Handles)

对于类*nix操作系统,每个进程至少包括三个文件描述符,分别对应于三个标准流,分别是 STDIN (standard input)STDOUT (standard output) STDERR (standard error)

Integer value Name <unistd.h> symbolic constant <stdio.h> file stream
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr

当我们在 Linux 下读取一个文件,而用户权限不足时,会提示 Permission Denied,这会写入到 stderr 中。通过将 file descriptor 2 重定向到/dev/null中,这个提示不会被出现到控制台的屏幕上了。

find / -name '*something*' 2>/dev/null

基于*nix万物皆文件的哲学,每个物理硬件设备都会在文件系统中体现为一个文件(位于/dev下)。内核以将硬件设备抽象成文件的方式,进而向用户提供统一的设备调用接口。这样,用户就可以直接与硬件设备交互(而无需关心这个设备底层是如何实现这个具体的操作的)

文件描述符与打开文件之间的关系

内核维护的3个数据结构

  • 进程级文件描述符表(file descriptor table)
  • 系统级全局文件表global file table
  • 文件系统i-node表(i-node table)

每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开,也可以在同一个进程中被多次打开。

进程级文件描述符表(file descriptor table)

操作系统内核为每个进程都维护一张**进程级文件描述符表(file descriptor table)**以记录着所有的文件描述符,该表每一条目都记录了单个文件描述符的相关信息,包括:

  • 控制标志(flags),目前内核仅定义了一个,即close-on-exec
  • 打开文件描述体指针

系统级全局文件表global file table

同时,内核对所有打开的文件的文件维护有一个系统级全局文件表global file table。表中各条目称为打开文件描述体(open file description),存储了与一个打开文件相关的全部信息,包括:

  • 文件偏移量(file offset):调用read()write()更新,调用lseek()直接修改
  • 状态标示(status flags):由open()调用设置,例如:只读、只写或非阻塞(nonblocking)等
  • inode 值

inode表

文件系统会为存储于其上的所有文件(包括目录)维护一个 inode 表,每个 inode 节点包含以下信息:

  • 文件类型(file type),可以是常规文件、目录、套接字或FIFO
  • 访问权限
  • 文件锁列表(file locks)
  • 文件大小
  • 等等

inode 存储在磁盘设备上,内核在内存中维护了一个副本,这里的i-node表为后者。副本除了原有信息,还包括:引用计数(从打开文件描述体)、所在设备号以及一些临时属性,例如文件锁。

What happens if a process forks

When a process forks, all the descriptors are “duplicated” in the child process. If any of the descriptors are marked close-on-exec, then after the parent forks but before the child execs, the descriptors in the child marked as close-on-exec are closed and will no longer be available to the child process. The parent can still continue using the descriptor but the child wouldn’t be able to use it once it has exec*-ed.*

https://copyconstruct.medium.com/the-method-to-epolls-madness-d9d2d6378642

Reference