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

Linux驱动开发进阶(二) - sysfs文件系统

文章目录

  • 1、前言
  • 2、kset、kobject、kobj_type
  • 3、统一驱动模型
    • 3.1、总线(bus)
    • 3.2、设备(device)
    • 3.3、驱动(driver)
  • 4、sysfs与热插拔
  • 5、子系统
  • 6、设备类
  • 7、总线与类的关系
  • 8、component框架

1、前言

  1. 学习参考书籍:李山文的《Linux驱动开发进阶》
  2. 本文属于个人学习后的总结,不太具备教学功能。
  3. 之前写过一篇sysfs文件系统的应用,即如何创建/sys目录下的文件属性,可以参考:sysfs统一设备模型-CSDN博客
  4. 之前只知道内核根目录下有一个sys文件夹,又或许我们都尝试过类似的命令如echo 1 > /sys/leds/led0/brightness来控制一个led。然而实际上,sysfs文件系统远比想象的重要,在后续学习spi子系统、i2c子系统、热拔插管理等之前,都应该先对sysfs文件系统有一个深入的理解,sysfs文件系统可以说是一切的根基。

2、kset、kobject、kobj_type

这里引用书中原文,“在Linux中使用kobject表示内核对象,而每个对象有不同的类型——kobj_type,要将这些内核对象联系起来就需要使用粘合剂——kset。每个kobject必须要有类型,即必须有kobj_type,而kset则用来设置每个kobject之间的关系。”

kobject、kobj_type、kset在内核分别有三个数据结构来描述,可以自行在kernel源码中查看。分别是struct kobject、struct kobj_type、struct kset。

在目前这个阶段,我个人觉得倒不需要对这三个核心对象有多么深的理解,我们只需要知道,这个三个核心对象可以通过相关接口函数在/sys目录下创建文件夹和文件。而/sys目录下的这些文件夹和文件又可以清晰表示出内核中存在的总线、设备、驱动、类。

所以可以把这三个核心对象比喻成砖头,而sysfs文件系统是由这些砖头构造出的房子。同时在后续的学习中,会看到kobject、kobj_type、kset的身影的,这里不用纠结。

3、统一驱动模型

在linux中,驱动模型被分为总线(bus)、设备(device)、驱动(driver)三个部分。至于为什么分为这三个部分和计算机的结构有关系。以usb设备为例,当一个usb设备插入电脑时,计算机会通知有设备插入,告知计算机设备类、设备属性以及做一些热插拔相关的事情等。然而计算机对于每一个usb设备都是这样一个流程,因此Linux将其抽象出为一个总线(bus)。在计算机对usb设备枚举结束后,会加载其对应的设备驱动程序,这里的驱动程序就是驱动(driver)。设备就是支持usb协议的设备,在Linux3.x之后,所有设备的描述都在设备树中完成。

3.1、总线(bus)

我们常说的总线设备驱动模型,总线就是指的如platform bus,spi bus,i2c bus。

总线bus在内核中的结构体叫struct bus_type,通过bus_register()来注册总线。

如下是自定义的一个总线:

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

static int bus_match(struct device *dev, struct device_driver *drv)
{
    printk(KERN_INFO"In %s \n", __func__);
    //match by driver name and device name
    return (strcmp(dev_name(dev), drv->name) == 0);
}

static int bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    printk(KERN_INFO "%s hotplug\n",dev_name(dev));
    return add_uevent_var(env, "MODALIAS=%s", dev_name(dev));
}

struct bus_type bus_test_type = {
    .name  = "bus-test",
    .match = bus_match,
    .uevent= bus_uevent,
};
EXPORT_SYMBOL(bus_test_type);

static int init_bus_test(void)
{
    int ret = 0;
    printk(KERN_INFO "init bus module!\n");
    ret = bus_register(&bus_test_type);
    if (ret) {
        printk(KERN_ERR "bus_register error!\n");
        return ret;
    }
    return ret;
}

static void exit_bus_test(void)
{
    bus_unregister(&bus_test_type);
    printk(KERN_INFO "exit bus module!\n");
}
module_init(init_bus_test);
module_exit(exit_bus_test);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("bust test");

