メインコンテンツへスキップ
  1. ノート/
  2. Linux/

Linux ファイルディスクリプタ fd

·3999 文字·8 分· loading · loading · · ·
ICE345
著者
ICE345
CS Student | System | Linux | OCaml
この記事は中国語版をもとにした日本語版メモです。コマンド、コード、数式、画像リンクは原文の意味を壊さないように保持し、説明文と見出しを日本語向けに整理しています。

作者: 杰克小麻雀
原文链接: https://blog.csdn.net/yushuaigee/article/details/107883964

1、从一个最常见的例子说起
#

在使用Linux的过程中, 我们平时经常看到下面这样的用法:

1echo log > /dev/null 2>&1
  • > :表示将输出结果重定向到哪里,たとえば:echo “123” > /home/123.txt
  • /dev/null :表示空设备ファイル

そのため echo log > /dev/null 表示把日志输出到空ファイル设备,也つまり将打印信息丢弃掉,屏幕上什么也不显示。

  • 1 :表示stdout标准输出
  • 2 :表示stderr标准错误
  • & :表示等同于的意思

そのため 2>&1 表示2的输出重定向等同于1,也つまり标准错误输出重定向到标准输出。なぜなら前面标准输出已经重定向到了空设备ファイル,そのため标准错误输出也重定向到空设备ファイル。

この用法平时很常见,重点是为什么这里是用 21 ,不是3456什么的呢?这要从 Linux 中的ファイル描述符说起。

2、Linux中的ファイル描述符(file descriptor)
#

我们知道在Linuxシステム中一切皆できます看成是ファイル,ファイル又可分为:普通ファイル、ディレクトリファイル、链接ファイル和设备ファイル。在操作这些所谓的ファイル的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。そのためLinux中规定每一个ファイル对应一个索引,这样要操作ファイル的时候,我们直接找到索引就できます对其进行操作了。

ファイル描述符(file descriptor)つまり内核为了高效管理这些已经被打开的ファイル所作成的索引,其是一个非负整数(通常是小整数),用于指代被打开的ファイル,所有执行I/O操作的システム调用都通过ファイル描述符来实现。同时还规定システム刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着もし此时去打开一个新的ファイル,它的ファイル描述符会是3,再打开一个ファイルファイル描述符つまり4……

Linux内核对所有打开的ファイル有一个ファイル描述符表格,里面存储了每个ファイル描述符作为索引与一个打开ファイル相对应的关系,简单理解つまり下图这样一个数组,ファイル描述符(索引)つまりファイル描述符表この数组的下标,数组的内容つまり指向一个个打开的ファイル的指针。

20200813212007131.png

上面只是简单理解,实际上关于ファイル描述符,Linux内核维护了3个数据结构

  • プロセス级的ファイル描述符表
  • システム级的打开ファイル描述符表
  • ファイルシステム的i-node表

一个 Linux プロセス启动后,会在内核空间中作成一个 PCB 控制块,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之间的关系以下图:

