文章目录
- 文件描述符(file descriptor,简称 fd)本质上就是:操作系统给“已打开资源”分配的一个编号。非负整数
- 分配编号后,后续操作就是围绕这个编号来的。代码通常不直接用 fd 编号;真正直接使用 fd 编号的是底层系统调用和操作系统内核
- 进程启动后默认会有3个文件描述符
- 只有当代码需要操作系统帮你“打开某种外部资源”时,才会分配fd,才会占用文件描述符。
- 不是所有情况都是一个操作占一个fd
- 每个进程都有自己的一张“文件描述符表”,这张表里面记录:fd 号码 -> 这个 fd 指向什么资源。文件描述符不是全局唯一的,而是进程内部的编号。
- 为什么说fd是进程才有?文件描述符是操作系统为了管理某个进程打开的资源而分配的编号
- 子进程会继承父进程的一些 fd
- 父子进程的 fd 表不是同一张表,父进程和子进程都有自己的 fd 表
- 线程有没有文件描述符?线程属于同一个进程,所以同一个进程内的多个线程共享同一张 fd 表。
- 一切皆文件。Linux 把很多资源统一成“可以 open/read/write/close 的对象”。
- 操作 fd,就是操作 fd 指向的资源
- 单进程允许打开的最大文件描述符数量的限制。cat /proc/PID/limits | grep "open files"
- 硬限制本身也是进程的属性。进程启动时,通常从父进程继承 soft limit 和 hard limit。
- ulimit -n 当前 shell 以及由这个 shell 启动的子进程默认会继承的 fd 限制
- 如何修改soft limit
- 看某个进程当前打开的fd总个数。ls /proc/<PID>/fd | wc -l
- 查看进程具体打开了哪些资源 lsof(list open files) -p <PID>。ls -l /proc/进程PID/fd
- 进程当前打开的 fd 都指向哪些资源,并找出重复最多的资源
文件描述符(file descriptor,简称 fd)本质上就是:操作系统给“已打开资源”分配的一个编号。非负整数
文件描述符(File Descriptor,简称 FD)
是操作系统为了高效管理已被打开的文件或资源,而向应用程序返回的一个非负整数(通常是从 0 开始的数字索引)
文件描述符就是进程访问文件、管道、终端、网络连接等资源时使用的“操作系统编号”
文件描述符就是:某个进程打开资源后,操作系统给这个资源分配的编号。所以判断“是不是占用文件描述符”,你就看:
这个进程有没有打开文件、管道、socket、终端、设备,并且还没关闭?
有,那就占用 fd。
不是“文件自己占用文件描述符”,而是:
某个进程打开了文件、管道、socket、终端之后,操作系统给这个进程分配的一个编号,这个编号就是文件描述符。
可以把文件描述符理解成:
一个进程手里拿着的“资源号码牌”。
不是“文件自己占用文件描述符”,而是:
某个进程打开了文件、管道、socket、终端之后,操作系统给这个进程发了一个编号,这个编号就是文件描述符。
分配编号后,后续操作就是围绕这个编号来的。代码通常不直接用 fd 编号;真正直接使用 fd 编号的是底层系统调用和操作系统内核
进程启动后默认会有3个文件描述符
进程默认有 fd=0、fd=1、fd=2,是为了让每个程序都有统一的输入、正常输出、错误输出通道。
所以程序不需要知道自己是在终端里运行、被管道连接、被重定向到文件,还是在 Docker/Celery 里运行;它只需要读 fd=0,写 fd=1,报错写 fd=2
只有当代码需要操作系统帮你“打开某种外部资源”时,才会分配fd,才会占用文件描述符。
只要你打开了这些资源,就通常会占 fd:
文件
管道
socket 网络连接
终端
设备文件
不是所有情况都是一个操作占一个fd
每个进程都有自己的一张“文件描述符表”,这张表里面记录:fd 号码 -> 这个 fd 指向什么资源。文件描述符不是全局唯一的,而是进程内部的编号。
文件描述符是进程级别的资源编号。
可以这样记:
每个进程有一张 fd 表;
fd 只是这张表里的编号;
不同进程可以有相同的 fd 编号;
线程共享所属进程的 fd 表;
fork / subprocess 创建子进程时,子进程可能继承父进程的一些 fd。
子进程 clangd 打开的文件,不会算到父进程 里面
Linux 里每个进程都有自己的 FD 表。
比如
PID 8 Celery worker child
PID 79001 mcp-language-server
PID 79016 clangd
它们各自有自己的:
/proc/8/fd
/proc/79001/fd
/proc/79016/fd
所以你看到:
ls /proc/8/fd | wc -l
1024
说明是 Celery 子进程 PID 8 自己的 FD 满了。
如果是 clangd 打开太多文件,应该看:
ls /proc/79016/fd | wc -l
而不是看 /proc/8/fd。
为什么说fd是进程才有?文件描述符是操作系统为了管理某个进程打开的资源而分配的编号
子进程会继承父进程的一些 fd
每个进程都有自己的 FD 表
但子进程刚创建出来时,系统会把父进程 FD 表里的部分条目“复制一份”给子进程
所以不是“父子进程共用同一张 FD 表”,而是:
父进程有自己的 FD 表
子进程也有自己的 FD 表
但是子进程刚出生时,它的 FD 表内容,很多是从父进程那里复制来的
父子进程的 fd 表不是同一张表,父进程和子进程都有自己的 fd 表
子进程启动后,也有自己的 fd=0、fd=1、fd=2。默认情况下,它们连接到和父进程一样的终端;只有你显式写了 PIPE、重定向文件等,才会把它们改接到别的地方
线程有没有文件描述符?线程属于同一个进程,所以同一个进程内的多个线程共享同一张 fd 表。
一切皆文件。Linux 把很多资源统一成“可以 open/read/write/close 的对象”。
在 Linux 和 Unix
操作系统中,有一个非常核心的哲学叫做
“一切皆文件”。这意味着不仅普通的文本文件是文件,网络连接(Socket)、管道(Pipe)、标准输入输出、甚至硬件设备,在系统底层都被抽象成了“文件”。而
FD 就是访问这些资源的“钥匙”。
/dev/null 理解成:
Linux 提供的一个“特殊文件入口”,你往里面写东西,系统直接丢掉。
它不是普通的 .txt 文件,不会真的保存 hello。
操作 fd,就是操作 fd 指向的资源
fd 就是进程访问资源的编号;操作 fd,就是让操作系统根据这个编号找到背后的文件、终端、管道、socket 等资源,然后对它执行读、写、关闭等操作
单进程允许打开的最大文件描述符数量的限制。cat /proc/PID/limits | grep “open files”
“单进程允许打开的最大文件描述符数量限制”就是:一个进程最多能同时打开多少个文件、管道、socket、设备等资源
硬限制本身也是进程的属性。进程启动时,通常从父进程继承 soft limit 和 hard limit。
ulimit -n 当前 shell 以及由这个 shell 启动的子进程默认会继承的 fd 限制
如何修改soft limit
看某个进程当前打开的fd总个数。ls /proc//fd | wc -l
先列出这个进程所有 fd
再统计有多少行
得到这个进程当前打开了多少个 fd
查看进程具体打开了哪些资源 lsof(list open files) -p 。ls -l /proc/进程PID/fd
fd 表示这个进程打开了某个资源;后面的字母表示这个 fd 对这个资源的访问方式。即这个资源是以什么方式被打开的:读、写、读写。
进程当前打开的 fd 都指向哪些资源,并找出重复最多的资源
遍历 PID=513811 进程的所有 fd,查看每个 fd 指向哪里,把相同目标归类统计,按数量从多到少排序,显示前 50 个
forfdin/proc/513811/fd/*;doreadlink"$fd";done|sort|uniq-c|sort-nr|head-50