当前位置: 首页 > article >正文

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下一切皆文件”的核心理解。
 


http://www.kler.cn/a/567111.html

相关文章:

  • AI编程Cursor之高级使用技巧
  • iOS for...in 循环
  • SpringBoot项目启动报错:PathVariable annotation was empty on param 0.
  • thinkphp下的Job队列处理
  • C语言多级指针详解 - 通过实例理解一级、二级、三级指针
  • day01_Java基础
  • 请谈谈 Node.js 中的流(Stream)模块,如何使用流进行数据处理?
  • Windows提权之第三方提权(九)
  • uniapp选中日期移动到中间
  • P8682 [蓝桥杯 2019 省 B] 等差数列--sort()
  • 宝塔webhooks与码云实现自动部署
  • 基于ArcGIS Pro、Python、USLE、INVEST模型等多技术融合的生态系统服务构建生态安全格局高阶应用
  • 【产品小白】怎么量化用户体验呢
  • 解锁 Hutool - Captcha:轻松打造图片验证码
  • 陕西省地标-DB61/T 1121-2018 政务服务中心建设和运营规范
  • Scanpy单细胞h5ad数据转化为Seurat对象
  • win11编译pytorchaudio cuda128版本流程
  • 【分库分表】基于mysql+shardingSphere的分库分表技术
  • JDK17安装方法/如何安装JDK17/环境变量配置
  • C++奇迹之旅:C++的单例模式