Linux设备驱动那些事

目的

  • 初步了解 linux 设备驱动框架模型
  • 初步了解设备驱动模型有哪些元素
  • 设备驱动模型元素的说明及解释
  • 设备驱动模型元素的工作原理
  • 设备驱动模型的小例子

对整体有个粗略的了解,设备驱动类型种类太多,不同的设备具有不同的特性,如果想完全掌握这块知识,不是一朝一夕的事情,更不是一两篇博客搞定的事情,但入门概念和设计思路一旦了解清楚,后续的工作将会大大加速。

概述

说到 linux 设备驱动模型,有几个概念不得不提

  • bus
  • class
  • device
  • driver

我们使用设备和驱动的场景不同,就要求我们从多角度的视角去理解它们。有的是为了管理上方便,维护上方便,易读性,所以才有了上述的概念。一个驱动可能对应多个设备,一个设备(拥有多种能力)也可能对应多个驱动。驱动和设备又可以分类,设备与驱动又可以通过 bus 集中管理

bus 的由来

设备种类异常繁多,不同厂商同一类设备还有不同版本;潮起潮落,有的老厂商死去,有新厂商进来,要面对这么多复杂的变化,对应代码的维护是非常有挑战性的。早期的 linux 把设备驱动维护全部放入内核。由于设备的变化,内核对应的代码也不得不做出同样的变化,这给内核的稳定性,维护性,易用性提出了极大的挑战。所有问题都是因为驱动要拿到设备的基地址、中断号,这些信息针对不同的设备是不断变化的。早期的驱动基本上是针对固定硬件的,这些信息都是 hard code 到驱动里面的,一旦设备有新增或更改,对应的驱动部分也需要进行调整,这就造成了驱动和设备的耦合性太强,为了解决这个问题,需要给驱动和设备加一个适配层对其之间的关系进行解耦,这一层就是 bus 的由来。bus 负责 device 和 driver 的管理,解耦了 device 和 driver 的绑定关系。device 可以动态的把信息注册到 bus 上,driver 同样也是需要注册到 bus 上,这样做的好处是 driver 可以动态的拿取对应的设备的基地址、中断号,这样无论设备怎么变化,驱动所对应的实现基本上不需要更改了。同时 bus 也具有天然的分类功能,成千上万的设备,层出不穷,我们集中管理非常不方便,效率低下,有了bus 就具有了天然的对设备进行分组的概念,益处是显而易见的。同时,现在的硬件体系很多设备是挂在对应的总线上,也符合理解的习惯,硬件体系中还有很多设备并没有挂到真正的总线上,对于这类设备的管理系统会虚拟出一条总线对它们进行统一管理,这就是 platform bus 的由来。下面再提一下三种特殊的 bus

  • 三种特殊的 bus ( 这篇文章写的比较好,下面的一小段话,直接摘抄过来了,对此表示感谢。 原文链接:https://blog.csdn.net/yangjizhen1533/article/details/111590311
    分别是 system bus、virtual bus 和 platform bus。它们并不是一个实际存在的bus(像USB、I2C等),而是为了方便设备模型的抽象,而虚构的。
  1. system bus 是旧版内核提出的概念,用于抽象系统设备(如CPU、Timer等等)。而新版内核认为它是个坏点子,因为任何设备都应归属于一个普通的子系统(New subsystems should use plain subsystems,
    drivers/base/bus.c, line 1264),所以就把它抛弃了(不建议再使用,它的存在只为兼容旧有的实现)。
  2. virtaul bus 是一个比较新的bus,主要用来抽象那些虚拟设备,所谓的虚拟设备,是指不是真实的硬件设备,而是用软件模拟出来的设备,例如虚拟机中使用的虚拟的网络设备。
  3. platform bus 就比较普通,它主要抽象集成在CPU(SOC)中的各种设备。这些设备直接和CPU连接,通过总线寻址和中断的方式,和CPU交互信息。
  • bus 的作用
    任何 bus 维护两个表,一个是设备链表,一个是驱动链表。bus 负责把设备和驱动进行匹配关联,只有得到正确关联的设备和驱动,才能正常工作。设备和驱动的匹配,其实就是根据 bus 的 match 接口进行,一旦匹配成功就会调用 probe 接口,在probe函数中实现设备的初始化、各种配置以及生成用户空间的文件接口。 probe 函数是总线在匹配成功时调用的函数,bus->probe 和 drv->probe 中只会有一个起效,同时存在时使用 bus->probe

bus 在系统中的体现就是 /sys/bus 目录,/sys 目录是个非常有用的目录,对于 bus, class, device, driver 均有体现,方便维护与理解。

class 的由来

其实就是根据人们的日常习惯对设备进行分类的一种表现形式,比如:键盘和鼠标我们都看成输入设备,属于输入设备分类。此外还有 net 网络设备分类,block 块设备分类,tty 设备分类。就是符合人们生活习惯的一种分类方式。这些分类相同的设备,具有很多共同特性,非常符合面向对象设计思想,这样就可以设计出基类对象,设备或驱动的编写都可以基于这些基础对象类进一步扩展,大家有了这个天然的约定,理解和维护都非常方便。

class 在系统中的体现就是 /sys/class 目录

device 设备

这里的 device 就是指的硬件设备,无需过多理解。这里说一下小典故,早期的设备代码都是耦合到内核代码内的,对于嵌入式设备,这些设备的适配给内核带来非常大的问题,内核 OMAP development tree 的维护者,发送了一封邮件给 Linus,请求提交 OMAP 平台代码修改,并附带修改以及如何解决 merge conficts,鉴于此原因还引起了 Linus 的怒吼 “Gaah.Guys, this whole ARM thing is a f*cking pain in the ass.” 对于此问题的解决,就是后来的设备树 devicetree,设备树的优点不言而喻。

  • 设备树的优点
  1. 实现驱动代码与设备硬件信息相分离。
  2. 通过被 bootloader(uboot) 和 Linux 传递到内核, 内核可以从设备树中获取对应的硬件信息。
  3. 对于同一 SOC 的不同主板,只需更换设备树文件即可实现不同主板的无差异支持,而无需更换内核文件,实现了内核和不同板级硬件数据的拆分。
    设备树的出现,让内核摆脱由于设备的改变,内核也必须随之进行变动的问题,带来极大的好处( 具体带来哪些好处,需要读者自己揣摩了 )
  • 设备树的加载
    dts -> dtb -> device_node -> platform_device
    上述流程网上有大量的资料进行解释,经过上述流程,系统启动后,就有了对应的设备节点,有了设备节点,利用驱动就可以驱动这些设备进行工作了。

  • x86 硬件设备的加载 ( 对于 x86/64 的硬件加载这篇文章写的比较好,下面的一小段话,间接抄过来了,对此表示感谢。https://www.zhihu.com/question/475730584/answer/2035883968 )
    设备树是因为嵌入式场景而导入的,对于 x86/64 上许多设备是基于 PCI/PCIe 总线的,可以自动枚举,这种设备不需要设备树来告知系统. 当然也有一些设备是无法枚举或自发现的,但这是另一波人维护的内核代码,他们使用的叫平台设备(platform)。x86系统的硬件信息保存在 2 个地方,ACPI 和 SMBIOS 。在x86启动阶段,操作系统会从 BIOS 芯片上的 ACPI 表的读出硬件信息,但是这部分硬件信息相对设备树中的简单,现在的 x86 芯片所有的外部总线都是挂载到 PCI 总线上, PCI 提供了自枚举的功能,操作系统无法枚举时,也可以交由 APCI 枚举. ACPI 数据不能随意更改,而 SMBIOS 数据内容在系统启动时会被更新。一些系统监控,底层功能扩展,性能优化软件更倾向于从SMBIOS中获取所需数据,Linux则提供了 dmidecode 命令可以 dump 出 SMBIOS 的所有数据。包括 ARM,RISCV等其他平台近年同样引入了对 ACPI 和 SMBIOS 的支持。
    设备启动 -> 系统固件 ( BIOS 设置、初始化和自检 ) -> 系统固件使用固件初始化期间获得的信息,根据需要使用各种平台配置和电源接口数据更新ACPI表 -> 引导程序 -> 启动内核 -> 内核从 ACPI 表读取设备信息并加载驱动

系统启动过程分析,下面这篇文章不错 https://www.jianshu.com/p/35fb17ad825e

device 在系统中的体现就是 /sys/devices 目录

driver 驱动

这里的 driver 就是指硬件的驱动程序,通过上述介绍,我们知道 bus 管理 device 和 driver。bus 就像牵线的红娘,驱动 device 和 driver 进行匹对,配对原则很多,主要的是根据驱动的名字和设备的名字进行匹对。匹配方式很多,方法也各有不同,网上有很多相对应的文章对此进行介绍,想深入研究的同学可以搜索一下,进行深入研究。一旦匹配成功,进一步就是调用驱动的 probe 接口进行进一步确认。一旦匹配成功,我们的应用层程序就可以操作对应的设备了。

driver 在系统中的体现就是 /sys/module/ 下的一个子目录, /sys/module 下加载了所有的驱动模块( 包括内核 builtin 的模块 ),总线模块,设备模块

怎么表达上述概念

其实上述的四个概念 bus, class, device, driver 之间的关系相当复杂,一个设备属于某个 bus, 同时按分类又属于某个 class, 按功能设备对接一个或多个驱动;驱动对象也存在类似的关系,为了更好的表达这些关系,需要一些高度抽象的设计,把这些对象的公共部分提取出来,这就是 kobject, kset, ktype 对象的由来,这些结构的抽象对于 linux 设备驱动管理奠定了夯实的基础。

上述的分类概念,就类似一个中年男人,在家里针对不同的家庭成员, 可能是儿子,丈夫,父亲的角色;在公司则是员工,同事,组长,为了更精确的描述这个人的不同角色,才有了儿子,丈夫,父亲,员工,同事,组长的这些词,同理 bus, class 的也是同样的设计理念,为了描述男人这个对象这么复杂的关系图谱,需要很精巧的数据结构配合,这就是 kobject, kset, ktype。 家庭,公司可以用 kset 表示, 男人可以用 kobject, 男人的角色属性(儿子,丈夫,父亲,员工 ... )用 ktype 进行表示。网上很多文章花了很多 kobject, kset, ktype 的关系图,如果不真正理解它的涵义,很容易被绕晕,其实就是图的表达,这张图由多个树 tree 组成,树的组成则是由同一个对象在由于角色关系形成的。明白这个道理 kobject, kset, ktype 基本上就能理解此结构的最终涵义!

kobject 对象

从面向对象的思路来看,上述的概念实例化后都可以看成一个对象,对于一个对象就肯定有基本的属性,比如:名字,状态,引用计数等。如果不对基类对象做基本定义,就像盖房子,用的砖五花八门,大小不一样,颜色不一样,底层的不统一,导致上层的结构的不可靠。比如:对于一个设备定义,可能出现如下定义

// 设备定义 1
struct device1 {
    const char         *mingzi;                 // 中文拼音    
};

// 设备定义 2
struct device2 {
    const char         *dev_name;               // 简写
} ;

// 设备定义 3
struct device3 {
    const char         *usb_name;               // 根据分类定义
} ;

其实上述结构定义的目的,就是定义一个 name 字段,一个字段就能搞定;由于每个人的理解能力,知识深度,地域文化差异,可能有上千种定义方式;这样的话,所有建立此代码的工程应用都高度耦合这段结构,维护工作量巨大,通用性也极差。由于设备的复杂性,属性字段更多,对于每个字段各种奇葩的定义都有可能发生。这样设备驱动的代码又是各自为战,高耦合的问题还是没有真正解决。要解决此问题,就必须有个基础对象的规范定义,这就是 kobject 对象的由来。下面看看 kobject 的定义,定义如下:

struct kobject {
    const char		    *name;                  // kobject的名字,且作为一个目录的名字
    struct list_head	entry;                  // 连接下一个kobject结构
    struct kobject		*parent;                // 指向父亲kobject结构体,如果父设备存在
    struct kset		    *kset;                  // 指向kset集合
    struct kobj_type	*ktype;                 // 指向kobject的属性描述符
    struct sysfs_dirent	*sd;                    // 对应sysfs的文件目录
    struct kref		    kref;                   // kobject的引用计数
    unsigned int        state_initialized:1;    // 表示该kobject是否初始化
    unsigned int        state_in_sysfs:1;       // 表示是否加入sysfs中
    unsigned int        state_add_uevent_sent:1;
    unsigned int        state_remove_uevent_sent:1;
    unsigned int        uevent_suppress:1;
};

针对上述结构,我们看到 kobject 包含了最基础的属性,有链表表达,父节点表达,集合表达,类型表达,引用计数表达。这些公共属性,对于 bus, class, device, driver 的划分提供了基础支撑。

kset 对象

kset 的根据名字就能猜出它的目的,就是方便把设备驱动进行管理的一个对象模型。kset是包含多个 kobject 的集合;如果需要在 sysfs 的目录中包含多个子目录,那需要将它定义成一个 kset;kset 结构体中包含 struct kobject 字段,可以使用该字段链接到更上一层的结构,用于构建更复杂的拓扑结构,整个 sys 目录其实就是利用 kset 进行组织的。利用这种对象模型,可以表达出不同角度的对象组织方式。这样理解 kset 比较形象,就是对应 /sys 下的一个目录节点。

ktype 对象

ktype 用于表征 kobject 的类型,指定了删除 kobject时要调用的函数, kobject 结构体中有 struct kref 字段用于对 kobject 进行引用计数,当计数值为 0 时,就会调用 kobj_type 中的 release 函数对 kobject 进行释放,这个就有点类似于C++中的智能指针了, ktype 是 kobject 基本方法的实现,主要是一套属性及文件系统的操作方法

struct kobj_type {
        void (*release)(struct kobject *kobj);
        const struct sysfs_ops *sysfs_ops;      // 文件系统的操作方法
        struct attribute **default_attrs;       // use default_groups instead
        const struct attribute_group **default_groups;
        const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
        const void *(*namespace)(struct kobject *kobj);
        void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};

有关讲解 kobject, kset, ktype 相关的文章相当多,这篇也不错 https://www.eet-china.com/mp/a42768.html

struct bus_type {
        const char                      *name;
        const char                      *dev_name;
        struct device                   *dev_root;
        const struct attribute_group    **bus_groups;
        const struct attribute_group    **dev_groups;
        const struct attribute_group    **drv_groups;

        int (*match)(struct device *dev, struct device_driver *drv);
        int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
        int (*probe)(struct device *dev);
        void (*sync_state)(struct device *dev);
        int (*remove)(struct device *dev);
        void (*shutdown)(struct device *dev);
        int (*online)(struct device *dev);
        int (*offline)(struct device *dev);
        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);
        int (*num_vf)(struct device *dev);
        int (*dma_configure)(struct device *dev);

        const struct dev_pm_ops         *pm;
        const struct iommu_ops          *iommu_ops;
        struct subsys_private           *p;                   // 这个结构体很重要
        struct lock_class_key           lock_key;
        bool                            need_parent_lock;
};

// ------------------------------------------------------------------------------------
// 驱动核心的私有数据,除了驱动核心能处理它,其余一概不能对此结构进行访问与处理 !!!
// ------------------------------------------------------------------------------------
struct subsys_private {
        struct kset                     subsys;
        struct kset                     *devices_kset;        // 代表bus目录下的 device 的子目录
        struct list_head                interfaces;
        struct mutex                    mutex;
        struct kset                     *drivers_kset;        // 代表bus目录下的 driver 的子目录
        struct klist                    klist_devices;        // 是 bus 的设备链表
        struct klist                    klist_drivers;        // 是 bus 的驱动链表
        struct blocking_notifier_head   bus_notifier;
        unsigned int                    drivers_autoprobe:1;
        struct bus_type                 *bus;
        struct kset                     glue_dirs;
        struct class                    *class;
};

上述结构表明了 kset 基本上等同一个 /sys 下的目录节点

struct kset {
        struct list_head                list;
        spinlock_t                      list_lock;
        struct kobject                  kobj;                // kset 结构也包含一个 kobject 对象,可以链接到上层对象,组织出更复杂的拓扑结构
        const struct kset_uevent_ops    *uevent_ops;
} __randomize_layout;

我们再看看 class 的定义

struct class {
        const char                      *name;
        struct module                   *owner;
        const struct attribute_group    **class_groups;
        const struct attribute_group    **dev_groups;
        struct kobject                  *dev_kobj;

        int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
        char *(*devnode)(struct device *dev, umode_t *mode);

        void (*class_release)(struct class *class);
        void (*dev_release)(struct device *dev);

        int (*shutdown_pre)(struct device *dev);

        const struct kobj_ns_type_operations *ns_type;
        const void *(*namespace)(struct device *dev);

        void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);

        const struct dev_pm_ops         *pm;
        struct subsys_private           *p;
};

