struct file 并不是一个标准的 C 语言库结构体,它不是一个像 struct tm (来自 <time.h>) 或 struct stat (来自 <sys/stat.h>) 那样你可以直接在自己的用户空间 C 程序中使用的结构体。

struct file 是 Linux 内核 中的一个核心数据结构,它代表了内核中的一个打开的文件(或更准确地说是“打开的文件描述符”的内核表示),如果你想在用户空间编程,你几乎不会直接操作 struct file,理解它对于进行 Linux 内核编程(尤其是驱动程序开发)至关重要。
下面我将从以下几个方面来解释 struct file:
- 它是什么?—— 文件描述符的内核视角
- 它的核心成员有哪些?—— 解析结构体
- 它在内核中是如何使用的?—— 生命周期
- 为什么它重要?—— 核心作用
- 用户空间 vs. 内核空间
- 一个简单的内核模块示例
它是什么?—— 文件描述符的内核视角
在用户空间,当你调用 open() 函数时,它会返回一个小的非负整数,这就是文件描述符,你可以把这个文件描述符想象成一个“句柄”或“凭证”,它代表了你的进程与一个打开文件之间的连接。
在内核层面,这个简单的整数背后关联着一系列复杂的数据结构来管理文件 I/O,其中最重要的就是 struct file。

可以把它想象成一个“打开文件的动态信息记录卡”,这张卡片记录了:
- 当前读写位置 (
f_pos) - 访问模式 (只读、只写、读写)
- 指向文件 inode 的指针 (
f_inode),inode 包含了文件的静态元数据(大小、所有者、权限等)。 - 指向文件操作表的指针 (
f_op),这是最重要的部分之一,它定义了可以对这个文件执行的所有操作(读、写、 seek、 ioctl 等)。 - 文件私有数据 (
private_data),驱动程序可以用它来存储自己的状态信息。
关键关系:
进程 -> 文件描述符 (整数) -> struct file (内核对象) -> struct inode (文件静态信息) -> 文件系统 -> 块设备 (如硬盘)
一个进程可以有多个打开的文件,因此一个进程可以有多个文件描述符,多个进程也可以打开同一个文件,这意味着内核中会存在多个 struct file 对象,但它们可能共享同一个 struct inode 对象。

