在这之前我理解中的文件描述符 (fd) 一直认为就是一个进程创建的虚拟文件,然后就是大家口口相传的 fd 是属于一个进程的一个有序标识,当进程打开一个文件时系统就会为当前进程分配一个文件描述符。

然后这周末在做一个文件接口的时候,就想着能不能直接利用 fd 来对数据进行传递,这样我就不用考虑清理缓存(错误理解:按照自己之前的理解就是创建一个虚拟文件后通过 fd 进行 write/read,当进程结束后反正系统会将虚拟文件中的数据一并回收清理掉的)了,于是艰难的组织自己混乱的概念就问了夏老大几个如何使用文件描述符的问题。然后夏老大说你这概念都是混乱的,先自己写个 demo 了解一下文件描述符如何使用,这周有空给我讲讲这块的知识(这篇博客就是在学习之后根据个人理解总结的笔记,肯定还有很多错误之处还请小伙伴们看到能够指出让我纠正)。

其实关于文件描述符几个大家耳熟能详的基本概念我还是能够理解的

1,文件描述符是一个有序的非负整数的索引值

2,每个进程有自己独立的文件描述符列表

3,多个不同的文件描述符可以链接相同文件

4,当之前的文件被关闭之后文件描述符可能会被后续打开的文件重复使用

5,可以利用文件描述符进行读写数据

之前的我对文件描述符的理解就到这里了…然后昨天刚过去上班夏老大就问我 demo 写的怎么样了,对文件描述符有没有不一样的理解了(emmm…好吧我坦白我周末玩去了,忘记写了…)。然后夏老大就说周三可以给我讲讲这块的知识让我今晚可以自己先把 demo 弄一下,于是乎我回去之后折腾了半天 golang 的文件 API ,然后我发现文件描述符应该不是什么虚拟文件(当然其实是可以做到类似效果后面说),每个文件描述符其实对应的就是一个具体文件,并且我之前还在纠结 write 到 fd 里的数据是存在硬盘还是内存中,实际上是在硬盘(cache)中…

关于文件描述符

首先文件描述符确实是不同进程之间的文件描述符都会有自己的文件描述符列表,当进程结束后文件描述符列表也会销毁。

文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行 I/O 操作的系统调用都通过文件描述符来实现。在 Unix 进程中,文件描述符 0 表示 stdin 标准输入,1 表示 stdout 标准输出,2 表示 stderr 标准错误,也就是进程自己的文件描述符默认从 3 开始

在 Linux 上,可以在路径 /proc/PID/fd/ 下访问进程中打开的文件描述符集,其中 PID 是进程标识符。文件描述符 /proc/PID/fd/0 为 stdin , /proc/PID/fd/1 为 stdout , /proc/PID/fd/2 为 stderr 。作为这些的快捷方式,任何正在运行的进程还可以通过文件夹 /proc/self/fd 和 /dev/fd 访问自己的文件描述符。(我目前还不知道在 MacOS 上怎么看进程的文件描述符)

当然,文件描述符也是有最大范围限制的,毕竟系统不可能让进程能够打开无限多的文件。在 linux 中理论上系统内存有多少就可以打开多少的文件描述符,系统级限制指的是在实际中内核是会做相应的处理,一般最大打开文件数会是系统内存的 10%(以 KB 来计算)。这个数字可以通过 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max 命令查看。用户级限制是指为了控制每个进程消耗的文件资源,内核也会对单个进程最大打开文件数做默认限制。系统默认值一般是 1024,可以使用 ulimit -n 命令查看。

当然文件描述符不仅只能打开文件,在类 Unix 系统中,文件描述符可以引用文件系统中命名的任何 Unix 文件类型。除了常规文件之外,这还包括目录、块和字符设备(也称为“特殊文件”)、Unix 域套接字和命名管道。文件描述符还可以引用文件系统中通常不存在的其他对象,例如匿名管道和网络套接字。

利用  man 2 open 查看文档可以看到,进程在调用 open 系统调用后会得到一个 fd,调用 write/read 就能够对文件进行读写操作了(貌似在系统调用层只能使用 fd 对文件进行读写?)。

夏老大还提了一下一个骚操作,将文件打开之后获取到 fd 然后将文件删除,但是此时还是可以利用 fd 进行 write/read 这样别的进程就无法对这个文件进行操作了,进程销毁后这个文件自然也不会写入磁盘,其实就实现了我之前想要的“虚拟文件”效果,哈哈哈哈哈哈。

今天还学到一招就是 man 原来还有文档类型的,以下输出来自 man man ,直接使用 man 1 的话就是查看命令手册,使用 man 2 能够看到系统调用文档,使用 man 3 可以看到一些 C 库的文档…

NAME
     man, apropos, whatis – display online manual documentation pages

SYNOPSIS
     man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect] [-m arch[:machine]] [-p [eprtv]] [mansect] page ...

     man -f [-d] [-M manpath] [-P pager] [-S mansect] keyword ...
     whatis [-d] [-s mansect] keyword ...

     man -k [-d] [-M manpath] [-P pager] [-S mansect] keyword ...
     apropos [-d] [-s mansect] keyword ...

DESCRIPTION
     The man utility finds and displays online manual documentation pages.  If mansect is provided, man restricts the search to the specific section of the manual.

     The sections of the manual are:
           1.   General Commands Manual
           2.   System Calls Manual
           3.   Library Functions Manual
           4.   Kernel Interfaces Manual
           5.   File Formats Manual
           6.   Games Manual
           7.   Miscellaneous Information Manual
           8.   System Manager's Manual
           9.   Kernel Developer's Manual

其实这次的收获远不止如此,还对 I/O 以及系统调用有见识到一些东西,就感觉一下子看到另外一个窗口一样,但是时间有限就先潦草写这么多,后面有空再分享(咕咕咕)

最后,夏老大说在调用别人的 API 时也可以看看人家最终是使用哪些系统调用去实现这个功能的,当对于这些系统调用熟悉后排查文件就能够清晰的知道问题出在哪一层了。

参考链接

彻底弄懂 Linux 下的文件描述符(fd) https://yushuaige.github.io/2020/08/14/%E5%BD%BB%E5%BA%95%E5%BC%84%E6%87%82%20Linux%20%E4%B8%8B%E7%9A%84%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%EF%BC%88fd%EF%BC%89/

File descriptor 文件描述符(维基百科) https://en.wikipedia.org/wiki/File_descriptor