目录
[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系统的核心设计哲学之一。键盘、显示器、磁盘、管道、套接字等,都可以用 open、read、write、close 等统一接口进行操作。本文将深入内核数据结构,解释这种抽象是如何实现的。
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
可以看到 read 和 write 系统调用被使用,无论 /dev/zero 还是 /dev/null 都是"文件"。
6. 小结
"一切皆文件"的核心在于内核通过 struct file 和 struct file_operations 实现了多态 :不同类型的设备提供相同的函数接口,而上层代码无需区分。这是Linux系统简洁而强大的设计之一。下一篇我们将讨论I/O中的缓冲区机制,这是理解 printf/write 行为差异的关键。