它的核心成员有哪些?—— 解析结构体
struct file 定义在 Linux 内核的 <linux/fs.h> 头文件中,它的成员非常多,因为内核版本不同也会有变化,以下是其中一些最核心和最常用的成员:
struct file {
/*
* ... (其他成员)
*/
unsigned int f_flags; // 文件打开时设置的标志,如 O_RDONLY, O_RDWR, O_NONBLOCK 等
fmode_t f_mode; // 读/写模式,如 FMODE_READ, FMODE_WRITE
loff_t f_pos; // 当前读写位置,loff_t 是 64 位的,支持大文件
struct file_operations *f_op; // 指向文件操作结构体的指针,这是驱动的核心!
void *private_data; // 驱动程序可以使用的私有数据指针
struct inode *f_inode; // 指向该文件对应的 inode 对象
struct dentry *f_path.dentry; // 指向目录项对象,包含路径信息
/*
* ... (其他成员,如 f_count 引用计数等)
*/
};
成员详解:
f_flags: 保存了open()系统调用时传入的标志,如O_APPEND(追加写)、O_NONBLOCK(非阻塞) 等,驱动程序可以检查这些标志来改变行为。f_mode: 标志文件是以读模式还是写模式打开的。f_mode & FMODE_WRITE为真表示可写。f_pos: 当前文件偏移量,当你调用read()或write()时,数据就从/向这个位置开始操作,操作完成后,内核会自动更新这个值,对于普通文件,它指向文件中的字节位置,对于设备,其含义由驱动程序自己定义。f_op: 这是连接驱动程序和 VFS (虚拟文件系统) 的桥梁,它是一个指向struct file_operations结构体的指针。struct file_operations包含了一系列函数指针,如read,write,llseek,open,release,ioctl等,当用户空间调用read(fd, ...)时,VFS 会找到这个struct file对象,然后通过它的f_op指针找到对应的read函数并执行它。编写驱动程序,主要就是填充这个结构体。private_data: 这是一个非常实用的通用指针,驱动程序的open函数通常会在这里分配内存或存储设备号、设备结构体指针等,然后在read,write,release等函数中通过这个指针来访问和修改自己的状态。f_inode: 指向文件的inode对象。inode存储了文件的静态属性,如文件大小、所有者、组、权限、时间戳等。struct file是动态的(读写位置会变),而struct inode是相对静态的。
它在内核中是如何使用的?—— 生命周期
struct file 对象的生命周期是动态的,与文件描述符的打开和关闭紧密相关:
- 创建: 当一个进程成功调用
open()系统时,内核会为这次打开创建一个新的struct file对象。 - 使用: 这个新创建的
struct file对象会被关联到进程的文件描述符表中,之后,所有针对该文件描述符的 I/O 操作(read,write,lseek等)都会通过这个struct file对象进行。 - 销毁: 当进程调用
close()关闭文件描述符时,内核会减少struct file对象的引用计数,当引用计数降为 0 时,内核会释放这个struct file对象占用的内存,通常在销毁前,会调用f_op->release()函数,让驱动程序进行清理工作(如释放private_data中的内存)。
为什么它重要?—— 核心作用
struct file 是 Linux 内核设计哲学的体现,它实现了设备驱动与 VFS 的解耦。
- 统一接口: VFS (Virtual File System) 提供了一套统一的文件操作接口给用户空间,无论是操作一个普通文本文件、一个块设备(如
/dev/sda)还是一个网络套接字,用户程序调用的read(),write()函数都是一样的。 - 动态绑定:
struct file中的f_op指针是“动态”的,VFS 在处理 I/O 请求时,并不关心具体是哪种设备,它只需要通过struct file找到对应的file_operations结构体,然后调用里面的函数即可。 - 驱动程序实现: 作为驱动程序开发者,你的工作就是:
- 定义一个
struct file_operations结构体,并实现其中的read,write等函数。 - 当设备被打开时(
open系统调用),内核会创建一个struct file对象,并将你定义的file_operations结构体的地址赋值给f_op字段。 - 之后,所有针对该设备的 I/O 请求,都会被 VFS 转发到你实现的函数中。
- 定义一个
这种设计使得 Linux 的文件系统非常灵活和可扩展。
用户空间 vs. 内核空间
| 特性 | 用户空间 | 内核空间 |
|---|---|---|
| 代码位置 | 你的 C 程序(如 main() 函数) |
Linux 内核模块(如 init_module() 函数) |
| 访问对象 | 使用文件描述符 (整数) | 直接操作 struct file, struct inode 等内核对象 |
| 库支持 | 可以使用标准 C 库 (stdio.h, fcntl.h 等) |
使用内核头文件 (<linux/fs.h>, <linux/module.h> 等) |
| 权限 | 受限,不能直接访问硬件 | 最高权限,可以访问所有内存和硬件 |
| 函数调用 | 调用 open(), read(), write() 等库函数,这些函数最终会触发系统调用进入内核 |
直接调用内核提供的函数,如 copy_from_user(), copy_to_user() |
struct file |
不可见,你只能拿到一个整数文件描述符 | 核心数据结构,直接操作 |
一个简单的内核模块示例
这个示例创建一个虚拟设备 /dev/mychardev,当向它写入数据时,它会将数据回显(类似 echo),并打印内核日志。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h> // for kmalloc/kfree
#include <linux/uaccess.h> // for copy_from_user, copy_to_user
#define DEVICE_NAME "mychardev"
#define CLASS_NAME "mychardev_class"
// 定义我们的文件操作结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
// .read 和 .write 是必须实现的
.read = my_device_read,
.write = my_device_write,
// 其他操作可以暂时为 NULL
.open = my_device_open,
.release = my_device_close,
};
// 设备号 (主设备号和次设备号)
static dev_t dev_num;
static struct class *my_class;
static struct cdev my_cdev;
// private_data 可以指向任何东西,这里我们用一个简单的字符串来存储
static char *kernel_buffer;
static int buffer_size = 1024;
// 打开设备时的回调函数
static int my_device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "mychardev: Device opened.\n");
// 将我们的内核缓冲区指针存入 private_data
file->private_data = kernel_buffer;
return 0;
}
// 关闭设备时的回调函数
static int my_device_close(struct inode *inode, struct file *file) {
printk(KERN_INFO "mychardev: Device closed.\n");
return 0;
}
// 从设备读取数据的回调函数
static ssize_t my_device_read(struct file *file, char __user *user_buffer, size_t len, loff_t *offset) {
printk(KERN_INFO "mychardev: Read operation called.\n");
// 示例:这里我们只是简单返回,不实现具体读取逻辑
return 0;
}
// 向设备写入数据的回调函数
static ssize_t my_device_write(struct file *file, const char __user *user_buffer, size_t len, loff_t *offset) {
char *data = (char *)file->private_data; // 从 private_data 获取我们的缓冲区
int bytes_to_copy = len;
if (bytes_to_copy > buffer_size)
bytes_to_copy = buffer_size;
// 将用户空间的数据拷贝到内核空间
if (copy_from_user(data, user_buffer, bytes_to_copy) != 0) {
printk(KERN_ERR "mychardev: Failed to copy data from user.\n");
return -EFAULT;
}
printk(KERN_INFO "mychardev: Received %zu bytes from user: %s\n", len, data);
return bytes_to_copy; // 返回实际写入的字节数
}
// 模块初始化函数
static int __init mychardev_init(void) {
// 1. 分配设备号
if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) {
printk(KERN_ERR "mychardev: Failed to allocate device number.\n");
return -1;
}
printk(KERN_INFO "mychardev: Device number allocated: %d, %d\n", MAJOR(dev_num), MINOR(dev_num));
// 2. 创建设备类
my_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(my_class)) {
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "mychardev: Failed to create device class.\n");
return PTR_ERR(my_class);
}
// 3. 创建字符设备文件
if (IS_ERR(device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME))) {
class_destroy(my_class);
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "mychardev: Failed to create device file.\n");
return PTR_ERR(device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME));
}
// 4. 初始化 cdev 结构体并添加到内核
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
if (cdev_add(&my_cdev, dev_num, 1) < 0) {
device_destroy(my_class, dev_num);
class_destroy(my_class);
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "mychardev: Failed to add cdev.\n");
return -1;
}
// 5. 分配 private_data 使用的内存
kernel_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!kernel_buffer) {
// ... 清理代码 ...
printk(KERN_ERR "mychardev: Failed to allocate kernel buffer.\n");
return -1;
}
memset(kernel_buffer, 0, buffer_size);
printk(KERN_INFO "mychardev: Module loaded successfully.\n");
return 0;
}
// 模块退出函数
static void __exit mychardev_exit(void) {
// 1. 释放 cdev
cdev_del(&my_cdev);
// 2. 销毁设备文件和类
device_destroy(my_class, dev_num);
class_destroy(my_class);
// 3. 释放设备号
unregister_chrdev_region(dev_num, 1);
// 4. 释放 private_data 的内存
kfree(kernel_buffer);
printk(KERN_INFO "mychardev: Module unloaded.\n");
}
module_init(mychardev_init);
module_exit(mychardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver example.");
在这个例子中,你可以看到:
fops结构体将我们自定义的my_device_read和my_device_write函数与 VFS 连接起来。- 在
my_device_open中,我们将一个内核缓冲区的地址赋值给了file->private_data。 - 在
my_device_write中,我们通过file->private_data获取到了这个缓冲区,并使用copy_from_user将用户空间的数据安全地拷贝进来,这就是struct file在驱动程序中的典型用法。
struct file 是 Linux 内核管理打开文件的核心数据结构,它是一个动态的、与特定文件描述符相关的对象,封装了文件的当前状态和操作方法,对于内核开发者,尤其是驱动开发者来说,理解并熟练使用 struct file 及其相关的 file_operations 是一项必备技能,它使得复杂的硬件操作能够通过统一的文件 I/O 接口呈现给用户空间,极大地简化了应用程序的开发。