3.2、设备(device)

设备device在内核中的结构体叫struct device,通过device_register()来注册设备。

如下是注册一个设备的示例程序,设备注册成功后,会在/sys/devices目录下创建一个名为“dev_test”的文件夹。

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

extern struct bus_type bus_test_type;

static void dev_test_release(struct device *dev)
{

}

static struct device dev_test = {
    .init_name  = "dev_test",
    .bus        = &bus_test_type,  		// 总线必须初始化,用来与驱动进行匹配
    .release    = dev_test_release,
};
	
static int __init init_dev_test(void)
{
	int ret = 0;
	
    ret = device_register(&dev_test);
    if(ret < 0)
    {
        printk(KERN_ERR "device register error!\n");
        return ret;        
    }
	
    return ret;
}

static void __exit exit_dev_test(void)
{

	device_unregister(&dev_test);
}

module_init(init_dev_test);
module_exit(exit_dev_test);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("device test");

3.3、驱动(driver)

驱动driver在内核中的结构体叫struct device_driver,通过driver_register()来注册设备。

如下是注册一个集合了总线、设备、驱动的示例程序:

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

static int bus_match(struct device *dev, struct device_driver *drv)
{
    printk(KERN_INFO"In %s \n", __func__);
    //match by driver name and device name
    return (strcmp(dev_name(dev), drv->name) == 0);
}

static int bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    printk(KERN_INFO "%s hotplug\n", dev_name(dev));
    return add_uevent_var(env, "MODALIAS=%s", dev_name(dev));
}

static int bus_probe(struct device *dev)
{
	return dev->driver->probe(dev);
    return 0;
}

static int bus_remove(struct device *dev)
{
    dev->driver->remove(dev);
	return 0;
}

struct bus_type bus_test = 
{
    .name  = "bus-test",
    .match = bus_match,
    .uevent= bus_uevent,
    .probe = bus_probe,
    .remove= bus_remove,
};
EXPORT_SYMBOL(bus_test);

static int driver_probe(struct device *dev)
{
    printk(KERN_INFO "driver probe!\n");
    return 0;
}

static int driver_remove(struct device *dev)
{
    printk(KERN_INFO "driver remove!\n");
    return 0;
}

static struct device_driver driver_test=
{
    .name = "driver_test",
    .bus  = &bus_test,
    .probe = driver_probe,
    .remove = driver_remove,
};

static void dev_test_release(struct device *dev)
{
    printk(KERN_INFO "device release!\n");
}

static struct device dev_test = {
    .init_name  = "driver_test",
    .bus        = &bus_test, 
    .release    = dev_test_release,
};

static int __init init_driver_test(void)
{
    int ret = 0;
    printk(KERN_INFO "init module!\n");
    ret = bus_register(&bus_test);
    if (ret) {
        printk(KERN_ERR "bus register error!\n");
        return ret;
    }
    ret = device_register(&dev_test);
    if(ret)
    {
        printk(KERN_ERR "device register error!\n");
        return ret;        
    }
    ret = driver_register(&driver_test);
    if (ret) {
        printk(KERN_ERR "driver register error!\n");
        return ret;
    }
    return ret;
}

static void __exit exit_driver_test(void)
{
    driver_unregister(&driver_test);
    device_unregister(&dev_test);
    bus_unregister(&bus_test);
    printk(KERN_INFO "exit module!\n");
}
module_init(init_driver_test);
module_exit(exit_driver_test);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("driver test");

通常,我们只会实现bus中的probe、remove函数,不会实现driver中的probe、remove函数。但在通常的印象中,我们在编写总线设备驱动时,不是一直都有实现自己的probe和remove函数吗?其实不然,我们只是在自己的结构体中实现了probe和remove,举例如下:

定义自己的driver结构体vm_test_driver,其中包含了device_driver、probe、remove:

实现自己的probe函数:

初始化vm_test_driver结构体:

在bus probe中调用vm_test_driver的probe:

再举个例子,如编写一个i2c设备驱动程序时,这里的i2c_driver也是一个自己的结构体:

