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)。 ...

June 10, 2026

linux

文件的10个关键系统调用 open/close read/write/lseek(随机查找) fstst(返回文件数据)/ftruncate(清空) unlink(删除文件)/mkdir / dup 文件系统的抽象 struct File { mode (permissions, type); size; user; group;//type数据结构的类型 timestamps(atime, mtime,ctime); content (bytes); }; map <string, File> // from file path to file 一种数据结构, 从路径到文件的映射

June 10, 2026

linux select函数

linux select函数 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核: 我们所关心的文件描述符 对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常) 我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待) 从 select函数返回后,内核告诉我们一下信息: 对我们的要求已经做好准备的描述符的个数 对于三种条件哪些描述符已经做好准备.(读,写,异常) 有了这些返回信息,我们可以调用合适的I/O函数(通常是 read 或 write),并且这些函数不会再阻塞. 函数声明 #include <sys/select.h> int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout); 返回值:做好准备的文件描述符的个数,超时为0,错误为 -1. 参数 struct timeval 首先我们先看一下最后一个参数。它指明我们要等待的时间: struct timeval{ long tv_sec; /*秒 */ long tv_usec; /*微秒 */ } 有三种情况: timeout == NULL 等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号, select函数将返回 -1,并将变量 erro设为 EINTR。 timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态。 timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回 0。对于第一种情况,等待也会被信号所中断。 readset, writset, exceptse 中间的三个参数 readset, writset, exceptset,指向描述符集。这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。一个文件描述集保存在 fd_set 类型中。fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。如下图所示: ...

June 10, 2026

文件控制函数

文件控制函数 file control 功能描述 fcntl函数可以用来对已打开的文件描述符进行各种控制操作, 以改变已打开文件的的各种属性 头文件 #include <unistd.h> #include <fcntl.h> 函数原型 int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); c int fcntl(int fd, int cmd, struct flock *lock); 描述 fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符. 针对cmd的值,fcntl能够接受第三个参数(arg) fcntl函数有5种功能c 复制一个现有的描述符(cmd=F_DUPFD). 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW). cmd 选项 F_DUPFD 返回一个如下描述的(文件)描述符: (1)最小的大于或等于arg的一个可用的描述符 (2)与原始操作符一样的某对象的引用 (3)如果对象是文件(file)的话,返回一个新的描述符,这个描述符与arg共享相同的偏移量(offset) (4)相同的访问模式(读,写或读/写) (5)相同的文件状态标志(如:两个文件描述符共享相同的状态标志) (6)与新的文件描述符结合在一起的close-on-exec标志被设置成交叉式访问execve(2)的系统调用 F_GETFD 取得与文件描述符fd联合close-on-exec标志,类似FD_CLOEXEC. 如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg被忽略) F_SETFD 设置close-on-exec旗标。该旗标以参数arg的FD_CLOEXEC位决定。 F_GETFL 取得fd的文件状态标志,如同下面的描述一样(arg被忽略) F_SETFL 设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC。 F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略) F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明,否则,arg将被认为是进程id 命令字(cmd)F_GETFL和F_SETFL的标志如下面的描述: O_NONBLOCK 非阻塞I/O ; 如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误 O_APPEND 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志 O_DIRECT 最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据. 如果不能够避免缓存,那么它将最小化已经被缓存了的数 据造成的影响.如果这个标志用的不够好,将大大的降低性能 O_ASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候 **注意:**在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。 ...

June 10, 2026