MDFINDVTMTMuZ2lm
  • 在プロセス A 中,ファイル描述符 1 和 20 都指向了同一个打开ファイル表项,标号为 23(指向了打开ファイル表中下标为 23 的数组元素),这可能是通过调用 dup()、dup2()、fcntl() 或者对同一个ファイル多次调用了 open() 函数形成的。
  • プロセス A 的ファイル描述符 2 和プロセス B 的ファイル描述符 2 都指向了同一个ファイル,这可能是在调用 fork() 后出现的(即プロセス 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 プロセス所使用的ファイル描述符列表。

20200813200300316.png

/dev/pts是リモート登陆(telnet,ssh等)后作成的控制台设备ファイル所在的ディレクトリ。なぜなら我是通过Xshellリモート登录的,そのため标准输入0,标准输出1,标准错误2的ファイル描述符都指向虚拟终端控制台 /dev/pts/6 。 再看下面是新打开的 test.py 的ファイル描述符,竟然是4,说好的从3开始呢?

この我也困扰了好久,查了各种资料,终于在一个大佬的帮助下在一个论坛找到原因,有时候中文查不到还是要试试英文搜索啊。なぜならvim这种编辑器的原理是先打开源ファイル并拷贝,その後关闭源ファイル再打开自己的副本,変更完ファイル保存的时候直接将副本重命名覆盖源ファイル。そのため打开源ファイル的时候用的ファイル描述符3,その後打开自己的副本是时候就该用ファイル描述符4了,その後关闭源ファイル,ファイル描述符3就被释放了,我们確認的时候就只剩下了4,这里它指向的是vim作成的副本ファイル。这里只是说个大概意思,具体深究要去深入了解一下 vim的实现原理——奥尔特星云大使,下面是当时我看到的论坛上的资料截图,链接在这:StackOverFlow

20200813204121774.png

もし不相信できます试一试别的プロセス,比如 tail。

在Linux上用 tail -f test.py 打开一个ファイル,保持打开状态,再新打开一个新的shell,输入コマンドpidof tail 获取tailプロセス的pid号,その後ll /proc/$pid/fd確認tailプロセス所使用的ファイル描述符列表,できます看到ファイル描述符确实是从3开始使用的。tail不是编辑器不存在変更ファイル的情况,そのため直接ファイル描述符直接打开的源ファイル。实际上できます使用 ll /proc/$pid/fdコマンド获取当前実行的任意プロセス的ファイル描述符使用情况。

2020081321574073.png

4、C语言中ファイル描述符的使用
#

C语言中できます通过 open 函数返回一个ファイル的ファイル描述符,まず作成一个 test.py ファイル用于打开,その後作成一个 test.c ファイル,输入下面コード保存。 コンパイル后执行,发现新打开ファイル的ファイル描述符是3

1 2 3 4 5 6 7 8 9 10 11 12 13#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); close(fd); return 0; }
20200814150406203.png

5、Python中ファイル描述符的使用
#

Python中通过 sys 模块封装了标准输入、标准输出和错误输出。通过我们平时常用的内建函数 open できます获取一个ファイル的ファイル描述符,まず作成一个 test.py ファイル用于打开,その後作成一个 test2.py ファイル,输入下面コード保存。 执行,发现新打开ファイル的ファイル描述符是3

1 2 3 4 5 6 7 8import sys print('stdin fd = ', sys.stdin.fileno()) print('stdout fd = ', sys.stdout.fileno()) print('stderr fd = ', sys.stderr.fileno()) with open("test.py", "w") as f: print('test.py fd = ', f.fileno())
20200814153527764

6、Linux設定システム最大打开ファイル描述符个数
#

(1)システム级限制

理论上システムメモリ有多少就できます打开多少的ファイル描述符,但是在实际中内核是会做相应的处理,一般最大打开ファイル数会是システムメモリ的10%(以KB来计算),称之为システム级限制。この数字できます通过 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max コマンド確認。

20200814163652927

更改システム级限制有临时更改和永久更改两种方式:

  • 临时更改:session断开或者システム重启后会恢复原来的设置值。使用コマンド sysctl -w fs.file-max=xxxx,其中xxxxつまり要设置的数字。
  • 永久更改:vim编辑 /etc/sysctl.conf ファイル,在后面追加 fs.file-max=xxxx,其中xxxxつまり要设置的数字。保存退出后还要使用sysctl -p コマンド使其生效。

(2)用户级限制

同时为了控制每个プロセス消耗的ファイル资源,内核也会对单个プロセス最大打开ファイル数做默认限制,即用户级限制。32位システム默认值一般是1024,64位システム默认值一般是65535,できます使用 ulimit -n コマンド確認。

20200814164901908

7、参考链接
#

  1. 每天进步一点点——Linux中的ファイル描述符与打开ファイル之间的关系——cywosp
  2. Linuxファイル描述符到底是什么?——C语言中文网
  3. 句柄和ファイル描述符(FD)——阳光丶不锈
  4. 带你破案:ファイル描述符到底是什么?——vran
  5. Linux設定调优:最大打开ファイル描述符个数——Idea Buffer
  6. 変更Linuxシステム下的最大ファイル描述符限制——BlueguyChui