4、sysfs与热插拔

Linux热插拔机制依赖于sysfs和devtmpfs这两个文件系统,其中sysfs最为重要。在上面的总线bus示例程序中,bus->uevent函数如下:

static int bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    printk(KERN_INFO "%s hotplug\n", dev_name(dev));
    return add_uevent_var(env, "MODALIAS=%s", dev_name(dev));
}

这里会将MODALIAS环境变量发送到用户空间,此时udev就可以监听到该值,同时可以通过相应的udev规则来执行相应的动作。具体的热插拔管理会在后续章节继续学习。

5、子系统

在linux中有很多子系统,如spi子系统,i2c子系统,led子系统等。在/sys/class目录下,一个文件夹就代表了一个子系统。相关结构体为struct subsys_private。所有的总线或类实际上就是一个子系统,例如当注册一个spi总线时,此时就会分配一个子系统结构体。

注册类时,也会分配一个subsys_private结构体:

下图展示了subsys_private与bus和class的关系(图片来自李山文的《Linux驱动开发进阶》):

6、设备类

如果有100个设备,那100设备的加入会使/sys/devices目录变得凌乱。为了更清晰的管理设备,linux内核引入了device class,即设备类。设备类可以把具有相同特性的设备归为一类,例如键盘鼠标触摸屏可以归为输入设备。

7、总线与类的关系

(图片来自李山文的《Linux驱动开发进阶》):

下面模拟类似的总线驱动、总线设备、总线子设备、设备、设备驱动之间的关系:

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

struct class  dummy_class = {
	.name		= "dummy_master",
	.owner		= THIS_MODULE,
};

static int bus_match(struct device *dev, struct device_driver *drv)
{
    printk(KERN_INFO"In %s \n", __func__);
    //match by driver name and device name
    return (strcmp(dev_name(dev), drv->name) == 0);
}

static int bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    int ret;
    printk(KERN_INFO "%s hotplug\n", dev_name(dev));
    ret = add_uevent_var(env, "THIS_TEST=%s", dev_name(dev));
    if(ret)
    {
        return ret;
    }
    return add_uevent_var(env, "DEMO=%s", "bus-class-test");
}

static int bus_probe(struct device *dev)
{
    return dev->driver->probe(dev);
    return 0;
}

static int bus_remove(struct device *dev)
{
    dev->driver->remove(dev);
	return 0;
}

struct bus_type dummy_bus_type = 
{
    .name  = "dummy_bus",
    .match = bus_match,
    .uevent= bus_uevent,
    .probe = bus_probe,
    .remove= bus_remove,
};
EXPORT_SYMBOL(dummy_bus_type);

static int driver_probe(struct device *dev)
{
    printk(KERN_INFO "driver probe!\n");
    return 0;
}

static int driver_remove(struct device *dev)
{
    printk(KERN_INFO "driver remove!\n");
	return 0;
}

static struct device_driver dummy_controller_drv=
{
    .name = "dummy_controller",
    .bus  = &dummy_bus_type,
    .probe = driver_probe,
    .remove = driver_remove,
};

static void dev_release(struct device *dev)
{
    printk(KERN_INFO "device release!\n");
}

static struct device dummy_controller_dev = {
    .init_name  = "dummy_controller",
    .bus        = &dummy_bus_type,
    .release    = dev_release,
};

static struct device dummy_0 = {
    .init_name  = "dummy_0",
    .release    = dev_release,
};


static struct device_driver dummy_device_drv=
{
    .name = "dummy_device",
    .bus  = &dummy_bus_type,
    .probe = driver_probe,
    .remove = driver_remove,
};

static struct device dummy_device_dev = {
    .init_name  = "dummy_device",
    .release    = dev_release,
};

