在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。

    Linux中文件描述符 - 图1

    linux文件描述符限制及使用详解

    Linux中文件描述符的理解(文件描述符、文件表项、i-node)

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

    Linux下文件描述符