device 的结构定义

struct device {
        struct kobject                  kobj;
        struct device                   *parent;
        struct device_private           *p;
        const char                      *init_name;             /* initial name of the device */
        const struct device_type        *type;
        struct bus_type                 *bus;                   /* type of bus device is on */
        struct device_driver            *driver;                /* which driver has allocated this device */
        void                            *platform_data;         /* Platform specific data, device core doesn't touch it */
        void                            *driver_data;           /* Driver data, set and get with dev_set_drvdata/dev_get_drvdata */
#ifdef CONFIG_PROVE_LOCKING
        struct mutex                    lockdep_mutex;
#endif
        struct mutex                    mutex;                  /* mutex to synchronize calls to its driver. */
        struct dev_links_info           links;
        struct dev_pm_info              power;
        struct dev_pm_domain            *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
        struct irq_domain               *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
        struct dev_pin_info             *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
        struct list_head                msi_list;
#endif
        const struct dma_map_ops        *dma_ops;
        u64                             *dma_mask;              /* dma mask (if dma'able device) */
        u64                             coherent_dma_mask;      /* Like dma_mask, but for alloc_coherent mappings as not all hardware 
                                                                supports 64 bit addresses for consistent allocations such descriptors. */
        u64                             bus_dma_limit;          /* upstream dma constraint */
        unsigned long                   dma_pfn_offset;
        struct device_dma_parameters    *dma_parms;
        struct list_head                dma_pools;              /* dma pools (if dma'ble) */
#ifdef CONFIG_DMA_DECLARE_COHERENT
        struct dma_coherent_mem         *dma_mem;               /* internal for coherent mem override */
#endif
#ifdef CONFIG_DMA_CMA
        struct cma                      *cma_area;              /* contiguous memory area for dma allocations */
#endif
        /* arch specific additions */
        struct dev_archdata             archdata;
        struct device_node              *of_node;               /* associated device tree node */
        struct fwnode_handle            *fwnode;                /* firmware device node */
#ifdef CONFIG_NUMA
        int                             numa_node;              /* NUMA node this device is close to */
#endif
        dev_t                           devt;                   /* dev_t, creates the sysfs "dev" */
        u32                             id;                     /* device instance */
        spinlock_t                      devres_lock;
        struct list_head                devres_head;
        struct class                    *class;
        const struct attribute_group    **groups;               /* optional groups */
        void (*release)(struct device *dev);
        struct iommu_group              *iommu_group;
        struct dev_iommu                *iommu;
        bool                            offline_disabled:1;
        bool                            offline:1;
        bool                            of_node_reused:1;
        bool                            state_synced:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
        bool                            dma_coherent:1;
#endif
};

