Linux 文件系统与设备文件系统分析2

Linux 文件系统与设备文件系统分析2(基于Linux6.6)---sysfs 文件系统与Linux设备模型介绍

一、Linux 设备模型

1.1、设备模型概述

Linux 设备模型(Linux Device Model)是 Linux 内核管理硬件设备的框架,它提供了设备的抽象层,使得内核可以以统一的方式与硬件进行交互。设备模型的核心目的是简化设备驱动程序的编写,并为设备之间的交互提供统一的接口。

1. 设备模型的主要组成部分

Linux 设备模型的核心组成部分包括:

  • 设备(Device):表示硬件设备的实例,通常与硬件驱动程序(driver)关联。
  • 驱动程序(Driver):设备驱动程序负责对硬件设备进行初始化、管理和操作,驱动程序和设备通过设备模型关联。
  • 类(Class):用于表示设备类型的集合。一个类是多个设备的逻辑分类,类用于定义某一类设备共享的属性和操作。
  • 总线(Bus):总线表示设备与设备之间的连接。总线是硬件设备和设备驱动程序之间的桥梁,不同类型的设备可能通过不同的总线连接。
  • 设备节点(Device Node):设备文件在 /dev 目录中的表示。用户空间通过设备节点与设备进行交互。

2. 设备(Device)

设备是 Linux 设备模型中的基本单位。设备通常表示为 struct device 结构体,它包含了设备的相关信息,比如设备的状态、设备的驱动程序、设备的父设备、设备的子设备等。

设备结构体(struct device)示例:

struct device {
    const char *init_name;    // 设备的名字
    struct device_driver *driver;  // 设备驱动
    struct device *parent;    // 父设备
    struct device *children;  // 子设备列表
    struct device *bus;       // 总线设备
    void *driver_data;        // 驱动程序特定的数据
    ...
};

3. 设备驱动程序(Driver)

设备驱动程序是控制硬件设备的代码,负责设备的初始化、数据传输、硬件状态管理等。每个设备都有一个驱动程序,该驱动程序负责实现对设备的操作。

设备驱动程序与设备通过设备模型的结构体 struct device_driver 关联。设备驱动程序通常会定义一些操作函数(如 proberemovesuspendresume 等)来管理设备。

设备驱动程序结构体(struct device_driver)示例:

struct device_driver {
    const char *name;          // 驱动程序的名字
    int (*probe)(struct device *dev);  // 设备匹配时的初始化函数
    int (*remove)(struct device *dev); // 设备移除时的清理函数
    ...
};

4. 类(Class)

类用于表示一组功能相似的设备,它们共享相同的属性和操作接口。Linux 内核中有许多预定义的设备类,如字符设备、块设备等。类不仅仅是硬件设备的分类,它还提供了一些通用的接口和方法,用于操作这类设备。

类是通过 struct class 结构体定义的,struct class 结构体包含了类相关的信息和操作函数。

类结构体(struct class)示例:

struct class {
    const char *name;        // 类的名称
    struct device *dev_kobj; // 类的设备对象
    struct class_dev_iter *class_dev_iter; // 设备迭代器
    ...
};

5. 总线(Bus)

总线是连接设备和驱动程序之间的桥梁。一个总线可以包含多个设备,设备通过总线与内核中的设备模型交互。总线类型有很多种,例如 PCI 总线、USB 总线等。

在 Linux 内核中,总线是由 struct bus_type 结构体表示的,包含了总线的相关信息,以及与总线交互的操作函数。

总线结构体(struct bus_type)示例:

struct bus_type {
    const char *name;        // 总线的名称
    struct device *dev;      // 总线设备
    int (*match)(struct device *dev, struct device_driver *drv); // 匹配函数
    ...
};

6. 设备模型的工作流程

设备模型的工作流程主要分为以下几个步骤:

  1. 设备和驱动程序的注册

    • 当内核启动时,硬件设备信息会被探测和注册。驱动程序会根据设备信息进行匹配,找到合适的驱动并将其绑定到设备上。
  2. 设备匹配

    • 驱动程序会根据设备的属性与设备模型中的设备进行匹配。如果匹配成功,驱动程序会被加载并注册到设备上。
  3. 设备的初始化和管理

    • 驱动程序通过调用设备模型提供的接口来对设备进行初始化,配置设备,提供设备操作方法等。
  4. 设备的注销和清理

    • 当设备不再需要使用时,驱动程序会释放与设备相关的资源并将设备从设备模型中移除。

