文件描述符,简单来说是一个从0开始递增的非负整数。
具体来说是linux/unix对文件系统的一种底层抽象,这种抽象是通过三张表来实现的。
这三张表分别是:
1.进程级的文件描述符表;(文件标志位/文件指针)
2.系统级的打开文件描述符表;(当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改) 打开文件时的标识(open()的flags参数) 文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式) 与信号驱动相关的设置 对该文件i-node对象的引用,即i-node 表指针)
3.文件系统的i-node表。(文件的字节数 / 文件拥有者的User ID / 文件的Group ID / 文件的读、写、执行权限/文件的时间戳 / 链接数,即有多少文件名指向这个inode / 文件数据block的位置)
假设,现在一个linux/unix系统启动了。系统的底层储存由多个扇区组成,多个扇区组成一个块,这个块就是系统的存储的最小单元。
硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。inode就是通过定位块的位置和大小获得储存位置的定位。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
一个 Linux 进程启动后,会在内核空间中创建一个 PCB 控制块,PCB 内部有一个文件描述符表(File descriptor table),记录着当前进程所有可用的文件描述符,也即当前进程所有打开的文件。进程级的描述符表的每一条记录了单个进程所使用的文件描述符的相关信息。
特殊场景分析:
如果一个进程调用了dup()、dup2()、fcntl() 等方法,或者父进程打开后关闭了文件,父进程fork的子进程又打开了该文件,就可能会出现一个进程中多个文件描述符指向同一个文件打开表的情况。(参考上图的进程A的FD1和FD20,同时指向了打开文件表23)
如果进程A和进程B归属于同一个用户,进程A的FD2打开了一个文件后关闭,进程B的FD2又打开了同一个文件,就可能会出现多个进程中的文件描述符指向同一个文件打开表的情况。
(参考上图的进程A FD2和进程B FD2,同时指向了文件打开表73)
如果进程A的FD0打开了一个文件后未关闭,同时进程B的FD3又打开了同一个文件,就可能会出现多个进程的文件描述符指向不同的文件打开表,但是不同的文件打开表指向同一个inode的情况。
或者进程A和进程B归属于不同的用户,且不同用户对文件有不同的权限时,无论进程A和B同时或者不同时打开同一个文件,两个进程都会指向一个不同的文件打开表,但是不同的文件打开表又会指向同一个文件。
(参考上图的进程A FD0指向了文件打开表0,进程B的FD3指向了文件打开表86 ,但是文件打开表0和文件打开表86同时指向了inode1976)
文件描述符这种设计,是为了适应linux多用户多进程的特点。
进程级的文件描述符表赋予了多个进程同时管理一个文件的能力,系统级的文件打开表赋予了多个进程(对于多个用户对文件有不同的权限)或者同一个进程对一个文件同时操作的能力,inode表作为文件的元数据表才是物理层面的文件属性表。
可以简单理解为进程级的文件描述表为了多进程,文件打开表为了多用户,inode才是文件本身。