driver 的结构定义

struct device_driver {
        const char                      *name;
        struct bus_type                 *bus;

        struct module                   *owner;
        const char                      *mod_name;              /* used for built-in modules */
        bool                            suppress_bind_attrs;    /* disables bind/unbind via sysfs */
        enum probe_type                 probe_type;
        const struct of_device_id       *of_match_table;
        const struct acpi_device_id     *acpi_match_table;

        int (*probe) (struct device *dev);
        void (*sync_state)(struct device *dev);
        int (*remove) (struct device *dev);
        void (*shutdown) (struct device *dev);
        int (*suspend) (struct device *dev, pm_message_t state);
        int (*resume) (struct device *dev);
        const struct attribute_group **groups;
        const struct attribute_group **dev_groups;

        const struct dev_pm_ops         *pm;
        void (*coredump) (struct device *dev);

        struct driver_private           *p;
};

struct driver_private {
        struct kobject kobj;
        struct klist klist_devices;
        struct klist_node knode_bus;
        struct module_kobject *mkobj;
        struct device_driver *driver;
};

bus ---> p ( struct subsys_private ) ---> subsys ( struct kset ) ---> kobj
class ---> p ( struct subsys_private ) ---> subsys ( struct kset ) ---> kobj
device ---> kobj
driver ---> p ( struct driver_private ) ---> kobj

