linux系统文件描述符详解

首先我们需要知道操作系统内维护了三个系统文件表:文件描述符表(file descriptor table),打开文件表(open file table),inode table。这三个表的结构见Table-1

我们知道在like-unix系统中所有的IO操作(包括socket等)都是基于文件描述符的。程序刚刚启动的时候已经默认帮我们分配了三个文件描述符,就是我们常用的0标准输入,1标准输出,2标准错误。如果此时进程再打开新的文件,它的文件描述符会加1也就是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号。

Table-1

linux系统文件描述符详解_第1张图片

 文件描述符表(file descriptor table)

每一个进程维护了一份自己的文件描述符表,该表中的每条记录维护了该条文件描述符的相关信息,包括:

  • 控制标志(fd flags), 截至目前,内核仅定义了一个flag,close-on-exec
  • open file ptr : 指向open file table的指针

打开文件表(open file table)

内核内维护的一个系统范围的打开文件表。该表中包含每一个打开的文件条目,文件表项跟踪所有对文件的读或写操作当前偏移量和文件的打开模式(O_RDONLYO_WRONLY, or O_RDWR)。

该表中的每条记录维护了与一个打开的文件相关的全部信息,包括:

  • 文件偏移量(file offset):调用read()或者write()操作更新,调用fseek()直接修改,流类型的文件(比如pipes 和 sockets)不能使用偏移量,因为文件中的数据不是随机访问的。
  • 访问模式(status flags):只读,只写或读写等
  • inode指针:指向inode表元素的指针,关联物理文件
  • 引用计数:比如一条记录被2个文件描述符引用,计数就为2

inode表

在linux系统中使用inode号描述文件,就像进程使用pid描述进程一样。inode存储了文件的元信息。系统上的所有文件都分配了一个inode记录。

每个inode记录包含以下信息(可以使用stat命令查看):

$ stat a
  File: a
  Size: 4         	Blocks: 8          IO Block: 4096   regular file
Device: 805h/2053d	Inode: 1446030     Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/zhangboqi)   Gid: ( 1000/zhangboqi)
Access: 2021-09-29 14:35:32.344894535 +0800
Modify: 2021-09-29 14:35:32.344894535 +0800
Change: 2021-09-29 14:35:32.344894535 +0800
 Birth: -
  • 文件的字节数(Size):
  • Blocks: 文件占用块大小,单位512字节,就是文件实际占用大小
  • IO Block: 文件系统每次IO操作的最小大小,ext4默认是4096字节
  • 文件类型:如,常规文件(regular file),目录,套接字或FIFO等
  • 设备号(Device):设备号
  • inode号(Inode):文件对应的inode号
  • 硬链接数(Links):即有多少个文件指向这个inode
  • 文件权限(Access)
  • 文件uid和gid:
  • 文件的时间戳:Access为文件上一次打开的时间,Modify为文件上一次修改的时间,Change为inode上一次变动的时间

注:inode存储在磁盘设备上,但内核会在内存中维护一份。这里的inode table就是指内存中的副本。inode table中的记录同样维护了一个引用计数,该记录每被open file记录引用一次就加1。

文件描述符数量限制

进程可用的文件描述符的数量由sys/limits.h文件中的/OPEN_MAX控制。文件描述符的数量也可由ulimit -n控制。可以分配给进程的文件描述符的数量由资源限制控制。默认值是在/etc/security/limits文件中设置的,通常设置为2000。可以通过ulimit命令或setrlimit子例程更改该限制。最大大小由常量OPEN_MAX定义。

文件描述符的创建

open, pipe, createfcntl子程序都可以创建文件描述符。文件描述符在每个进程里都是唯一的,但是他们可以有fork子程序创建的子进程共享。也可以由fcntldupdup2子程序复制。

文件描述符是一个存储在内核为每个进程维护的u_block区域中的文件描述符表的索引。进程获取文件描述符的最常见方法是通过opencreate操作,或者从父进程继承。当一个fork操作发生时。系统为子进程复制父进程的文件描述符表,它允许子进程平等地访问父进程使用的文件。

管理文件描述符

因为文件是可以同时被多个用户共享的,所以有必要允许相关进程共享一个公共偏移指针,并且访问同一文件的独立进程拥有一个单独的当前偏移指针,open file table 条目维护了一个引用计数,用来跟踪分配给文件的文件描述符的数量。

对单个文件的多个引用可能是由下列任何一种情况引起的:

  • 打开文件的单独进程
  • 子进程保留分配给父进程的文件描述符
  • fcntl或dup子例程创建一个文件描述符的副本

共享打开文件

每个打开操作创建一个系统打开文件表项。单独的表项确保每个进程有单独的当前I/O偏移量。独立偏移量可以保护数据的完整性。当一个文件描述符被复制时,两个进程将共享相同的偏移量,并可能发生交错,在这种情况下,字节不是按顺序读或写的。

复制文件描述符

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

dup和dup2子程序

dup子例程创建一个文件描述符的副本。副本是在包含原始描述符的用户文件描述符表中的空白位置创建的。dup进程将open file table项中的引用计数加1,并返回副本所在的文件描述符的索引号。

fork子程序

fork子例程创建一个子进程,子进程继承分配给父进程的文件描述符。然后子进程执行一个新进程。当fcntl子程序关闭时继承的文件描述符会设置close-on-exec标志

FCNTL(文件描述符控制)子程序

fcntl子例程操作文件结构并控制打开的文件描述符。它可以用来对描述符进行以下更改:

  • 复制一个文件描述符(与dup子例程相同)。
  • 获得或设置“close-on-exec ”标志。
  • 为描述符设置非阻塞模式。
  • Append future writes to the end of the file(O_APPEND).
  • 当可以进行I/O时,允许向进程生成信号。
  • Set or get the process ID or the group process ID for SIGIO handling.
  • 关闭所有文件描述符。

预设文件描述符值

当shell运行一个程序时,它会打开三个文件,其中包含文件描述符0、1和2。这些描述符的默认赋值如下:

Descriptor

Explanation

0 Represents standard input.
1 Represents standard output.
2 Represents standard error.

这些默认的文件描述符连接到终端,因此,如果程序读取文件描述符0并写入文件描述符1和2,则程序从终端收集输入并将输出发送到终端。当程序使用其他文件时,文件描述符按升序分配。

如果使用<(小于)或>(大于)符号重定向I/O,则shell的默认文件描述符分配将被更改。例如,以下命令将文件描述符0和1的默认赋值从终端更改为适当的文件:

prog < FileX > FileY

在这个例子中,文件描述符0现在指的是FileX,文件描述符1指的是FileY。文件描述符2没有被更改。程序不需要知道它的输入来自哪里,也不需要知道它被发送到哪里,只要文件描述符0表示输入文件,1和2表示输出文件。

下面的示例程序演示了标准输出的重定向:

#include 
#include 

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子例程在文件描述符表中分配任何值。

你可能感兴趣的:(linux,linux,运维)