Linux笔记---一切皆文件
1. 含义
“一切皆文件”是 Linux 对系统资源的高度抽象,通过文件接口屏蔽底层差异,提供了简洁、一致的操作方式。这种设计降低了系统复杂性,使得工具、脚本和应用程序能够以统一模式处理多样化资源,是 Linux 强大灵活性的重要基石。
简单来说,在Linux操作系统中,所有的资源(包括普通文件(文本、二进制文件等)、目录、设备(如磁盘、键盘)、进程信息、网络套接字、管道等)都被抽象为了文件。
在用户层面上,我们可以通过对对应的文件进行操作,进而完成对这些资源的操作。
这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具,即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读PIPE)的操作都可以用read 函数来进行;几乎所有更改(更改文件,更改系统参数,写 PIPE)的操作都可以用 write 函数来进行。
2. 原理
之前我们讲过,当打开一个文件时,操作系统为了管理所打开的文件,都会为这个文件创建一个file结构体,该结构体定义在 / usr / src / kernels / 3.10.0-1160.71.1.el7.x86_64 / include / linux / fs.h 下:
struct file {
...
struct inode* f_inode; /* cached value */
const struct file_operations* f_op;
...
atomic_long_t f_count; // 表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值。
unsigned int f_flags; // 表示打开文件的权限
fmode_t f_mode; // 设置对文件的访问模式,例如:只读,只写等。所有的标志在头⽂件<fcntl.h> 中定义
loff_t f_pos; // 表示当前读写文件的位置
...
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
值得关注的是 struct file 中的 f_op 指针指向了一个 file_operations 结构体,这个结构体中的成员除了struct module* owner 其余都是函数指针。该结构和 struct file 都在fs.h下:
struct file_operations {
struct module* owner;
//指向拥有该模块的指针;
loff_t(*llseek) (struct file*, loff_t, int);
//llseek ⽅法⽤作改变⽂件中的当前读/写位置, 并且新位置作为(正的)返回值.
ssize_t(*read) (struct file*, char __user*, size_t, loff_t*);
//⽤来从设备中获取数据. 在这个位置的⼀个空指针导致 read 系统调⽤以 -EINVAL("Invalid argument") 失败.
//⼀个⾮负返回值代表了成功读取的字节数(返回值是⼀个"signed size" 类型, 常常是⽬标平台本地的整数类型).
ssize_t(*write) (struct file*, const char __user*, size_t, loff_t*);
//发送数据给设备. 如果 NULL, -EINVAL 返回给调⽤ write 系统调⽤的程序. 如果⾮负, 返回值代表成功写的字节数.
ssize_t(*aio_read) (struct kiocb*, const struct iovec*, unsigned long, loff_t);
//初始化⼀个异步读 -- 可能在函数返回前不结束的读操作.
ssize_t(*aio_write) (struct kiocb*, const struct iovec*, unsigned long, loff_t);
//初始化设备上的⼀个异步写.
int (*readdir) (struct file*, void*, filldir_t);
//对于设备⽂件这个成员应当为 NULL; 它⽤来读取⽬录, 并且仅对**⽂件系统**有⽤.
unsigned int (*poll) (struct file*, struct poll_table_struct*);
int (*ioctl) (struct inode*, struct file*, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file*, unsigned int, unsigned long);
long (*compat_ioctl) (struct file*, unsigned int, unsigned long);
int (*mmap) (struct file*, struct vm_area_struct*);
//mmap ⽤来请求将设备内存映射到进程的地址空间. 如果这个⽅法是 NULL, mmap 系统调⽤返回 - ENODEV.
int (*open) (struct inode*, struct file*);
//打开⼀个⽂件
int (*flush) (struct file*, fl_owner_t id);
//flush 操作在进程关闭它的设备⽂件描述符的拷⻉时调⽤;
int (*release) (struct inode*, struct file*);
//在⽂件结构被释放时引⽤这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file*, struct dentry*, int datasync);
//⽤⼾调⽤来刷新任何挂着的数据.
int (*aio_fsync) (struct kiocb*, int datasync);
int (*fasync) (int, struct file*, int);
int (*lock) (struct file*, int, struct file_lock*);
//lock ⽅法⽤来实现⽂件加锁; 加锁对常规⽂件是必不可少的特性, 但是设备驱动⼏乎从不实现它.
ssize_t(*sendpage) (struct file*, struct page*, int, size_t, loff_t*, int);
unsigned long (*get_unmapped_area)(struct file*, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file*, int, struct file_lock*);
ssize_t(*splice_write)(struct pipe_inode_info*, struct file*, loff_t*, size_t, unsigned int);
ssize_t(*splice_read)(struct file*, loff_t*, struct pipe_inode_info*, size_t, unsigned int);
int (*setlease)(struct file*, long, struct file_lock**);
};
file_operation 就是把系统调用和驱动程序关联起来的关键数据结构,这个结构的每一个成员都对应着一个系统调用。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
每一种设备都有用于描述自身的读写方法与属性等(在对应的数据结构中),将这些方法的地址赋值给对应的函数,将属性抽象成文件的内容,就可以用访问文件的方式来访问这些资源。
虽然在访问这些设备时所调用的函数都是文件的 read 和 write 等,但实际上调用的却是对应设备的读写函数。
按照面向对象语言的视角来理解就是:struct file 是一个抽象类,而各种设备继承自 struct file 并各自实现了读写等方法。在较高的层次就可以将这些设备都看作文件来处理。
这就是C语言中如何实现继承与多态并进行运用的最好例子!
上图中的外设,每个设备都可以有自己的read、write,但一定是对应着不同的操作方法。但通过struct file 下 file_operation 中的各种函数回调,让我们开发者只用file便可调取 Linux 系统中绝大部分的资源。
这便是“linux下一切皆文件”的核心理解。