综上所述我们的对象基本信息可以通过 kobject 对象进行表示,对象的基本操作方法和属性可以通过 ktype 对象进行表示,对象的组织(链表,树)可以通过 kset 进行表示,这是整个 linux 设备驱动管理模型的基石,是经验的总结。

sysfs

"sysfs is a ram-based filesystem initially based on ramfs. It provides a means to export kernel data structures, their attributes, and the linkages between them to userspace.” --- documentation/filesystems/sysfs.txt
sysfs 是基于 ram 运行的一种文件系统,类似于 /proc 目录。在系统内对应的就是 /sys 目录。就是内核为用户空间提供了设备的一些存取方式,提供了操作接口。通俗的说,我们通过 /sys 目录可以非常清晰的看到系统对设备管理的整体布局,同时也可以对设备进行读写操作。关于 /sys 目录,上述也有零星提及,主要就是这些目录,这里就不大篇幅的展开了。 /sys/bus, /sys/module, /sys/devices, /sys/class, /sys/firmware ( acpi/smbios )

系统启动过程中,会利用下述命令,对 sysfs 进行挂载,挂载点就是 /sys 目录,因此 /sys 就是 sysfs 的具体体现。

mount -t sysfs sysfs /sys

udev

udev 是 linux kernel 的设备管理器,在最新的内核版本中 kernel_3.10 中 udev 已经代替了 devfs、hotplug 等功能。它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。
udev是硬件平台无关的,属于 user space 的进程,它脱离驱动层的关联而建立在操作系统之上,基于这种设计实现,可以动态修改及删除 /dev 下的设备文件名称和指向,按照我们的设计目的安排和管理设备文件系统,而完成如此灵活的功能只需要简单地修改 udev的配置文件即可,无需重新启动操作系统,udev 是通过 netlink socket 一种套接字与系统进行通信的,大大简化和解耦了应用层的设备管理工作。关于 udev 是个庞大的知识集,规则,工具,这里只是做一个点引,更深入的学习,需要翻阅更专业的资料。

