「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
文件描述符的本质
文件描述符本质上是内核向进程提供的一种凭据,内核为进程维护了一张文件描述符表用来记录文件描述符和文件的对应关系,并且记录了读写模式相关的信息。
在Linux中,当每个进程被启动的时候,内核都会为其在/proc
目录下创建一个虚拟目录,目录名称为进程的PID
。在该目录下有一个名为fd
的文件夹,记录了进程打开的文件。
首先我们启动一个Python解释器
然后执行ps -ef | grep python3
查出Python解释器进程的PID是7072, 最后进入到
/proc/7072/fd
可以看到,该目录中由三个文件, 这代表着进程已经打开三个文件, 文件描述符分别是0
,1
,2
, 这些文件是操作系统都为进程分配三个文件描述符, 分别是:
- 标准输入, fd=0。
- 标准输出, fd=1。
- 标准错误输出, fd=2。
执行一下ls -l
可以看到,这三个文件都链接到了/dev/pts/0
设备上,该设备代表了用户的SSH终端.
本文测试过程中均使用SSH连接到服务器上进行测试
既然是文件,意味着我们可以像操作文件一样操作它们。(这也是Linux的有趣之处)
我们标准输入文件写入数据print('Hello from kovogo')
,再切换到另外一个窗口看一下
可以看到,另一窗口检查到了我们的输入.
从以上,我们可以观察到文件描述符从0开始计数, 并且逐个增长,借助这个规律一些功能可以被巧妙的实现,当然也可以回答一些疑问,但是在这之前还是让我们先验证一下这个机制。
我们先创建三个文件,然后再按顺序打开它们
根据此前的推测
1.log
的文件描述符应该是3
, 2.log
的文件描述符应该4
, 3.log
的文件描述符应该是5
,进入该进程的文件描述符目录,观察可以看到
select是怎么实现的? 为什么能监文件描述符的数量比epoll少?
经过上文的分析,我们已经知道文件描述符从0开始递增,并且不间断,select
系统调用就利用了这个机制,其通过一个位图来存储要监控的文件描述符。那么什么是位图呢?
我们知道一个字节有8个位组成,当我们对这些比特位赋予了一定信息之后他就成了位图, 位图通常用于判断一个元素是否在集合中。
假设,给定一个只包含0至15的数组,我们需要对该数组进行去重,仅输出不重复的元素,那么除了使用Map来保存出现过的元素,我们还可以使用位图来处理。
如下所示,以下是一个2个字节的位图。
假设要判断一个元素是否在位图中,我们只需要:
- 如果你使用short进行存储位图
bool exists = ((1 << elem) & bitmap) > 0
复制代码
- 如果你使用byte进行存储位图
首先,要定位该元素可能会被存储在哪个字节, 直接对8进行整除即可, 然后定位该元素位于哪个比特位
int byte_pos = elem / 8;
int elem_pos = elem % 8;
bool exists = (1 << elem_pos) & bitmap[byte_pos]) > 0
复制代码
简单了解过位图之后,你就知道为啥select只能监控有限数量的描述符了,因为select存储文件描述符的位图是的大小是有限制,并且由于文件描述符是递增的所以该位图基本上不会有大量的比特位被浪费掉,但是,其位图大小是有限制。
我们简单调用一下sizeof
看一下位图的大小, select.h
中该位图的结构体类型名为fd_set.
结合上图可以知道, 我的机子上可以最大监控8 * 128 = 1024
个文件描述符,最大能监控的文件描述符值是1024
,当然你可以通过编译内核来调整fd_set
的大小。
小声bibi: 说实话我还没深入到这种地步
近期评论