在Linux通用I/O模型中,I/O操作系列函数(系统调用)都是围绕一个叫做文件描述符的整数展开
/O操作系统调用都以文件描述符(一个非负整数),指代打开的文件。每个进程都有一个打开文件表,可以理解成一个数组,文件描述符可以理解成数组的下标
相关I/O操作系统调用以文件描述符为参数,便可以通过数组访问定位到指定的文件对象,进而进行I/O操作。
当某个程序打开文件时,操作系统返回相应的文件描述符,程序为了处理该文件必须引用此描述符。所谓的文件描述符是一个低级的正整数。最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应,如下表。
文件描述符 用途 POSIX名称 stdio流
0 标准输入 STDIN_FILENO stdin
1 标准输出 STDOUT_FILENO stdout
2 标准出错 STDERR_FILENO stderr
正常情况下,程序在开始运行之前,由shell准备好这3个文件描述符。更准确的说法是,程序继承了shell文件描述符的副本,一般是指向shell所在的终端。当然了,可以通过在shell中对输入/输出进行重定向或者在程序启动后关闭并重新打开文件描述符,修改文件描述符指向。
在linux系统中,内核维护了三个数据结构,分别是进程级文件描述符表、系统级打开文件表和文件系统i-node表。
文件描述符表
内核为每个进程维护一个文件描述符表,该表每一条目都记录了单个文件描述符的相关信息,包括:
控制标志(flags),目前内核仅定义了一个,即close-on-exec
打开文件描述体指针
打开文件表
内核对所有打开的文件维护一个系统级别的打开文件描述表(open file description table),简称打开文件表。表中条目称为打开文件描述体(open file description),存储了与一个打开文件相关的全部信息,包括:
文件偏移量(file offset),调用read()和write()更新,调用lseek()直接修改
访问模式,由open()调用设置,例如:只读、只写或读写等
i-node对象指针
i-node表
每个文件系统会为存储于其上的所有文件(包括目录)维护一个i-node表,单个i-node包含以下信息:
文件类型(file type),可以是常规文件、目录、套接字或FIFO
访问权限
文件锁列表(file locks)
文件大小
i-node存储在磁盘设备上,内核在内存中维护了一个副本,这里的i-node表为后者。副本除了原有信息,还包括:引用计数(从打开文件描述体)、所在设备号以及一些临时属性,例如文件锁。
复制与关闭
文件描述符的复制和重定向非常简单,使用dup()系统调用即可完成。在shell中,使用>即可进行重定向。
流程如下:
打开目标文件,返回文件描述符n;
关闭文件描述符1;
调用dup将文件描述符n复制到1;
关闭文件描述符n;
在程序中使用fork()创建子进程时,父进程中已经打开的fd也会自动在子进程中打开。子进程可以直接对这些文件进行操作。
此时,需要分别在子进程和父进程中关闭fd。一般父进程创建子进程后,父进程会直接关闭掉fd,子进程处理完成后再关闭fd。
使用unix域套接字也可以进行文件描述符的传递,但从一个进程传递到另一个进程后,fd可能会发生变化。
注意使用完毕后,分别关闭fd。