下面几个工具,如果自己做操作系统是需要了解一下的,方便系统启动后让设备能正常加载,保证应用层的正常运行。

  • udevd —— 作为deamon,记录hotplug事件,然后排队后再发送给udev,避免事件冲突(race conditions)。
  • udevtrigger —— 扫描sysfs文件系统,生成相应的硬件设备hotplug事件。
  • udevsettle —— 查看udev事件队列,等队列内事件全部处理完毕才退出。

对于系统启动后 init 脚本,一般都是类似于下面的处理

mkdir -p /dev/.udev/db 
udevd --daemon 
mkdir -p /dev/.udev/queue 
udevtrigger 
udevsettle 

说到 udev 不得不提到 mdev ,mdev 相对于 udev 简单很多,基本功能一样,不过它们的底层运行原理是不一样的。mdev 也是提供了设备管理的功能,具体大家可以查看相关的资料进行深入学习

uevent 机制

设备的 hotplug 是操作系统运行过程,比较常见的操作,属于基本运行的刚性需求。这就避免不了系统需要把这些信息动态通知到用户层,这就是 uevent 机制。uevent 整个机制相对比较清晰,当有新的 device 产生时,kobjct 产生 uevent 事件,通过两种方式通知到用户层 netlink 和 uevent_helper,在用户空间 linux 提供了两个机制一个是 udev( user device ) 通过 nelink 接收 uevent 产生的事件,另外一种方式是通过 uevent_helper,该方式主要是通过回调方式通知用户层。到这里我们就似乎想到了点什么,就是刚刚提到的 udev 和 mdev。

