使用文件描述符

文件描述符是进程用于标识打开的文件的无符号整数。

进程可用的文件描述符的数目受 sys/limits.h 文件中的 /OPEN_MAX 控制符限制。 文件描述符的数目还由 ulimit -n 标记控制。 openpipecreatfcntl 子例程都生成文件描述符。 对于每个进程而言,文件描述符通常是唯一的,但是它们可以被子进程(使用 fork 子例程)创建或通过 fcntldupdup2 子例程复制。

文件描述符是 u_block 区域(由内核为每个进程维护)中文件描述符表的索引。 进程获取文件描述符的最常用方式是通过 opencreat 操作,或者通过从父进程继承。 进行 fork 操作时,将为子进程复制描述符表,这允许子进程可以同等访问父进程使用的文件。

文件描述符表和系统打开文件表

文件描述符和打开文件表结构追踪每个进程对文件的访问并确保数据完整性。
描述
文件描述符表 将表中的索引号(文件描述符)转换至打开的文件。 每个进程都创建有文件描述符表,其位于为该进程留出的 u_block 区域中。 文件描述符表中的每个条目都具有下列字段:标记区域和文件指针。 最多有 OPEN_MAX 个文件描述符。 文件描述符表的结构如下:
struct ufd
{
        struct file *fp;
        int flags;
} *u_ufd
系统打开文件表 包含每个打开文件的条目。 文件表条目追踪当前偏移量(被所有对文件的读或写操作引用)和文件的打开方式(O_RDONLYO_WRONLYO_RDWR)。

打开文件表结构包含文件的当前 I/O 偏移量。 系统将每个读/写操作视为对当前偏移量的默示寻找。 因此,如果读或写 x 字节,那么指针向前 x 字节。 lseek 子例程可用于将当前偏移量重新分配到可随机访问的文件中的指定位置。 流类型文件(例如管道和套接字)不使用偏移量,因为文件中的数据不可随机访问。

管理文件描述符

因为文件可以由许多用户共享,所以必须允许相关进程共享公用的偏移量指针并且访问相同文件的独立进程具有单独的当前偏移量指针。 打开文件表条目维护引用数目来追踪分配给文件的文件描述符的数目。

下列情况的任意一种都会引起对单个文件的多次引用:
  • 一个单独的进程打开文件
  • 子进程保留分配给父进程的文件描述符
  • fcntldup 子例程创建文件描述符的副本

共享打开文件

每个打开操作创建一个系统打开文件表条目。 单独的表条目确保每个进程具有独立的当前 I/O 偏移量。 独立的偏移量保护数据的完整性。

复制文件描述符时,两个进程共享相同的偏移量并且可能发生交错,其中不连续读或写字节。

复制文件描述符

可以在进程之间以下列方式复制文件描述符:dupdup2 子例程、fork 子例程和 fcntl(文件描述符控制)子例程。

dup 和 dup2 子例程

dup 子例程创建文件描述符的一个副本。 在包含原描述符的用户文件描述符表中的空白空间处创建副本。 dup 进程将文件表条目中的引用计数增加 1 并返回放置副本处的文件描述符的索引号。

dup2 子例程扫描请求的描述符分配并关闭请求的文件描述符(如果其打开)。 它允许进程指定副本将占据哪个描述符条目(如果需要特定的描述符表条目)。

fork 子例程
fork 子例程创建继承分配给父进程的文件描述符的子进程。 然后子进程执行一个新的进程。 具有由 fcntl 子例程设置的 close-on-exec 标记的继承的描述符关闭。

 

fcntl(文件描述符控制)子例程
fcntl 子例程处理文件结构并控制打开文件描述符。 它可用于对描述符进行下列更改:
  • 复制文件描述符(与 dup 子例程相同)。
  • 获取或设置 close-on-exec 标记。
  • 为描述符设置非阻塞方式。
  • 将将来的写入追加到文件的末尾 (O_APPEND)。
  • 可以进行 I/O 时,对进程启用生成信号。
  • 设置或获取 SIGIO 处理的进程标识或组进程标识。
  • 关闭所有文件描述符。

预置文件描述符值

当 shell 运行程序时,它打开三个具有文件描述符 0 , 1 和 2 的文件。 这些描述符的缺省分配如下所示:

描述信息 说明
重大安全事件数量 代表标准输入。
第 1 年 代表标准输出。
2 代表标准错误。

这些缺省文件描述符被连接到终端,因此,如果程序读文件描述符 0 并写文件描述符 1 和 2,那么程序从终端收集输入并将输出发送至终端。 程序使用其他文件时,以升序分配文件描述符。

如果使用 <(小于)或 >(大于)符号重定向 I/O,则会更改 shell 的默认文件描述符分配。 例如,下列命令将文件描述符 0 和 1 的缺省分配从终端更改为合适的文件:
prog < FileX > FileY

在此示例中,文件描述符 0 现在引用FileX和文件描述符 1 引用FileY. 文件描述符 2 未更改。 程序不需要知道其输入来自何处,也不需要知道发送至何处,只要文件描述符 0 代表输入文件,1 和 2 代表输出文件。

下列样本程序说明标准输出的重定向:
#include <fcntl.h>
#include <stdio.h>

void redirect_stdout(char *);

main()
{
       printf("Hello world\n");       /*this printf goes to
                                      * standard output*/
       fflush(stdout);
       redirect_stdout("foo");        /*redirect standard output*/
       printf("Hello to you too, foo\n");
                                      /*printf goes to file foo */
       fflush(stdout);
}

void
redirect_stdout(char *filename)
{
        int fd;
        if ((fd = open(filename,O_CREAT|O_WRONLY,0666)) < 0)
                                        /*open a new file */
        {
                perror(filename);
                exit(1);
        }
        close(1);                       /*close old */
                                        *standard output*/
        if (dup(fd) !=1)                /*dup new fd to
                                        *standard input*/
        {
                fprintf(stderr,"Unexpected dup failure\n");
                exit(1);
        }
        close(fd);                       /*close original, new fd,*/
                                         * no longer needed*/
}

在文件描述符表内,文件描述符号码被分配请求描述符时的最低可用描述符号码。 然而,可以在文件描述符表内通过使用 dup 子例程分配任何值。

文件描述符资源限制

可以被分配给进程的文件描述符的数目由资源限制管理。 在 /etc/security/limits 文件中设置缺省值且通常设为 2000 。 可以通过 ulimit 命令或 setrlimit 子例程更改限制。 最大大小由常量 OPEN_MAX 定义。