Linux “一切皆文件”的内核实现——struct file与file_operations

目录

[1. 引言](#1. 引言)

[2. 从进程的文件描述符到内核的 struct file](#2. 从进程的文件描述符到内核的 struct file)

[3. struct file_operations ------ 函数跳转表](#3. struct file_operations —— 函数跳转表)

[4. 为什么说"一切皆文件"?](#4. 为什么说“一切皆文件”?)

[5. 验证:查看设备的 file_operations](#5. 验证:查看设备的 file_operations)

[6. 小结](#6. 小结)


1. 引言

"一切皆文件"是Linux系统的核心设计哲学之一。键盘、显示器、磁盘、管道、套接字等,都可以用 openreadwriteclose 等统一接口进行操作。本文将深入内核数据结构,解释这种抽象是如何实现的。

2. 从进程的文件描述符到内核的 struct file

回顾一下:每个进程的 files_struct 中包含一个 fd_array[],每个元素指向一个 struct file 对象。struct file 是内核中表示一个打开文件的数据结构,定义在 <linux/fs.h> 中:

c

复制代码
struct file {
    struct inode *f_inode;           // 指向文件的元数据
    const struct file_operations *f_op; // 关键:函数指针集合
    atomic_long_t f_count;           // 引用计数
    unsigned int f_flags;            // 打开标志(O_RDONLY, O_NONBLOCK等)
    fmode_t f_mode;                  // 访问模式
    loff_t f_pos;                    // 当前文件偏移量
    // ... 其他成员
};

3. struct file_operations ------ 函数跳转表

file_operations 结构体中包含大量函数指针,对应各种系统调用在特定设备上的具体实现:

c

复制代码
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    // ... 还有 poll, ioctl, mmap 等
};

不同设备提供不同的函数实现:

  • 对于普通磁盘文件,read 指向 generic_file_read,负责从文件系统读取数据。

  • 对于键盘设备,read 指向键盘驱动提供的函数,负责获取按键输入。

  • 对于显示器设备,write 指向显示驱动,负责在屏幕上显示字符。

统一接口的秘密: 当进程调用 read(fd, ...) 时,内核通过 fd 找到 struct file,再通过 file->f_op->read 调用具体设备的读函数。用户态无须关心底层差异。

4. 为什么说"一切皆文件"?

因为内核为几乎所有资源都实现了 file_operations 接口:

  • 普通文件:磁盘文件系统

  • 目录:目录的 read 返回目录项

  • 块设备:如 /dev/sda

  • 字符设备:如 /dev/tty/dev/input/mouse

  • 管道(pipe)和 FIFO

  • 套接字(socket)

开发者可以使用同一套系统调用接口与这些资源交互,极大降低了学习和开发成本。

5. 验证:查看设备的 file_operations

可以使用 ls -l /dev 查看设备文件,并通过 strace 跟踪系统调用。例如:

bash

复制代码
strace -e read,write cat /dev/zero > /dev/null

可以看到 readwrite 系统调用被使用,无论 /dev/zero 还是 /dev/null 都是"文件"。

6. 小结

"一切皆文件"的核心在于内核通过 struct filestruct file_operations 实现了多态 :不同类型的设备提供相同的函数接口,而上层代码无需区分。这是Linux系统简洁而强大的设计之一。下一篇我们将讨论I/O中的缓冲区机制,这是理解 printf/write 行为差异的关键。