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、前言
- 学习参考书籍:李山文的《Linux驱动开发进阶》
- 本文属于个人学习后的总结,不太具备教学功能。
- 之前写过一篇sysfs文件系统的应用,即如何创建/sys目录下的文件属性,可以参考:sysfs统一设备模型-CSDN博客
- 之前只知道内核根目录下有一个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框架
可以调整设备驱动的加载顺序。