static int __init init_test(void)
{
    int ret = 0;
    printk(KERN_INFO "init module!\n");
    ret = class_register(&dummy_class);
    if(ret)
    {
        printk(KERN_ERR "dummy_class register error!\n");
        return ret;
    }
    ret = bus_register(&dummy_bus_type);			// 注册总线(可以比喻成spi总线)
    if (ret) {
        printk(KERN_ERR "dummy_bus_type register error!\n");
        return ret;
    }
    ret = driver_register(&dummy_controller_drv);	// 注册控制器驱动(可以比喻成spi控制器驱动)
    if (ret) {
        printk(KERN_ERR "dummy_controller_drv register error!\n");
        return ret;
    }
    ret = driver_register(&dummy_device_drv);		// 注册设备驱动(可以比喻成spi设备驱动)
    if (ret) {
        printk(KERN_ERR "dummy_device_drv register error!\n");
        return ret;
    }
    ret = device_register(&dummy_controller_dev);	// 注册控制器设备(可以比喻成设备树中的spi控制器节点)
    if(ret)
    {
        printk(KERN_ERR "dummy_controller_dev register error!\n");
        return ret;        
    }
    dummy_0.class = &dummy_class;					
    dummy_0.parent=&dummy_controller_dev;
    ret = device_register(&dummy_0);				// 为后续添加的设备注册一个父设备(可以比喻成spidevX.X)
    if(ret)
    {
        printk(KERN_ERR "dummy_0 register error!\n");
        return ret;
    }
    dummy_device_dev.parent=&dummy_0;				
    dummy_device_dev.bus = &dummy_bus_type;
    dummy_device_dev.devt = MKDEV(103, 1);
    ret = device_register(&dummy_device_dev);		// 注册设备(可以比喻成设备树中的spi控制器节点下的spi设备节点)
    if(ret)
    {
        printk(KERN_ERR "dummy_device_dev register error!\n");
        return ret;
    }
    return ret;
}

static void __exit exit_test(void)
{
    device_unregister(&dummy_device_dev);
    device_unregister(&dummy_0);
    device_unregister(&dummy_controller_dev);
    driver_unregister(&dummy_device_drv);
    driver_unregister(&dummy_controller_drv);
    bus_unregister(&dummy_bus_type);
    class_unregister(&dummy_class);
    printk(KERN_INFO "exit module!\n");
}

module_init(init_test);
module_exit(exit_test);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("bus class test");

实现了一个总线,设备,驱动的简单例程,例如spi总线、spi控制器驱动、spi设备驱动合一的例程。

8、component框架

可以调整设备驱动的加载顺序。

相关文章:

  • html5炫酷的科技感3D文字效果实现详解
  • ROS多机通信(四)——Ubuntu 网卡 Mesh 模式配置指南
  • 拥抱成长型思维:解锁持续进步的人生密码
  • 于window环境下为tyora下载安装Pandoc
  • cpp11~17 新基础类型--long long
  • go常用标准库学习笔记
  • 3.22-UDP
  • 管家婆工贸ERP PR004.委外完工验收明细表
  • LeetCode 每日一题 2025/3/17-2025/3/23
  • 回顾Python基础语法,辨析和C++等的不同~
  • vulkanscenegraph显示倾斜模型(5.4)-相机操纵器
  • MCP(Model Context Protocol)好比大模型外挂!
  • 蓝桥杯C++基础算法-0-1背包
  • WEB PKI目前的问题
  • kotlin知识体系(三) : Android Kotlin 中的函数式编程实践指南
  • Docker学习笔记(十一)宿主机无法链接宿主机问题处理
  • UnoCSS极速入门:下一代原子化CSS引擎实战指南
  • 靶场(十五)---小白心得思路分析---LaVita
  • 【C++指针】搭建起程序与内存深度交互的桥梁(上)
  • Android LiveData 的 `setValue` 与 `postValue` 区别详解
  • 《一鸣惊人》五一特别节目:以戏曲为桥梁,展现劳动者的坚守
  • 北部艳阳高照、南部下冰雹,五一长假首日上海天气很“热闹”
  • 人物|德国新外长关键词:总理忠实盟友、外交防务专家、大西洋主义者
  • 事关广大农民利益,农村集体经济组织法5月1日起施行
  • 李铁案二审今日宣判,押送警车已进入法院
  • 王毅:携手做世界和平与发展事业的中流砥柱