整个设备添加通知流程如下:(重要)

                                                                               |---> kobject_uevent_net_broadcast ( netlink 方式 ) ---> uevent_net_broadcast_untagged ---> netlink_broadcast

device_register ---> device_add ---> kobject_uevent ---> kobject_uevent_env ---|

                                                                               |---> call_usermodehelper ( uevent_helper 方式 )


各位同学可以从网上找一下相关 udev 通知的小例子,结合下面的例子进行配合运行,方便对整套系统的运作原理提供非常大的帮助!

小例子

例子分为三部分 bus, device, driver,需要更改 Makefile 里面的路径,如果有必要的话。class 的演示没有加入里面,如果有兴趣的同学可以试一试。网上还有很多有关 udev 的小例子,配合下面的例子,一块运行,对于理解 linux 设备驱动管理模型有很大帮助。如果想查看结果,首先编译这三个模块,其次 insmod my_bus.ko && insmod my_device.ko && insmod my_driver.ko,最后可以对这些节点进行读写,具体可以加入打印信息,利用 dmesg 进行查看

[root@localhost bus]# tree my_bus/
my_bus/
├── devices
│   └── my_dev -> ../../../devices/my_dev
├── drivers
│   └── my_dev
│       ├── bind
│       ├── my_dev -> ../../../../devices/my_dev
│       ├── rwstate
│       ├── uevent
│       ├── unbind
│       └── version
├── drivers_autoprobe
├── drivers_probe
├── rwstate
├── uevent
└── version

5 directories, 10 files

[root@localhost devices]# tree my_dev/
my_dev/
├── driver -> ../../bus/my_bus/drivers/my_dev
├── power
│   ├── async
│   ├── autosuspend_delay_ms
│   ├── control
│   ├── runtime_active_kids
│   ├── runtime_active_time
│   ├── runtime_enabled
│   ├── runtime_status
│   ├── runtime_suspended_time
│   └── runtime_usage
├── rwstate
├── subsystem -> ../../bus/my_bus
├── uevent
└── version

