文件描述符fd
转载于 奇伢云存储
fd 是什么
fd 是 File descriptor 的缩写,中文名叫做:文件描述符。文件描述符是一个非负整数,本质上是一个索引值。
什么时候拿到的 fd?
当打开一个文件时,内核向进程返回一个文件描述符(open系统调用),后续 read、write 这个文件时,则只需要用这个文件描述符来标识该文件,将其作为参数传入 read、write。
fd 的值范围是什么?
在 POSIX 语义中,0,1,2 这三个 fd 值已经被赋予特殊含义,分贝时标准输入(STDIN_FILENO),标准输出(STDOUT_FILENO),标准错误(STDERR_FIFENO)。
文件描述符是有一个范围的:0 ~ OPEN_MAX - 1,最早期的 UNIX 系统中的范围很小,现在的主流系统单就这个值来说,变化范围是几乎不受限制的,只收到系统硬件配置和系统管理员配置的约束。
1 | 查看当前系统的配置 |
窥探 Linux 内核
stask_struct
进程的抽象是基于 struct task_struct 结构体,只提取需要理解的字段
1 | struct task_struct { |
files 是一个指针,指向一个为 struct files_struct 的结构体。这个结构体就是用来管理该进程打开的所有文件的管理结构。
重点理解一个概念
struct task_struct 是进程的抽象封装,表示一个进程。当创建一个进程,其实就是创建一个 struct task_struct。
files_struct
这个结构体管理某进程打开的所有文件的管理结构
1 | /* |
files_struct 是用来管理所有打开的文件的。本质上就是数组管理的方式,所有打开的文件结构都在要给数组里。
- struct file * fd_array[NR_OPEN_DEFAULT] 静态数组
- struct fdtable 动态数组
files_struct 小结
files_struct 本质上是用来管理进程所有打开的文件的,内部的核心是由一个静态数组和动态数组管理结构实现的。
文件描述符 fd 本质上就是索引,fd 就是这个数组的索引,通过非负数 fd 就能拿到对应的 struct file 结构体的地址。
- fd 就是 files 这个字段指向的指针数组的索引而已。通过 fd 能够找到对应文件的 struct file 结构体;
file
struct file 标识一个进程打开的文件,下面解释几个重要的字段:1
2
3
4
5
6
7
8
9
10
11
12
13
14struct file {
// ...
struct path f_path;
struct inode *f_inode;
const struct file_operations *f_op;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
// ...
} - f_path:标识文件名
- f_inode:inode 这个是 vfs 的 inode 类型,是基于具体文件系统之上的抽象封装
- f_post:当前文件偏移
思考问题:
思考问题一:files_struct 结构体只会属于一个进程,那么 struct file 这个结构体呢,是指挥属于某一个进程?还是可能被多个进程共享?
划重点:struct file 是属于系统级别的结构体,换句话说是可以共享与多个不同的进程。
inode
inode 是操作系统抽象出来的一层虚拟文件系统,叫做 VFS(Virtual File System),而在 VFS 之下才是真正的文件系统。
后面的没看懂,就不写了-_-
小结梳理
当用户打开一个文件时,用户只得到了一个 fd 句柄,但内核做了很多事情,我们得到几个关键的数据结构,这几个数据结构是有层次递进关系的,我们简单梳理下:
- 进程结构 task_struct:表进程实体,每一个进程都和一个 task_struct 结构体对应,其中 task_struct.files 指向一个管理打开文件的结构体 files_struct;
- 文件表项管理结构 files_struct: 用于管理进程打开的 open 文件列表,内部以数组的方式实现(静态数组和动态数组结合)。返回给用户的 fd 就是这个 数组的编号索引而已,索引元素为 file 结构;
- files_struct 只从属于某进程
- 文件 file 结构:表征一个打开的文件,内部包含关键的字段有当前文件偏移,inode 结构地址;
- 该结构虽然由进程触发创建,但是 file 结构可以在进程间共享;
- vfs inode 结构体:文件 file 结构指向的 vfs 的 inode,这个是操作系统抽象出来的一层,用于屏蔽后端各种各样的文件系统的 inode 差异;
- inode 与具体进程无关,是文件系统级别的资源;
- ext4 inode 结构体(指代具体文件系统 inode):后端文件系统的 inode 结构,不同文件系统自定义的结构体;
完整的结构图
思考实验
文件读写的时候会发生什么?
- 在完成 wirte 操作后,在文件 file 中的当前文件偏移量会增加所写入的字节数,如果这导致当前文件偏移量超出了当前文件长度,则会把 inode 的当前长度设置为当前文件偏移量(也就是文件变长)
- O_APPEND 标志打开一个文件,则相应的标识会被设置到文件 file 状态的标识中,每次对这种具有追加写标识的文件执行 write 操作的时候,file 的当前偏移量首先会被设置成 inode 结构体中的文件长度,这就使得每次写入的数据都追加到问价你的当前尾端处(该操作对用户态提供原子语义);
- 若一个文件 seek 定位到文件当前的尾端,则 file 中的当前文件偏移量设置成 inode 的当前文件长度;
- 每个进程对它自己的 file,其中包含了当前文件偏移,当多个进程写同一个文件时,由于一个文件 IO 最终只会是落到全局的一个 inode 上,这种并发场景则可能产生用户不可预期的结果;
总结
简要的总结
- 从姿势上来讲,用户 open 文件得到一个非负数句柄 fd,之后针对于该文件的 IO 操作都是基于这个 fd;
- 文件描述符 fd 本质上来讲就是数组索引,fd = 5,那对应数组的第 5 个元素而已,该数组是进程打开的所有文件的数组,数组元素类型为 struct_file;
- 结构体 task_struct 对应一个抽象的进程,files_struct 是这个进程管理该进程打开的文件数组管理器。fd 则对应了这个数组的编号,每一个打开的文件用 file 结构体表示,内含当前偏移等信息;
- file 结构体可以为进程共享,属于系统级资源,同一个文件可能对应多个 file 结构体,file 内部有个 inode 指针,指向文件系统的 inode;
- inode 是文件系统级别的概念,只由文件系统管理维护,不因进程改变(file 是进程触发创建的,进程 open 同一个文件会导致多个 file,指向同一个 inode);
架构图