目录

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

2、Linux中的文件描述符(file descriptor)

3、Linux上打开文件举例

4、C语言中文件描述符的使用

5、Python中文件描述符的使用

6、Linux配置系统最大打开文件描述符个数

7、参考链接


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

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

echo log > /dev/null 2>&1
  •  :表示将输出结果重定向到哪里,例如:echo "123" > /home/123.txt
  • /dev/null :表示空设备文件

所以 echo log > /dev/null 表示把日志输出到空文件设备,也就是将打印信息丢弃掉,屏幕上什么也不显示。

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

所以  2>&1 表示2的输出重定向等同于1,也就是标准错误输出重定向到标准输出。因为前面标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

这个用法平时很常见,重点是为什么这里是用 和 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 控制块,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() 后出现的(即进程 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创建的副本文件。这里只是说个大概意思,具体深究要去深入了解一下 vim的实现原理——奥尔特星云大使,下面是当时我看到的论坛上的资料截图,链接在这:StackOverFlow。

如果不相信可以试一试别的进程,比如 tail。

在Linux上用 tail -f test.py 打开一个文件,保持打开状态,再新打开一个新的shell,输入命令pidof tail 获取tail进程的pid号,然后 ll  /proc/$pid/fd 查看tail进程所使用的文件描述符列表,可以看到文件描述符确实是从3开始使用的。tail不是编辑器不存在修改文件的情况,所以直接文件描述符直接打开的源文件。实际上可以使用 ll  /proc/$pid/fd 命令获取当前运行的任意进程的文件描述符使用情况。

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);close(fd);return 0;
}

5、Python中文件描述符的使用

Python中通过 sys 模块封装了标准输入、标准输出和错误输出。通过我们平时常用的内建函数 open 可以获取一个文件的文件描述符,首先创建一个 test.py 文件用于打开,然后创建一个 test2.py 文件,输入下面代码保存。 执行,发现新打开文件的文件描述符是3