3 directories, 12 files

[root@localhost drivers]# tree my_dev/       
my_dev/
├── bind
├── my_dev -> ../../../../devices/my_dev
├── rwstate
├── uevent
├── unbind
└── version

1 directory, 5 files

其中 rwstate 都是可读写节点,我们可以利用 echo -n "bus write test" > /sys/bus/my_bus/rwstate 进行测试写入,也可以利用 cat /sys/bus/my_bus/rwstate 进行查看;每个对象都有一个只读属性 version,利用命令 cat 也可以查看对象的版本信息

  • my_bus 模块
    my_bus.c 源文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

//------------------------------------------------------------------------------------------------
static char* my_bus_name = "my_bus";
static char my_bus_rwbuf[128] = "readwrite-default";

static ssize_t bus_attrs_version_show(struct bus_type* bus, char* buf)
{
    return snprintf(buf, PAGE_SIZE, "%s: version 1.0.0\n", my_bus_name);
}

static ssize_t bus_attrs_rw_show(struct bus_type* bus, char* buf)
{
    return snprintf(buf, PAGE_SIZE, "%s: %s\n", my_bus_name, my_bus_rwbuf);
}

static ssize_t bus_attrs_rw_store(struct bus_type* bus, const char* buf, size_t count)
{
    if ( count >= 128 ) {
        count = 128;
    }
    if ( buf ) {
        memcpy(my_bus_rwbuf, buf, count);
        my_bus_rwbuf[count] = 0;
    }
    return count;
}

static BUS_ATTR(version, S_IRUGO, bus_attrs_version_show, NULL);
static BUS_ATTR(rwstate, (S_IRUGO | S_IWUGO), bus_attrs_rw_show, bus_attrs_rw_store);

static struct attribute* my_bus_attrs[] = {
    &bus_attr_version.attr,
    &bus_attr_rwstate.attr,
    NULL,
};

//------------------------------------------------------------------------------------------------
static char* my_device_name = "my_device";
static char my_device_rwbuf[128] = "readwrite-default";

static ssize_t device_attrs_version_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    return snprintf(buf, PAGE_SIZE, "%s: version 1.0.0\n", my_device_name);
}

static ssize_t device_attrs_rw_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    return snprintf(buf, PAGE_SIZE, "%s: %s\n", my_device_name, my_device_rwbuf);
}
static ssize_t device_attrs_rw_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    if (count >= 128) {
        count = 128;
    }
    if (buf) {
        memcpy(my_device_rwbuf, buf, count);
        my_device_rwbuf[count] = 0;
    }
    return count;
}

static DEVICE_ATTR(version, S_IRUGO, device_attrs_version_show, NULL);
static DEVICE_ATTR(rwstate, (S_IRUGO | S_IWUGO), device_attrs_rw_show, device_attrs_rw_store);

static struct attribute* my_dev_attrs[] = {
    &dev_attr_version.attr,
    &dev_attr_rwstate.attr,
    NULL,
};

//------------------------------------------------------------------------------------------------
static char* my_driver_name = "my_driver";
static char my_driver_rwbuf[128] = "readwrite-default";

static ssize_t driver_attrs_version_show(struct device_driver* driver, char* buf)
{
    return snprintf(buf, PAGE_SIZE, "%s: version 1.0.0\n", my_driver_name);
}

static ssize_t driver_attrs_rw_show(struct device_driver* driver, char* buf)
{
    return snprintf(buf, PAGE_SIZE, "%s: %s\n", my_driver_name, my_driver_rwbuf);
}
static ssize_t driver_attrs_rw_store(struct device_driver* driver, const char* buf, size_t count)
{
    if (count >= 128) {
        count = 128;
    }
    if (buf) {
        memcpy(my_driver_rwbuf, buf, count);
        my_driver_rwbuf[count] = 0;
    }
    return count;
}

#ifndef DRIVER_ATTR
#define DRIVER_ATTR(_name, _mode, _show, _store) \
        struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
#endif

static DRIVER_ATTR(version, S_IRUGO, driver_attrs_version_show, NULL);
static DRIVER_ATTR(rwstate, (S_IRUGO | S_IWUGO), driver_attrs_rw_show, driver_attrs_rw_store);

static struct attribute* my_drv_attrs[] = {
    &driver_attr_version.attr,
    &driver_attr_rwstate.attr,
    NULL,
};