7. 设备模型中的 sysfs 接口

sysfs 是一个虚拟文件系统,用于提供内核与用户空间之间的交互接口。通过 sysfs,用户可以访问和修改设备的属性。每个设备、类、总线等都在 /sys 目录下有对应的条目,用户可以通过该接口与设备进行交互。

例如,设备的属性信息通常会通过 sysfs 提供,通过读取和写入文件,可以改变设备的状态或获取设备的状态。

1.2、设备模型结构

如表,Linux设备模型包含以下四个基本结构:

类型

所包含的内容

内核数据结构

对应/sys项

设备(Devices)

设备是此模型中最基本的类型,以设备本身的连接按层次组织

struct device

/sys/devices/*/*/.../

驱动

(Drivers)

在一个系统中安装多个相同设备,只需要一份驱动程序的支持

struct device_driver

/sys/bus/pci/drivers/*/

总线

(Bus)

在整个总线级别对此总线上连接的所有设备进行管理

struct bus_type

/sys/bus/*/

类别(Classes)

这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在/sys/class/input/下

struct class

/sys/class/*/


device、driver、bus、class是组成设备模型的基本数据结构。kobject是构成这些基本结构的核心,kset又是相同类型结构kobject的集合。kobject和kset共同组成了sysfs的底层数据体系。本节采用从最小数据结构到最终组成一个大的模型的思路来介绍。当然,阅读时也可先从Device、Driver、Bus、Class的介绍开始,先总体了解设备模型的构成,然后再回到kobject和kset,弄清它们是如何将Device、Driver、Bus、Class穿插链接在一起的,以及如何将这些映像成文件并最终形成一个sysfs文件系统。

二、Sysfs文件系统

sysfs是一个基于内存的文件系统,它的作用是将内核信息以文件的方式提供给用户程序使用

sysfs可以看成与proc,devfs和devpty同类别的文件系统,该文件系统是虚拟的文件系统,可以更方便对系统设备进行管理。它可以产生一个包含所有系统硬件层次视图,与提供进程和状态信息的proc文件系统十分类似。

sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间导出内核的数据结构以及它们的属性。sysfs的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括block,bus,drivers,class,power和firmware等.

Sysfs 是 Linux 内核中的一个虚拟文件系统,用于向用户空间提供内核对象(如设备、驱动程序、内核模块等)和它们的属性信息。它通过一个文件系统接口,使得用户空间程序能够访问和操作内核对象,并与内核进行交互。Sysfs 通过挂载在 /sys 目录下来提供这些信息和控制接口。

2.1、Sysfs 文件系统的作用和特点

  1. 内核信息暴露: Sysfs 使内核的内部信息得以通过文件系统暴露出来,用户可以读取和修改这些信息。例如,硬件设备的状态、驱动程序的配置信息、内核参数等都可以通过 Sysfs 访问。

  2. 设备和驱动管理: Sysfs 文件系统包含了有关系统设备的信息,包括设备树(device tree)、设备驱动、设备属性、总线等。这使得用户能够通过读取、写入文件来查询或修改硬件设备的状态,或加载、卸载驱动程序。

  3. 动态性: Sysfs 文件系统是动态的。它是内核通过创建和删除文件节点来动态管理内核对象。随着设备的插入和移除,Sysfs 中的目录和文件会相应地变化。

  4. 与用户空间交互: Sysfs 提供了一种内核与用户空间交互的方式。例如,用户可以通过写入文件来控制设备的行为,或者通过读取文件获取设备的状态。这种机制提供了比传统的 ioctl 或 /proc 文件系统更简单、更结构化的接口。

sysfs提供一种机制,使得可以显式的描述内核对象、对象属性及对象间关系。sysfs有两组接口,一组针对内核,用于将设备映射到文件系统中,另一组针对用户程序,用于读取或操作这些设备。下表描述了内核中的sysfs要素及其在用户空间的表现:

          sysfs在内核中的组成要素              

               在用户空间的显示         

内核对象(kobject)

目录

对象属性(attribute)

文件

对象关系(relationship)

链接(Symbolic Link)

sysfs目录结构:

/sys 下的子目录                   

所包含的内容

/sys/devices

这是内核对系统中所有设备的分层次表达模型,也是/sys文件系统管理设备的最重要的目录结构;

/sys/dev

这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件;

/sys/bus

这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;

/sys/class

这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在/sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;

/sys/kernel

这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于sysctl(/proc/sys/kernel) 接口中;

/sys/module

这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在/sys/module 中:

  • 编译为外部模块(ko文件)在加载后会出现对应的/sys/module/<module_name>/,并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;
  • 编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的/sys/module/<module_name>,这些模块的可用参数会出现在/sys/modules/<modname>/parameters/<param_name> 中,
    • 如/sys/module/printk/parameters/time这个可读写参数控制着内联模块printk在打印内核消息时是否加上时间前缀;
    • 所有内联模块的参数也可以由"<module_name>.<param_name>=<value>"的形式写在内核启动参数上,如启动内核时加上参数"printk.time=1"与向"/sys/module/printk/parameters/time"写入1的效果相同;
  • 没有非0属性参数的内联模块不会出现于此。

/sys/power

这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

三、深入理解 sysfs 文件系统

sysfs是一个特殊文件系统,并没有一个实际存放文件的介质。

3.1、kobject结构

sysfs的信息来源是kobject层次结构,读一个sysfs文件,就是动态的从kobject结构提取信息,生成文件

sysfs文件系统与kobject结构紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。

kobject与sysfs的关系

每当在内核中注册一个kobject对象时,内核会自动在sysfs中创建一个相应的目录。该目录中的每个文件通常对应于kobject对象的一个属性。用户可以通过这些文件来读取或修改kobject对象的属性。

具体来说,sysfs 通过以下步骤与 kobject 进行交互:

  1. kobject注册:当内核模块或设备驱动注册一个kobject时,会调用 kobject_init()kobject_add() 函数。这时,内核会为这个kobject对象分配一个唯一的目录,通常位于 /sys 目录下。
  2. 创建目录和属性:kobject可以通过 sysfs_create_group() 将一组属性(通过 kobject_attribute)添加到该目录中。每个属性通常对应于一个文件,用户可以通过读取或写入这些文件来访问或修改该属性。
  3. 属性文件操作:当用户空间的应用程序访问这些属性文件时,内核会调用相应的回调函数来处理读取或写入请求。
  4. kobject注销:当kobject不再需要时,可以调用 kobject_del() 函数来注销该对象,并在sysfs中删除对应的目录和属性文件。

3.2、sysfs 如何读写kobject 结构

sysfs就是利用VFS的接口去读写kobject的层次结构,建立起来的文件系统。 kobject的层次结构的注册与注销XX_register()形成的。文件系统是个很模糊广泛的概念, linux把所有的资源都看成是文件,让用户通过一个统一的文件系统操作界面,也就是同一组系统调用,对属于不同文件系统的文件进行操作。这样,就可以对用户程序隐藏各种不同文件系统的实现细节,为用户程序提供了一个统一的,抽象的,虚拟的文件系统界面,这就是所谓"VFS(Virtual Filesystem Switch)"。这个抽象出来的接口就是一组函数操作。
实现一种文件系统就是要实现VFS所定义的一系列接口,file_operations, dentry_operations, inode_operations等,供上层调用。

file_operations是描述对每个具体文件的操作方法(如:读,写);

dentry_operations结构体指明了VFS所有目录的操作方法;

inode_operations提供所有结点的操作方法。

举个例子,写C程序,open(“hello.c”, O_RDONLY),它通过系统调用的流程是这样的

open() -> 系统调用->  sys_open() -> filp_open()-> dentry_open() -> file_operations->open()          

不同的文件系统,调用不同的file_operations->open(),在sysfs下就是sysfs_open_file()。我们使用不同的文件系统,就是将它们各自的文件信息都抽象到dentry和inode中去。这样对于高层来说,我们就可以不关心底层的实现,我们使用的都是一系列标准的函数调用。这就是VFS的精髓,实际上就是面向对象。

注意sysfs是典型的特殊文件。它存储的信息都是由系统动态的生成的,它动态的包含了整个机器的硬件资源情况。从sysfs读写就相当于向 kobject层次结构提取数据

sysfs 是 Linux 内核提供的一种机制,用于通过文件系统将内核中的对象(kobject)暴露给用户空间,从而提供一种简单的接口用于查询和控制内核对象。kobject 结构是 Linux 内核中表示各种对象的基本数据结构,通过 sysfs,这些对象可以通过文件接口(通常位于 /sys 目录下)进行访问。

1. kobject 结构介绍

kobject 是内核对象的基本数据结构,代表一个内核中可管理的对象。一个 kobject 可以有多个属性(即与 kobject 关联的文件),这些属性可以是只读的、可写的,或者具有其他交互功能。sysfs 提供了对这些属性的访问。

kobject 结构体:

struct kobject {
    struct kset *kset;  // 父级对象的 kset
    struct list_head entry;  // 用于链表管理
    const char *name;  // 对象的名字
    struct sysfs_ops *ops;  // sysfs 操作函数
    struct attribute *default_attrs;  // 默认属性(sysfs文件)
    // 其他成员
};
  • name:表示对象的名称
  • kset:表示父对象的集合
  • ops:用于与 sysfs 交互的操作函数
  • default_attrs:定义与该对象相关联的属性

2. 如何通过 sysfs 读写 kobject

通过 sysfs 操作 kobject 结构,通常有两个主要的功能:读取和写入属性。

读取 kobject 属性

sysfs 中,读取属性会调用 kobj_attribute 结构的 show 函数。该函数负责将内核中某个变量或状态返回给用户空间。

例子:创建一个简单的内核模块,并通过 sysfs 读取 kobject 属性

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>

static struct kobject *example_kobj;
static int example_value = 42;

static ssize_t example_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    return sprintf(buf, "%d\n", example_value);
}

static struct kobj_attribute example_attr = __ATTR(example, 0444, example_show, NULL);

static int __init sysfs_example_init(void)
{
    int error;

    // 创建 kobject 对象
    example_kobj = kobject_create_and_add("example", kernel_kobj);
    if (!example_kobj)
        return -ENOMEM;

    // 创建 sysfs 属性文件
    error = sysfs_create_file(example_kobj, &example_attr.attr);
    if (error)
        kobject_put(example_kobj);

    return error;
}

static void __exit sysfs_example_exit(void)
{
    // 删除 sysfs 属性
    kobject_put(example_kobj);
}

module_init(sysfs_example_init);
module_exit(sysfs_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple sysfs example");

解释:

  • example_show:这个函数用于响应用户对 example 属性文件的读取请求,它将 example_value 的值(42)返回给用户空间。
  • example_attr:这是一个 kobj_attribute 结构体,定义了属性的名称 example,以及读取和写入操作(这里只实现了读取操作)。
  • kobject_create_and_add:创建一个 kobject,并将其添加到内核的 kernel_kobj 下(也就是 /sys/kernel/example)。
  • sysfs_create_file:将属性文件 example 创建并关联到 example_kobj 对象。

写入 kobject 属性

写入操作会调用 kobj_attribute 结构体的 store 函数。该函数将用户提供的数据解析并存储到内核的相应变量中。

例子:创建一个简单的内核模块,并通过 sysfs 写入 kobject 属性

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>

static struct kobject *example_kobj;
static int example_value = 42;

static ssize_t example_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    return sprintf(buf, "%d\n", example_value);
}

static ssize_t example_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    int ret;
    
    ret = sscanf(buf, "%d", &example_value);
    if (ret != 1)
        return -EINVAL;

    return count;
}

static struct kobj_attribute example_attr = __ATTR(example, 0664, example_show, example_store);

static int __init sysfs_example_init(void)
{
    int error;

    // 创建 kobject 对象
    example_kobj = kobject_create_and_add("example", kernel_kobj);
    if (!example_kobj)
        return -ENOMEM;

    // 创建 sysfs 属性文件
    error = sysfs_create_file(example_kobj, &example_attr.attr);
    if (error)
        kobject_put(example_kobj);

    return error;
}

static void __exit sysfs_example_exit(void)
{
    // 删除 sysfs 属性
    kobject_put(example_kobj);
}

module_init(sysfs_example_init);
module_exit(sysfs_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple sysfs example with write operation");

解释:

  • example_show:这个函数用于读取 example_value
  • example_store:此函数负责处理用户向 example 属性写入的数据。它将输入的数据解析为一个整数,并更新 example_value 的值。
  • 属性文件 example 被设置为 0664 权限,允许用户写入数据。

3. 通过 sysfs 交互的应用实例

  1. 动态调整内核参数:你可以通过 sysfs 实现一些内核参数的动态修改,比如修改调度器的参数、网络配置或者设备驱动中的配置选项。例如,通过 /sys/class/net/eth0/speed 文件读取和写入网络接口的速率。

  2. 设备控制:可以通过 sysfs 控制硬件设备。例如,/sys/class/block/sda/queue/rotational 可以用于查看或修改硬盘是否为旋转硬盘(HDD)或固态硬盘(SSD)。

  3. 内核调试:许多调试信息通过 sysfs 提供访问。例如,内核中可以通过 sysfs 显示硬件信息、设备状态,或查询内核模块的参数。

四、实例分析

4.1、sysfs文件open流程

1、sysfs 文件的打开流程

  1. 用户空间请求打开文件

    用户空间进程通过系统调用 open() 请求打开一个 sysfs 文件,例如:

$ open("/sys/kernel/my_device/my_value", O_RDONLY);

该请求会被传递到内核中,内核会处理文件系统操作,并找到对应的 sysfs 文件。

  • VFS(Virtual File System)调用

    在内核中,Linux 使用虚拟文件系统(VFS)作为所有文件系统的抽象层。VFS 会根据请求的路径(如 /sys/kernel/my_device/my_value)查找该路径对应的文件对象。对于 sysfs 文件系统,VFS 会将请求转发给 sysfs 文件系统的实现。

  • sysfs 文件的 inode 对应的操作

    在 Linux 文件系统中,每个文件都有一个对应的 inode(索引节点)。sysfs 文件也是如此,每个 sysfs 文件都有一个与之关联的 inodeinode 存储了文件的元数据以及文件操作的指针。

    sysfs 文件的 inode 结构中会设置与该文件相关的操作函数指针。sysfs 主要关注以下操作:

    当打开一个 sysfs 文件时,VFS 会调用 sysfs 文件的 open 操作。如果文件是一个普通的属性文件(通过 kobject 暴露的),则会调用 kobject 相关的回调函数。

    • open: 用来处理文件的打开操作。
    • read: 处理读取文件操作。
    • write: 处理写入文件操作。
    • release: 处理文件关闭操作。
  • sysfs 打开操作

    sysfs 文件系统中,open 操作会调用与该文件相关的 sysfs 结构的 open 回调函数,通常是一个类似于 sysfs_open() 的函数。

    对于 sysfs 文件,内核并不会真正进行文件内容的加载,而是通过 kobjectshowstore 操作来处理数据。这些回调函数实际上是用于读取或写入内核对象的属性。

static int sysfs_open(struct inode *inode, struct file *file)
{
    struct kobject *kobj = sysfs_get_kobject(inode);
    if (kobj == NULL)
        return -EINVAL;

    return 0;
}

这个函数会首先获取与该文件关联的 kobject,然后返回成功,表示文件被成功打开。sysfs_open 并不会实际执行数据的读取或写入,而只是为后续的操作做好准备。

  • 权限检查

    在打开文件时,内核会检查用户是否具有访问该文件的权限。例如,如果文件是只读的(0644),并且用户尝试进行写入操作,内核会拒绝该请求。

  • 关联文件操作回调

    sysfs 文件系统的每个文件都对应一组操作回调函数,例如 showstore。这些回调函数实际上负责数据的读写工作。

    例如,文件 /sys/kernel/my_device/my_value 的读取操作会调用 my_show() 函数,而写入操作则会调用 my_store() 函数。

    • 读取:当文件被打开并准备读取时,sysfs 会通过 show 回调函数读取文件内容,通常是从内核对象中提取数据并返回给用户空间。
    • 写入:当文件被打开并准备写入时,sysfs 会调用 store 回调函数,将用户空间的数据写入内核对象中。
  • 返回文件描述符

    如果 open() 请求成功,内核会返回一个文件描述符,用户空间可以通过该文件描述符对文件进行后续的操作(如读取、写入等)。此时,文件已经在进程的文件描述符表中注册,可以被后续的系统调用(如 read()write())使用。

  • 用户空间操作

    一旦文件被成功打开,用户空间就可以通过标准的文件操作接口(如 read()write()ioctl() 等)与内核交互。例如:

$ cat /sys/kernel/my_device/my_value

上述命令会触发 sysfsread 操作,进而调用与文件关联的 show 函数来获取数据。

  • 文件关闭

    文件在使用完后会被关闭。此时,内核会调用与文件关联的 release 函数。对于 sysfs 文件,release 操作通常是为了释放相关资源,例如减少引用计数。

static int sysfs_release(struct inode *inode, struct file *file)
{
    // 释放资源,减少引用计数
    return 0;
}

2、总结

sysfs 文件的 open 流程可以概括为以下几个步骤:

  1. 用户空间通过 open() 系统调用请求打开文件。
  2. VFS 将请求转发到 sysfs 文件系统。
  3. sysfs 根据文件路径找到对应的 inode,并查找文件的 open 操作。
  4. sysfs_open() 函数被调用,关联相应的 kobject
  5. 权限检查确保当前操作合法。
  6. 返回文件描述符,文件被成功打开。
  7. 用户空间通过 read()write() 等操作与内核交互。
  8. 文件关闭时,调用 release 函数释放资源。

4.2  sysfs文件read/write流程

sysfs 文件的读取(read)和写入(write)操作有特定的流程,涉及到内核如何处理这些操作以与内核对象进行交互。

1. sysfs 文件结构

sysfs 中的每个文件通常与内核对象(kobject)的一个属性相关联。这些属性通常是通过 sysfs 文件暴露给用户空间的,用户可以读取或者写入这些文件,从而获取或修改内核对象的状态。

sysfs 中,每个文件都与一个属性对象(kobj_attribute)绑定。kobj_attribute 定义了与该属性相关联的操作:读取操作 (show) 和写入操作 (store)。

2. sysfs 文件读取(read)流程

当用户空间进程对某个 sysfs 文件执行 read() 操作时,Linux 内核会按照以下流程处理:

(1)用户调用 read() 系统调用

用户空间进程通过调用 read() 系统调用请求读取某个 sysfs 文件的数据。read() 调用会传入文件描述符、缓冲区和读取的字节数。

(2)查找文件对应的 kobj_attribute

内核会根据文件路径找到对应的 kobj_attribute 结构体。这个结构体包含了与该文件对应的内核属性及其操作函数:showstore

(3)调用 show() 函数

内核通过 kobj_attribute 中的 show() 函数来处理读取操作。show() 函数负责将内核对象的状态或者数据复制到用户空间的缓冲区。通常,show() 函数会从内核对象中获取某个值(例如设备状态、属性等),并将其格式化后返回给用户空间。

static ssize_t my_device_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    // 返回设备的某些状态或数据
    return sprintf(buf, "%d\n", my_device_status);
}

(4)返回数据给用户空间

show() 函数将数据写入传入的 buf 缓冲区。然后,内核将缓冲区中的数据复制到用户空间,完成读取操作。此时,read() 系统调用返回读取的字节数。

3. sysfs 文件写入(write)流程

当用户空间进程对某个 sysfs 文件执行 write() 操作时,Linux 内核会按照以下流程处理:

(1)用户调用 write() 系统调用

用户空间进程通过调用 write() 系统调用写入数据到某个 sysfs 文件。write() 调用会传入文件描述符、缓冲区和写入的字节数。

(2)查找文件对应的 kobj_attribute

内核会根据文件路径找到对应的 kobj_attribute 结构体,类似于 read() 流程。

(3)调用 store() 函数

内核通过 kobj_attribute 中的 store() 函数来处理写入操作。store() 函数会将用户空间提供的数据解析并存储到内核对象中。通常,store() 函数会根据用户提供的数据更新内核对象的状态。

static ssize_t my_device_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
    // 更新设备的状态
    sscanf(buf, "%d", &my_device_status);
    return count;
}

(4)更新内核对象的状态

store() 函数会对传入的数据进行处理(例如转换为整数、浮点数或其他格式),并将其存储到内核对象的相应字段中。这通常涉及到修改内核中某个变量或设置某个硬件设备的配置。

(5)返回写入字节数

store() 函数返回实际写入的字节数,内核会将这个值作为 write() 系统调用的返回值,表示成功写入的字节数。

4. sysfs 中的 kobj_attribute 结构

kobj_attribute 是一个结构体,表示 sysfs 文件与内核对象属性之间的映射。它包含两个函数指针,showstore,分别用于处理读取和写入操作:

struct kobj_attribute {
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
};
  • attr:继承自 attribute,包含与文件相关的元数据,例如文件的权限、名称等。
  • show:处理 read() 操作的函数指针。
  • store:处理 write() 操作的函数指针。

5. 总结

  • 读取流程:用户通过 read() 系统调用读取 sysfs 文件时,内核查找对应的 kobj_attribute,调用 show() 函数获取数据,并将数据返回给用户空间。
  • 写入流程:用户通过 write() 系统调用向 sysfs 文件写入数据时,内核查找对应的 kobj_attribute,调用 store() 函数处理数据,并更新内核对象的状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值