import sysprint('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())

6、Linux配置系统最大打开文件描述符个数

(1)系统级限制

理论上系统内存有多少就可以打开多少的文件描述符,但是在实际中内核是会做相应的处理,一般最大打开文件数会是系统内存的10%(以KB来计算),称之为系统级限制。这个数字可以通过 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max 命令查看。

更改系统级限制有临时更改和永久更改两种方式:

  • 临时更改: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 命令查看。

更改用户级限制也有临时更改和永久更改两种方式:

  • 临时更改:session断开或者系统重启后会恢复原来的设置值。使用命令 ulimit -SHn xxxx 命令来修改,其中xxxx就是要设置的数字。

  • 永久更改:vim编辑 /etc/security/limits.conf 文件,修改其中的 hard nofile xxxxsoft nofile xxxx,其中xxxx就是要设置的数字。保存后退出。关于hard和soft的区别,参照下面参考链接中的第5个。

7、参考链接

  1. 每天进步一点点——Linux中的文件描述符与打开文件之间的关系——cywosp
  2. Linux文件描述符到底是什么?——C语言中文网
  3. 句柄和文件描述符(FD)——阳光丶不锈
  4. 带你破案:文件描述符到底是什么?——vran
  5. Linux配置调优:最大打开文件描述符个数——Idea Buffer
  6. 修改Linux系统下的最大文件描述符限制——BlueguyChui

彻底弄懂 Linux 下的文件描述符(fd)相关推荐

  1. Linux下利用文件描述符恢复的成功失败实验

    数据误删除是作为初级运维人员常常遇到的"低级错误",一些有经验的老手有时也在疲劳.不冷静的情况下"马失前蹄".一旦误删除数据文件,尽快采用影响最小.最迅速的手段 ...

  2. Linux下的文件描述符

    引文 在 Linux 的世界里,一切设备皆文件.对文件的操作都是通过文件描述符(fd)来进行的. Linux 中有7种文件类型: 文件类型 文件类型描述 符号 普通文件 最常使用的一类文件,其特点是不 ...

  3. linux 下修改文件描述符限制

    修改linux的最大文件句柄数限制 对于一般的应用来说(象Apache.系统进程)1024完全足够使用.但是如何象squid.mysql.java等单进程处理大量请求的应用来说就有点捉襟见肘了.如果单 ...

  4. 一文弄懂Linux下五种IO模型

    Linux下主要的IO主要分为:阻塞IO(Blocking IO),非阻塞IO(Non-blocking IO),同步IO(Sync IO)和异步IO(Async IO). 同步:调用端会一直等待服务 ...

  5. linux文件描述符与标识符,文件描述符fd

    这里以问答的方式来讨论这个问题: 1. 文件描述符 fd 和文件指针 FILE *的关系? 文件描述符是什么?我们知道每一个进程都有一个自己的PCB(进程控制块),进程控制块的结构是: struct ...

  6. linux存储--文件描述符fd与FILE结构体(二)

    文件描述符fd 对于linux而言,所有对设备(对于linux而言,一切皆文件)和文件的操作都使用文件描述符来进行的. 文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表. ...

  7. Linux中的文件描述符与打开文件之间的关系

    1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是 ...

  8. Linux C:文件描述符、IO重定向、恢复标准输入输出

    目录 一.文件描述符 二.IO重定向 三.重定向回终端.伪终端 四.恢复标准输入输出 一.文件描述符 在Linux中,文件描述符是一个非负整数的数据类型.是FILE结构体中的一个成员属性. 每打开或者 ...

  9. linux 文件指针,Linux中文件描述符fd与文件指针FILE*互相转换实例解析

    本文研究的主要是Linux中文件描述符fd与文件指针FILE*互相转换的相关内容,具体介绍如下. 1.文件描述符fd的定义:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进 ...

  10. linux文件描述符有什么用,linux上的文件描述符3有什么特别之处?

    我的工作,那将在Linux和Mac OS X上运行的服务器应用程序它是这样的:linux上的文件描述符3有什么特别之处? 启动主要应用 控制器进程的叉 调用lock_down()在控制过程中 再次叉终 ...

最新文章

  1. shell实例第6讲:检查主机存活状态
  2. JAndFix: 基于Java实现的Android实时热修复方案
  3. java修改字节码技术,Javassist修改class,ASM修改class
  4. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试...
  5. 一篇男人必看的创业文章。(人活着不能没有钱,但是活着却不能只为了钱)...
  6. android string 去掉斜杠,Android – PATH中的改装和斜杠字符
  7. 蚂蚁的开放:想办法摸到10米的篮筐
  8. Python 分析谁才是「权利的游戏」真正的主角?
  9. 【更新】PDF控件Spire.PDF 3.9.538发布 | 附下载
  10. Linux服务器校准时间
  11. 网络多人聊天室1.0 群聊
  12. JavaScript复习,this指向、原型链、变量提升、作用域、闭包
  13. UltraCompare比较文件时出现UTF8_ConvertorForMac无转换符号!
  14. SWUST OJ533你的QQ多少级了?
  15. oCPC和oCPM的本质区别是什么?
  16. 360cdn能挡住cc攻击_人人用得起的CDN加速服务,可防御CC攻击
  17. win32com处理excel数据透视表格式
  18. 感谢一路相伴的朋友们!我的个人工作室招人啦!
  19. 判断浏览器版本是否ie8,ie9,ie10,ie11并提示升级
  20. 盘点百位富豪读过的大学:土鳖比海龟更有优势

热门文章

  1. C#正则验证车牌、新能源车牌
  2. 计算机网络之五:基本器件(网卡,集线器,交换机,路由器)
  3. VirtualBox下配置 Ubantu18.4 总纲领
  4. MongoDB中updateOne的正常使用
  5. 支付网关和api网关_什么是支付网关
  6. java 调用nc查询引擎_用友NC系统查询引擎应用心得
  7. java模拟选课_模拟学生选课系统的练习
  8. Weak Pointer
  9. 银行wifi认证登录怎么样
  10. idea配置php开发环境以及配置debug