int my_match(struct device* dev, struct device_driver* drv)
{
    // 比较设备的bus_id与驱动的名字是否匹配,匹配一致则在insmod驱动 时候调用probe函数 
    return !strncmp(dev->kobj.name, drv->name, strlen(drv->name));
}

static const struct attribute_group bus_attr_group = {
    .attrs = my_bus_attrs,
};

static const struct attribute_group dev_attr_group = {
    .attrs = my_dev_attrs,
};

static const struct attribute_group drv_attr_group = {
    .attrs = my_drv_attrs,
};

const struct attribute_group* my_bus_groups[] = {
    &bus_attr_group,
    NULL,
};

const struct attribute_group* my_dev_groups[] = {
    &dev_attr_group,
    NULL,
};

const struct attribute_group* my_drv_groups[] = {
    &drv_attr_group,
    NULL,
};

struct bus_type my_bus_type = {
    .name = "my_bus",
    .match = my_match,
    .bus_groups = my_bus_groups,
    .dev_groups = my_dev_groups,
    .drv_groups = my_drv_groups,
};

static int __init my_bus_init(void)
{
    int ret;
    ret = bus_register(&my_bus_type);
    return ret;
}

static void __exit my_bus_exit(void)
{
    bus_unregister(&my_bus_type);
}

MODULE_AUTHOR("freeabc");
MODULE_DESCRIPTION("my_bus demo!");
MODULE_LICENSE("GPL");

EXPORT_SYMBOL(my_bus_type);
module_init(my_bus_init);
module_exit(my_bus_exit);

Makefile 文件

obj-m := my_bus.o

SRC_DIR = $(shell pwd)
KERN_DIR = /lib/modules/$(shell uname -r)/build

modules:
	make -C ${KERN_DIR} M=${SRC_DIR} modules

modules_install:
	make -C ${KERN_DIR} M=${SRC_DIR} modules_install

clean:
	make -C ${KERN_DIR} M=${shell pwd} modules clean
  • my_device 模块
    my_device.c 源文件
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

extern struct bus_type my_bus_type;

static void my_dev_release(struct device *dev)
{
}

struct device my_dev = {
     .init_name = "my_dev",
     .bus = &my_bus_type,   
     .release = my_dev_release,
};

static int __init my_device_init(void)
{
    int ret;
    ret = device_register(&my_dev);
    return ret;
}

static void __exit my_device_exit(void)
{
    device_unregister(&my_dev);
}

MODULE_AUTHOR("freeabc");
MODULE_DESCRIPTION("my_device demo!");
MODULE_LICENSE("GPL");

module_init(my_device_init);
module_exit(my_device_exit);

Makefile 文件

obj-m := my_device.o

SRC_DIR = $(shell pwd)
KERN_DIR = /lib/modules/$(shell uname -r)/build
KBUILD_EXTRA_SYMBOLS=/root/device_mode/my_bus/Module.symvers

modules:
	make -C ${KERN_DIR} M=${SRC_DIR} modules

modules_install:
	make -C ${KERN_DIR} M=${SRC_DIR} modules_install

clean:
	make -C ${KERN_DIR} M=${shell pwd} modules clean
  • my_driver 模块
    my_driver.c 源文件
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

// cat /proc/kallsyms | grep my_bus_type
extern struct bus_type my_bus_type;

int my_probe(struct device *dev)
{
    printk("my_driver found the device it can handle!\n");
    return 0;
}

struct device_driver my_driver = {
    .name = "my_driver",
    .bus = &my_bus_type,
    .probe = my_probe,
};

static int __init my_driver_init(void)
{
    int ret;
    ret = driver_register(&my_driver);
    return ret;
}

static void __exit my_driver_exit(void)
{
    driver_unregister(&my_driver);  
}

MODULE_AUTHOR("freeabc");
MODULE_DESCRIPTION("my_driver demo!");
MODULE_LICENSE("GPL");

module_init(my_driver_init);
module_exit(my_driver_exit);

Makefile 文件

obj-m := my_driver.o

SRC_DIR = $(shell pwd)
KERN_DIR = /lib/modules/$(shell uname -r)/build
KBUILD_EXTRA_SYMBOLS=/root/device_mode/my_bus/Module.symvers

modules:
	make -C ${KERN_DIR} M=${SRC_DIR} modules

modules_install:
	make -C ${KERN_DIR} M=${SRC_DIR} modules_install

clean:
	make -C ${KERN_DIR} M=${shell pwd} modules clean
posted @ 2022-11-17 23:26  superconvert  阅读(418)  评论(0编辑  收藏  举报