golang,go,博客,开源,编程

进程的文件描述符表

Published on with 0 views and 0 comments

进程的文件描述符表

在 Linux 系统中,文件描述符表是每个进程用于管理文件描述符(File Descriptors,简称 FD)的一种数据结构。每个进程都有一个独立的文件描述符表,用于记录该进程打开的文件或输入输出资源(如管道、套接字、设备等)以及与之相关的信息。通过文件描述符,进程可以执行各种 I/O 操作,如读取、写入文件,或通过套接字进行网络通信。

1. 文件描述符表的结构

文件描述符表是一个内存中的数据结构,其中每个条目(通常是一个结构体)表示一个打开的文件或 I/O 流。每个文件描述符都是文件描述符表中的一个索引,用于访问对应的文件或资源。

  • 进程的文件描述符表:每个进程都有一个文件描述符表(File Descriptor Table),它包含指向内核中打开文件的指针。这些指针指向了内核内部的数据结构,如文件描述符结构体(struct file)和文件操作结构体(struct file_operations)。
  • 文件描述符表中的条目:每个条目包括文件描述符指向的文件或资源的相关信息,如:
    • 文件的位置(文件指针,记录文件的读写位置)。
    • 文件状态(只读、只写、读写等)。
    • 引用计数(表示文件是否被多个进程共享等)。

2. 文件描述符表的基本组成

在 Linux 系统中,进程的文件描述符表通常包含以下几个部分:

  • 索引为0、1、2的标准文件描述符

    • 文件描述符 0:标准输入(stdin),默认从键盘读取输入。
    • 文件描述符 1:标准输出(stdout),默认输出到屏幕。
    • 文件描述符 2:标准错误(stderr),默认输出到屏幕。

    这三个文件描述符在进程启动时由操作系统自动打开,并且是预设的。

  • 文件描述符表的动态部分:如果进程打开更多的文件或设备,操作系统会为每个打开的资源分配一个文件描述符,并将它们记录在文件描述符表中。每个文件描述符指向内核中相应的文件描述符结构。

3. 内核中的文件描述符

在内核中,文件描述符表记录着进程打开的文件、设备、管道等资源的指针。在 Linux 中,file 结构体是用来表示一个文件描述符的核心结构体。每个文件描述符在内核中都有一个对应的 struct file 结构体。该结构体包含了文件的状态、读写操作以及其他元数据。

// struct file
struct file {
    struct file_operations *f_op;  // 文件操作函数指针
    unsigned int f_flags;         // 文件的标志位(如只读、读写等)
    off_t f_offset;               // 当前文件读写位置(文件指针)
    struct inode *f_inode;        // 指向文件的 inode
    void *private_data;           // 特定于文件的私有数据(如套接字文件的私有数据)
};
  • f_op:指向 file_operations 结构体的指针,定义了该文件的操作函数,如读写操作。
  • f_flags:表示文件的标志,指示文件是以只读模式、只写模式,还是读写模式打开的。
  • f_offset:表示文件的当前读写位置(文件指针),用于文件的随机访问。
  • f_inode:指向文件的 inode(索引节点),包含文件的元数据(如权限、大小、时间戳等)。
  • private_data:特定于文件的私有数据,用于存储文件类型的特定数据,如网络套接字相关的数据。

4. 文件描述符表的操作

操作系统通过系统调用对文件描述符表进行管理,以下是几个常见的文件描述符操作:

4.1 打开文件(open 系统调用)

当一个进程调用 open() 打开文件时,操作系统会在文件描述符表中分配一个空闲的文件描述符,并在内核中创建一个 struct file 对象。这个对象包含了文件的操作信息和当前状态。

int fd = open("file.txt", O_RDWR);  // 打开文件并返回文件描述符
  • 返回值:成功时返回一个文件描述符,失败时返回 -1
  • 操作系统将文件描述符 fd 映射到 struct file 结构体,并记录在进程的文件描述符表中。

4.2 读写文件(read / write 系统调用)

一旦文件描述符被分配并指向一个文件,进程可以使用 read()write() 函数来进行文件的读写操作。

char buffer[128];
read(fd, buffer, sizeof(buffer));  // 从文件描述符 fd 读取数据
write(fd, buffer, sizeof(buffer));  // 将数据写入到文件描述符 fd 指向的文件
  • 读取操作:内核通过文件描述符获取到对应的 struct file,然后调用相应的文件操作函数来执行读取操作。
  • 写入操作:写入时,内核通过文件描述符获取 struct file,然后调用文件操作函数来执行写入操作。

4.3 关闭文件(close 系统调用)

当文件不再需要时,进程可以通过 close() 系统调用关闭文件描述符,操作系统会释放文件描述符所占用的资源,并将该文件描述符从文件描述符表中移除。

close(fd);  // 关闭文件描述符 fd
  • 关闭文件描述符:调用 close() 后,操作系统会更新进程的文件描述符表,释放对应的文件描述符,并减少该文件的引用计数。当引用计数为零时,内核会释放 struct file 结构体所占用的内存。

4.4 复制文件描述符(dup 系统调用)

进程还可以使用 dup()dup2() 系统调用复制一个文件描述符,使多个文件描述符指向相同的文件。

int new_fd = dup(fd);  // 复制文件描述符 fd
int new_fd2 = dup2(fd, 3);  // 将 fd 复制到文件描述符 3
  • dup():复制当前的文件描述符,返回一个新的文件描述符,指向相同的文件。
  • dup2():将文件描述符 fd 复制到指定的文件描述符 new_fd2

4.5 文件描述符的传递

通过 Unix 域套接字(Unix Domain Socket),一个进程可以将文件描述符传递给另一个进程。Linux 支持文件描述符的传递,通过 sendmsg()recvmsg() 系统调用来实现。

int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct msghdr msg;
struct iovec io;
char buf[1];
io.iov_base = buf;
io.iov_len = 1;
msg.msg_iov = &io;
msg.msg_iovlen = 1;

int fd_to_send = open("file.txt", O_RDONLY);
sendmsg(sockfd, &msg, 0);  // 传递文件描述符

5. 文件描述符表的限制

每个进程都有限制可以打开的文件描述符的数量,这个限制是由操作系统的内核配置和系统资源决定的。通常可以通过以下命令查看和设置文件描述符的最大限制:

  • 查看当前最大文件描述符限制
    ulimit -n  # 查看当前进程的文件描述符限制
    
  • 设置文件描述符限制
    ulimit -n 1024  # 设置最大文件描述符数为1024
    

总结

  • 文件描述符表是每个进程用来管理其打开的文件、设备、管道等 I/O 资源的结构。
  • 文件描述符是一个非负整数,进程通过文件描述符与内核中的文件对象进行交互。
  • 内核通过 struct file 数据结构管理每个文件描述符的具体信息,包括文件位置、文件标志、文件操作等。
  • 进程可以通过 open(), read(), write(), close() 等系统调用操作文件描述符。
  • 进程的文件描述符表是进程的局部资源,而文件描述符是指向内核中资源的句柄。

标题:进程的文件描述符表
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/01/07/1736226629362.html
联系:scotttu@163.com