【linux仓库】万物至简的设计典范:如何用‘文件’这一个概念操纵整个Linux世界?
通过分析文件描述符(FD)系统,揭示了Linux如何将各类设备统一抽象为文件接口:从进程的files_struct结构体到file_operations方法集,系统通过标准化的open/read/write等接口管理所有资源。重点剖析了FD分配规则、重定向实现(dup2)和进程继承机制,并指出struct file中的f_op指针是实现多态访问的关键——不同设备提供统一接口但各自实现具体操作。这种
🌟 各位看官好,我是egoist2023!
🌍 Linux == Linux is not Unix !
🚀 今天来学习一切皆文件的相关知识。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!
目录
书接上文
本文深入剖析了Linux文件描述符(FD)的核心机制。我们从open
系统调用的返回值切入,揭示了其“数组下标”的本质特性。通过追溯进程task_struct中的files_struct结构,我们阐明了0、1、2号FD被固定分配为标准输入、输出、错误的规则,并验证了C库FILE
结构体对FD的封装关系。
在此基础上,我们系统讲解了FD的分配规则、重定向的底层实现(基于dup2
系统调用),以及父子进程间FD的继承机制。
然而,这一切精巧的设计,都服务于一个更为宏大和深刻的Linux设计哲学——“一切皆文件”。
FD,正是这一哲学得以实现的基石与枢纽。它不仅仅是一个用于访问普通文件的句柄,更是一个统一的抽象接口。正是凭借FD这个“万能手柄”,Linux才能将形态各异的外部设备——无论是磁盘、键盘、显示器,还是网络套接字、管道——都抽象为一种可以统一进行读写(read
/write
)操作的对象。
接下来,我们将超越普通文件的范畴,深入虚拟文件系统(VFS) 层,探索Linux是如何用“文件”这同一个概念,来统一抽象万物的。我们将看到,正是FD机制的存在,才使得“一切皆文件”从一句口号,变成了一个强大而优雅的现实。
前言
OS要不要管理硬件呢?它是软硬件资源的管理者,要管理,那么该如何管理呢?
先描述,再组织!!!
struct device
{
int type;
int status;
...
struct list_head node;
}
一切皆文件
首先,在windows中是⽂件的东西,它们在linux中也是⽂件;其次⼀些在windows中不是⽂件的东
西,⽐如进程、磁盘、显⽰器、键盘这样硬件设备也被抽象成了⽂件,你可以使⽤访问⽂件的⽅法访问它们获得信息;甚⾄管道,也是⽂件;将来我们要学习⽹络编程中的socket(套接字)这样的东西,使⽤的接⼝跟⽂件接⼝也是⼀致的。
这样做最明显的好处是,开发者仅需要使⽤⼀套 API 和开发⼯具,即可调取 Linux 系统中绝⼤部分的资源。举个简单的例⼦,Linux 中⼏乎所有读(读⽂件,读系统状态,读PIPE)的操作都可以⽤
read 函数来进行;几乎所有更改(更改⽂件,更改系统参数,写 PIPE)的操作都可以⽤ write 函
数来进⾏。
之前我们讲过,当打开⼀个⽂件时,操作系统为了管理所打开的⽂件,都会为这个⽂件创建⼀个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 下 file_operation 中的各种函数回调,让我们开发者只⽤file便可调取 Linux 系统中绝⼤部分的资源!!这便是“linux下⼀切皆⽂件”的核心理解。
刨根问底
当我们打开⽂件时,操作系统在内存中要创建相应的数据结构来描述⽬标⽂件。⽽进程执⾏open系统调⽤,所以必须让进程和⽂件关联起来。每个进程都有⼀个指针*files, 指向⼀张表files_struct,该表最重要的部分就是包含⼀个指针数组,每个元素都是⼀个指向打开⽂件的指针!
Linux中,打开文件,要为我们创建struct fle,三个核心:
1.文件属性
2.文件内核缓冲区
3.底层设备文件的操作表(方法集)
根据上面所讲:
struct file 中还有一个 f_op 指针,它指向⼀个 file_operations 结构体。这个file_operations即是一个方法集,如read、write等等。
而每个设备都提供了自身需求的对应方法集的实现。
这样便做到了在上层部分函数接口都是统一的,而在下层部分每个函数接口的实现方法都是不同的。
而上层不就是C++中实现多态的基类吗?下层不就是C++中实现多态的派生类。
输出结论:一切皆文件!是站在进程的视角,在struct file结构体之上,看待文件的视角,strcutfile的方法集是一样的,但访问方式是不同的,因为不同的设备有不同的实现方法。
总结
本文深入解析了Linux"一切皆文件"的设计哲学,重点剖析了文件描述符(FD)的核心机制。从进程task_struct中的files_struct结构出发,阐述了FD作为"数组下标"的本质特性及其分配规则。通过分析struct file和file_operations结构体,揭示了Linux如何通过统一接口(如read/write)抽象各类设备资源,实现不同设备的差异化操作。关键点在于:上层提供统一接口,下层由各设备实现具体操作,类似C++的多态机制,使进程能以统一视角访问异构资源。这种设计极大简化了开发,使开发者仅需一套API即可操作绝大多数系统资源。
更多推荐
所有评论(0)