彻底弄懂Linux下的⽂件描述符(fd)
1. 从⼀个最常见的例⼦说起
在使⽤Linux的过程中,我们平时经常看到下⾯这样的⽤法:
echo log > /dev/null 2>&1
:表⽰将输出结果重定向到哪⾥,例如:echo "123" > /
/dev/null :表⽰空设备⽂件
所以 echo log > /dev/null 表⽰把⽇志输出到空⽂件设备,也就是将打印信息丢弃掉,屏幕上什么也不显⽰。
1 :表⽰stdout标准输出
2 :表⽰stderr标准错误
& :表⽰等同于的意思
所以 2>&1 表⽰2的输出重定向等同于1,也就是标准错误输出重定向到标准输出。因为前⾯标准输出已经重定向到了空设备⽂件,所以标准错误输出也重定向到空设备⽂件。
这个⽤法平时很常见,重点是为什么这⾥是⽤ 2 和 1 ,不是3456什么的呢?这要从 Linux 中的⽂件描述符说起。
2. Linux中的⽂件描述符(file descriptor)
我们知道在Linux系统中⼀切皆可以看成是⽂件,⽂件⼜可分为:普通⽂件、⽬录⽂件、链接⽂件和设备⽂件。在操作这些所谓的⽂件的时候,我们每操作⼀次就⼀次名字,这会耗费⼤量的时间和效率。所以Linux中规定每⼀个⽂件对应⼀个索引,这样要操作⽂件的时候,我们直接到索引就可以对其进⾏操作了。⽂件描述符(file descriptor)就是内核为了⾼效管理这些已经被打开的⽂件所创建的索引,其是⼀个⾮负整数(通常是⼩整数),⽤于指代被打开的⽂件,所有执⾏I/O操作的系统调⽤都通过⽂件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输⼊,1是标准输出,2是标准错误。这意味着如果此时去打开⼀个新的⽂件,它的⽂件描述符会是3,再打开⼀个⽂件⽂件描述符就是4......
Linux内核对所有打开的⽂件有⼀个⽂件描述符表格,⾥⾯存储了每个⽂件描述符作为索引与⼀个打开⽂件相对应的关系,简单理解就是下图这样⼀个数组,⽂件描述符(索引)就是⽂件描述符表这个数组的下标,数组的内容就是指向⼀个个打开的⽂件的指针。
上⾯只是简单理解,实际上关于⽂件描述符,Linux内核维护了3个数据结构:
进程级的⽂件描述符表
系统级的打开⽂件描述符表
⽂件系统的i-node表
⼀个 Linux 进程启动后,会在内核空间中创建⼀个 PCB(Process Control Block) 控制块,PCB 内部有⼀个⽂件描述符表(File descriptor table),记录着当前进程所有可⽤的⽂件描述符,也即当前进程所有打开的⽂件。进程级的描述符表的每⼀条记录了单个进程所使⽤的⽂件描述符的相关信息,进程之间相互独⽴,⼀个进程使⽤了⽂件描述符3,另⼀个进程也可以⽤3。除了进程级的⽂件描述符表,系统还需要维护另外两张表:打开⽂件表、i-node 表。这两张表存储了每个打开⽂件的打开⽂件句柄(open file handle)。⼀个打开⽂件句柄存储了与⼀个打开⽂件相关的全部信息。
系统级的打开⽂件描述符表:
当前⽂件偏移量(调⽤read()和write()时更新,或使⽤lseek()直接修改)
打开⽂件时的标识(open()的flags参数)
⽂件访问模式(如调⽤open()时所设置的只读模式、只写模式或读写模式)
与信号驱动相关的设置
对该⽂件i-node对象的引⽤,即i-node 表指针
⽂件系统的i-node表:
⽂件类型(例如:常规⽂件、套接字或FIFO)和访问权限
⼀个指针,指向该⽂件所持有的锁列表
⽂件的各种属性,包括⽂件⼤⼩以及与不同类型操作相关的时间戳
⽂件描述符、打开的⽂件句柄以及i-node之间的关系如下图:
在进程 A 中,⽂件描述符 1 和 20 都指向了同⼀个打开⽂件表项,标号为 23(指向了打开⽂件表中下标为 23 的数组元素),这可能是通过调⽤ dup()、dup2()、fcntl() 或者对同⼀个⽂件多次调⽤了 open() 函数形成的。
进程 A 的⽂件描述符 2 和进程 B 的⽂件描述符 2 都指向了同⼀个⽂件,这可能是在调⽤ fork() 后出现
常见vim编辑器命令模式下的命令有的(即进程 A、B 是⽗⼦进程关系),或者是不同的进程独⾃去调⽤ open() 函数打开了同⼀个⽂件,此时进程内部的描述符正好分配到与其他进程打开该⽂件的描述符⼀样。
进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开⽂件表项,但这些表项均指向 i-node 表的同⼀个条⽬(标号为 1976);换⾔之,它们指向了同⼀个⽂件。发⽣这种情况是因为每个进程各⾃对同⼀个⽂件发起了 open() 调⽤。同⼀个进程两次打开同⼀个⽂件,也会发⽣类似情况。
这就说明:同⼀个进程的不同⽂件描述符可以指向同⼀个⽂件;不同进程可以拥有相同的⽂件描述符;不同进程的同⼀个⽂件描述符可以指向不同的⽂件(⼀般也是这样,除了 0、1、2 这三个特殊的⽂件);不同进程的不同⽂件描述符也可以指向同⼀个⽂件。
3. Linux上打开⽂件举例
⽐如在Linux上⽤ vim test.py 打开⼀个⽂件,保持打开状态,再新打开⼀个新的shell,输⼊命令pidof vim 获取vim进程的pid号,然后 ll /proc/$pid/fd 查看vim 进程所使⽤的⽂件描述符列表。
/dev/pts是远程登陆(telnet,ssh等)后创建的控制台设备⽂件所在的⽬录。因为我是通过Xshell远程登录的,所以标准输⼊0,标准输出1,标准错误2的⽂件描述符都指向虚拟终端控制台 /dev/pts/6 。再看下⾯是新打开的 test.py 的⽂件描述符,竟然是4,说好的从3开始呢?
这个我也困扰了好久,查了各种资料,终于在⼀个⼤佬的帮助下在⼀个论坛到原因,有时候中⽂查不到还是要试试英⽂搜索啊。因为vim这种编辑器的原理是先打开源⽂件并拷贝,然后关闭源⽂件再打开⾃⼰的副本,修改完⽂件保存的时候直接将副本重命名覆盖源⽂件。所以打开源⽂件的时候⽤的⽂件描述符3,然后打开⾃⼰的副本是时候就该⽤⽂件描述符4了,然后关闭源⽂件,⽂件描述符3就被释放了,我们查看的时候就只剩下了4,这⾥它指向的是vim创建的副本⽂件。这⾥只是说个⼤概意思,具体深究要去深⼊了解⼀下,下⾯是当时我看到的论坛上的资料截图,链接在这:。
4. C语⾔中⽂件描述符的使⽤
C语⾔中可以通过 open 函数返回⼀个⽂件的⽂件描述符,⾸先创建⼀个 test.py ⽂件⽤于打开,然后创建⼀个 test.c ⽂件,输⼊下⾯代码保存。编译后执⾏,发现新打开⽂件的⽂件描述符是3。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
int fd = open("test.py", O_RDONLY);
if (fd == -1) {
return -1;
}
printf("test.py fd = %d \n", fd); // test.py fd = 3
close(fd);
return 0;
}
5. Python中⽂件描述符的使⽤
Python中通过 sys 模块封装了标准输⼊、标准输出和错误输出。通过我们平时常⽤的内建函数 open 可以获取⼀个⽂件的⽂件描述符,⾸先创建⼀个 test.py ⽂
件⽤于打开,然后创建⼀个 test2.py ⽂件,输⼊下⾯代码保存。执⾏,发现新打开⽂件的⽂件描述符是3。
import sys
print('stdin fd = ', sys.stdin.fileno()) // stdin fd = 0
print('stdout fd = ', sys.stdout.fileno()) // stdout fd = 1
print('stderr fd = ', sys.stderr.fileno()) // stderr fd = 2
with open("test.py", "w") as f:
print('test.py fd = ', f.fileno()) // test.py fd = 3
6. Linux配置系统最⼤打开⽂件描述符个数
系统级限制
理论上系统内存有多少就可以打开多少的⽂件描述符,但是在实际中内核是会做相应的处理,⼀般最⼤打开⽂件数会是系统内存的10%(以KB来计算),称之为系统级限制。这个数字可以通过 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max 命令查看。
更改系统级限制有临时更改和永久更改两种⽅式:
临时更改:session断开或者系统重启后会恢复原来的设置值。使⽤命令 sysctl -w fs.file-max=xxxx,其中xxxx就是要设置的数字。
永久更改:vim编辑 /f ⽂件,在后⾯添加 fs.file-max=xxxx,其中xxxx就是要设置的数字。保存退出后还要使⽤sysctl -p 命令使其⽣效。
⽤户级限制
同时为了控制每个进程消耗的⽂件资源,内核也会对单个进程最⼤打开⽂件数做默认限制,即⽤户级限制。32位系统默认值⼀般是1024,64位系统默认值⼀般是65535,可以使⽤ ulimit -n 命令查看。
更改⽤户级限制也有临时更改和永久更改两种⽅式:
临时更改:session断开或者系统重启后会恢复原来的设置值。使⽤命令 ulimit -SHn xxxx 命令来修改,其中xxxx就是要设置的数字。
永久更改:vim编辑 /etc/f ⽂件,修改其中的 hard nofile xxxx 和 soft nofile xxxx,其中xxxx就是要设置的数字。保存后退出。关于hard和soft 的区别,参照下⾯参考链接中的第5个。
7. 参考链接
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论