一、概念解释
文件描述符
文件描述符(file descriptor, fd)是Linux系统中对已打开文件的一个抽象标记,所有I/O系统调用对已打开文件的操作都要用到它。这里的“文件”仍然是广义的,即除了普通文件和目录外,还包括管道、FIFO(命名管道)、Socket、终端、设备等。
文件描述符是一个简单的整数,用以标明每一个进程所打开的文件。第一个打开的文件是0,第二个打开的是1,依此类推。并且0、1、2三个描述符总是默认分配给标准输入、标准输出和标准错误
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。
- 进程级的文件描述符表
- 系统级的打开文件描述符表
- 文件系统的i-node表
由于进程级文件描述符表的存在,不同的进程中会出现相同的文件描述符,它们可能指向同一个文件,也可能指向不同的文件。两个不同的文件描述符,若指向同一个打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量,那么从另一个文件描述符中也会观察到变化,无论这两个文件描述符是否属于不同进程,还是同一个进程,情况都是如此。
文件句柄
内核会维护系统内所有打开的文件及其相关的元信息,该结构称为打开文件表(open file table)。表中每个条目包含以下域:
- 文件的偏移量。POSIX API中的read()/write()/lseek()函数都会修改该值;
- 打开文件时的状态和权限标记。通过open()函数的参数传入;
- 文件的访问模式(只读、只写、读+写等)。通过open()函数的参数传入;
- 指向其对应的inode对象的指针。内核也会维护系统级别的inode表;
文件描述符表、打开文件表、inode表之间的关系可以用书中的下图:
- 在进程A中,文件描述符1和30都指向了同一个打开的文件句柄(#23),这可能是该进程多次对执行
打开
操作 - 进程A中的文件描述符2和进程B的文件描述符2都指向了同一个打开的文件句柄(#73),这种情况有几种可能,1.进程A和进程B可能是父子进程关系;2.进程A和进程B打开了同一个文件,且文件描述符相同(低概率事件=_=);3.A、B中某个进程通过UNIX域套接字将一个打开的文件描述符传递给另一个进程。
- 进程A的描述符0和进程B的描述符3分别指向不同的打开文件句柄,但这些句柄均指向i-node表的相同条目(#1936),换言之,指向同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了打开请求。同一个进程两次打开同一个文件,也会发生类似情况。
可见,
- 一个打开的文件可以对应一个或多个文件描述符(不管是同进程还是不同进程)
- 一个inode也可以对应多个打开的文件
打开文件表中的一行称为一条文件描述(file description),也经常称为文件句柄(file handle)。
多嘴一句,“句柄”这个词在UNIX世界中并不很正式,但在Windows里遍地都是。Windows NT内核会将内存中的所有对象(文件、窗口、菜单、图标等一切东西)的地址列表维护成整数索引,这个整数就叫做句柄,逻辑上讲类似于“指针的指针”,感觉上还是有一些相通的地方的。
文件描述符和文件句柄的区别
简单来说,每个进程都有一个打开的文件表(fdtable)。表中的每一项是struct file类型,包含了打开文件的一些属性比如偏移量,读写访问模式等,这是真正意义上的文件句柄。
文件描述符是一个整数。代表fdtable中的索引位置(下标),指向具体的struct file(文件句柄)。
查看命令
1、查看系统最大文件句柄数
# cat /proc/sys/fs/file-max
13059184
该文件指定了可以分配的文件句柄的最大数目,即系统全局的可用句柄数目,file-max ≈ 内存大小/ 10k
2、查看当前已分配的文件句柄
# cat /proc/sys/fs/file-nr
4608 0 13059184
文件中的三个值分别表示:已分配的文件句柄数、已分配但未使用的文件句柄数、最大的文件句柄数(同file-max)
3、查看单个进程最大文件句柄数(阈值)
# cat /proc/sys/fs/nr_open
1048576
内核参数,指的是单个进程可打开的最大文件句柄数,默认值是1048576(1024*1024)
4、查看系统文件描述符总数
# lsof -P -n | wc -l
27287
lsof是列出系统所占用的资源(list open files),但是这些资源不一定会占用句柄。比如共享内存、信号量、消息队列、内存映射等,虽然占用了这些资源,但不占用句柄。
5、查看文件描述符和文件句柄的限制
# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 514556
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 655350
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 655350000
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
其中open files一行就表示当前用户、当前终端、单个进程能拥有的文件描述符的数量阈值,可以用ulimit -n [阈值]命令来临时修改,退出登录即失效。如果想要永久修改,可以将ulimit -n [阈值]写入用户的.bash_profile文件或/etc/profile中,也可以修改/etc/security/limits.conf:
# vim /etc/security/limits.conf
# 用户名 软/硬限制 限制项 阈值
root soft nofile 65535
root hard nofile 65535
总结:
- 文件描述符是进程级别的,文件句柄是系统级别的,不能混用。它们在不同级别表示已打开的文件。
- 文件描述符与文件句柄直接关联,文件句柄与inode直接关联。
- 文件描述符在POSIX系统调用中直接可见,文件指针是C语言在其基础上的包装。
- 文件句柄在UNIX里不是个正式概念,所以无论在系统还是C语言API中都不显式存在。
参考:
https://blog.csdn.net/nazeniwaresakini/article/details/104220111
https://www.modb.pro/db/52214
https://blog.csdn.net/u013256816/article/details/60778709
https://segmentfault.com/a/1190000009724931