file descriptor
理解Linux的file descriptor(文件描述符) 我们知道在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。所以Linux中规定每一个文件对应一个索引,这样要操作文件的时候,我们直接找到索引就可以对其进行操作了。 文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4…… Linux内核对所有打开的文件有一个文件描述符表格,里面存储了每个文件描述符作为索引与一个打开文件相对应的关系,简单理解就是下图这样一个数组,文件描述符(索引)就是文件描述符表这个数组的下标,数组的内容就是指向一个个打开的文件的指针。 文件描述符指向了由系统内核维护的一个file table中的某个条目(entry)。这个解释可能过于抽象,不过在正式详细介绍fd之前,有必要先了解用户程序和系统内核之间的工作过程。 注: 本文描述的所有场景仅限于类unix系统环境,在windows中这玩意叫file handle(臭名昭著的翻译: 句柄)。 User space & Kernel space 现代操作系统会把内存划分为2个区域,分别为Use space(用户空间) 和 Kernel space(内核空间)。用户的程序在User space执行,系统内核在Kernel space中执行。 用户的程序没有权限直接访问硬件资源,但系统内核可以。比如读写本地文件需要访问磁盘,创建socket需要网卡等。因此用户程序想要读写文件,必须要向内核发起读写请求,这个过程叫system call。 内核收到用户程序system call时,负责访问硬件,并把结果返回给程序。 FileInputStream fis = new FileInputStream("/tmp/test.txt"); byte[] buf = new byte[256]; fis.read(buf); 上面代码的流程如下图所示 File Descriptor 上面简单介绍了User space和Kernel space,这对于理解fd有很大的帮助。fd会存在,就是因为用户程序无法直接访问硬件,因此当程序向内核发起system call打开一个文件时,在用户进程中必须有一个东西标识着打开的文件,这个东西就是fd。 file tables 一个 Linux 进程启动后,会在内核空间中创建一个 PCB 控制块,PCB 内部有一个文件描述符表(File descriptor table),记录着当前进程所有可用的文件描述符,也即当前进程所有打开的文件。进程级的描述符表的每一条记录了单个进程所使用的文件描述符的相关信息,进程之间相互独立,一个进程使用了文件描述符3,另一个进程也可以用3。除了进程级的文件描述符表,系统还需要维护另外两张表:打开文件表、i-node 表。这两张表存储了每个打开文件的打开文件句柄(open file handle)。一个打开文件句柄存储了与一个打开文件相关的全部信息。 和fd相关的一共有3张表,分别是file descriptor、file table、inode table,如下图所示。 file descriptors file descriptors table由用户进程所有,每个进程都有一个这样的表,这里记录了进程打开的文件所代表的fd,fd的值映射到file table中的条目(entry)。 ...