良许Linux教程网 干货合集 详解Linux设备驱动之设备模型

详解Linux设备驱动之设备模型

你是否想过如何在Linux系统中为你的设备编写驱动程序?你是否想过如何在Linux系统中让你的驱动程序与其他组件协调工作?你是否想过如何在Linux系统中让你的驱动程序实现一些高级的功能,比如热插拔、电源管理、设备共享等?如果你对这些问题感兴趣,那么本文将为你介绍一种实现这些目标的有效方法——Linux设备驱动之设备模型。设备模型是一种用于描述设备之间关系的数据结构,它可以让你用一种简单而统一的方式,将设备的信息和属性传递给内核,从而实现设备的识别和驱动。设备模型也是一种用于实现协调工作的机制,它可以让你用一种标准而通用的方式,定义和使用各种设备的接口和协议,从而实现与总线、类、驱动等其他组件的交互和协作。设备模型还是一种用于实现高级功能的框架,它可以让你用一种灵活而可扩展的方式,定义和使用各种设备的操作和命令,从而实现热插拔、电源管理、设备共享等功能。本文将从设备模型的基本概念、语法规则、编写方法、注册过程、操作方式等方面,为你详细地介绍设备模型在Linux设备驱动中的应用和作用,帮助你掌握这种有用而强大的方法。

Linux设备模型是对系统设备组织架构进行抽象的一个数据结构,旨在为设备驱动进行分层、分类、组织。降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等。


Overview

设计目的

  • 电源管理和系统关机(Power management and system shutdown)

    设备之间大多情况下有依赖、耦合,因此要实现电源管理就必须对系统的设备结构有清楚的理
    
    解,应知道先关哪个然后才能再关哪个。设计设备模型就是为了使系统可以按照正确顺序进行
    
    硬件的遍历。
    
  • 与用户空间的交互(Communications with user space)

    实现了sysfs虚拟文件系统。它可以将设备模型中定义的设备属性信息等导出到用户空间,使
    
    得在用户空间可以实现对设备属性的访问及参数的更改。详见
    
    Documentation/filesystems/sysfs.txt。
    
  • 可热插拔设备(Hotpluggable devices)

    设备模型管理内核所使用的处理用户空间热插拔的机制,支持设备的动态添加与移除。
    
  • 设备类别(Device classes)

    系统的许多部分对设备如何连接没有兴趣, 但是它们需要知道什么类型的设备可用。设备模型
    
    也实现了一个给设备分类的机制, 它在一个更高的功能性级别描述了这些设备。
    
  • 对象生命期(Object lifecycles)

    设备模型的实现一套机制来处理对象生命期。
    

设备模型框图

Linux 设备模型是一个复杂的数据结构。如图所示为和USB鼠标相关联的设备模型的一小部分:
Overview diagram

这个框图展示了设备模型最重要的四个部分的组织关系(在顶层容器中详解):

  • Devices

    描述了设备如何连接到系统。
    
  • Drivers

    系统中可用的驱动。
    
  • Buses

    跟踪什么连接到每个总线,负责匹配设备与驱动。
    
  • classes

    设备底层细节的抽象,描述了设备所提供的功能。
    

底层实现

kobject

作用与目的

Kobject是将整个设备模型连接在一起的基础。主要用来实现以下功能:

  • 对象的引用计数(Reference counting of objects)

    通常, 当一个内核对象被创建, 没有方法知道它会存在多长时间。 一种跟踪这种对象生命周
    
    期的方法是通过引用计数。 当没有内核代码持有对给定对象的引用, 那个对象已经完成了它
    
    的有用寿命并且可以被删除。
    
  • sysfs 表示(Sysfs representation)

    在sysfs中显示的每一个项目都是通过一个与内核交互的kobject实现的。
    
  • 数据结构粘和(Data structure glue)

    设备模型整体来看是一个极端复杂的由多级组成的数据结构, kobject实现各级之间的连接粘和。
    
  • 热插拔事件处理(Hotplug event handling)

    kobject处理热插拔事件并通知用户空间。
    

数据结构

/* include in  */

struct kobject {
    const char *name; /* 该kobject的名称,同时也是sysfs中的目录名称 */
    struct list_head entry; /* kobjetct双向链表 */
    struct kobject *parent; /* 指向kset中的kobject,相当于指向父目录 */
    struct kset *kset; /*指向所属的kset*/
    struct kobj_type *ktype; /*负责对kobject结构跟踪*/
    ...
};

/* 定义kobject的类型及释放回调 */
struct kobj_type {
    void (*release)(struct kobject *); /* kobject释放函数指针 */
    struct sysfs_ops *sysfs_ops; /* 默认属性操作方法 */
    struct attribute **default_attrs; /* 默认属性 */
};

/* kobject上层容器 */
struct kset {    
    struct list_head list; /* 用于连接kset中所有kobject的链表头 */
    spinlock_t list_lock; /* 扫描kobject组成的链表时使用的锁 */
    struct kobject kobj; /* 嵌入的kobject */
    const struct kset_uevent_ops *uevent_ops; /* kset的uevent操作 */
};

/* 包含kset的更高级抽象 */
struct subsystem {
    struct kset kset; /* 定义一个kset */
    struct rw_semaphore rwsem; /* 用于串行访问kset内部链表的读写信号量 */
};

kobject和kset关系:
kobject and kset

如图所示,kset将它的children(kobjects)组成一个标准的内核链表。所以说kset是一个包含嵌入在同种类型结构中的kobject的集合。它自身也内嵌一个kobject,所以也是一个特殊的kobject。设计kset的主要目的是容纳,可以说是kobject的顶层容器。kset总是会在sysfs中以目录的形式呈现。需要注意的是图中所示的kobject其实是嵌入在其他类型中(很少单独使用),也可能是其他kset中。

kset和subsystem关系:
一个子系统subsystem, 其实只是一个附加了个读写信号量的kset的包装,反过来就是说每个 kset 必须属于一个子系统。根据subsystem之间的成员关系建立kset在整个层级中的位置。
子系统常常使用宏直接静态定义:

    /* 定义一个struct subsystem name_subsys 并初始化kset的type及hotplug_ops */
    decl_subsys(name, struct kobj_type *type,struct kset_hotplug_ops *hotplug_ops);

操作函数

  • 初始化
/* 初始化kobject内部结构 */
void kobject_init(struct kobject *kobj);
/* 设置name */
int kobject_set_name(struct kobject *kobj, const char *format, ...);

/* 先将kobj->kset指向要添加的kset中,然后调用会将kobject加入到指定的kset中 */
int kobject_add(struct kobject *kobj);
/* kobject_register = kobject_init + kobject_add */
extern int kobject_register(struct kobject *kobj);

/* 对应的Kobject删除函数 */
void kobject_del(struct kobject *kobj);
void kobject_unregister(struct kobject *kobj);

/* 与kobject类似的kset操作函数 */
void kset_init(struct kset *kset);
kobject_set_name(&my_set->kobj, "The name");
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);

Tip: 初始化前应先使用memset将kobj清零;初始化完成后引用计数为1

  • 引用计数管理
/* 引用计数加1并返回指向kobject的指针 */
struct kobject *kobject_get(struct kobject *kobj);
/* 当一个引用被释放, 调用kobject_put递减引用计数,当引用为0时free这个object */
void kobject_put(struct kobject *kobj);

/* 与kobject类似的kset操作函数 */
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);
  • 释放
当引用计数为0时,会调用ktype中的release,因此可以这样定义release回调函数:
void my_object_release(struct kobject *kobj)
{
    struct my_object *mine = container_of(kobj, struct my_object, kobj);
    /* Perform any additional cleanup on this object, then... */
    kfree(mine);
}

/* 查找ktype */
struct kobj_type *get_ktype(struct kobject *kobj);
  • subsystem相关
decl_subsys(name, type, hotplug_ops);
void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys);
void subsys_put(struct subsystem *subsys);

Low-Level Sysfs Operations

kobject和sysfs关系

kobject是实现sysfs虚拟文件系统背后的机制。sysfs中的每一个目录都对应内核中的一个kobject。将kobject的属性(atrributes)导出就会在sysfs对应的目录下产生由内核自动生成的包含这些属性信息的文件。只需简单的调用前面所提到的kobject_add就会在sysfs中生成一个对应kobject的入口,但值得注意的是:

  • 这个入口总会以目录呈现, 也就是说生成一个入口就是创建一个目录。通常这个目录会包含一个或多个属性文件(见下文)。
  • 分配给kobject的名字(用kobject_set_name)就是给 sysfs 目录使用的名字,因此在sysfs层级中相同部分的kobject命名必须唯一,不能包含下划线,避免使用空格。
  • 这个入口所处的目录表示kobject的parent指针,如果parent为NULL,则指向的是它的kset,因此可以说sysfs的层级其实对应的就是kset的层级。但当kset也为NULL时,这个入口就会创建在sysfs的top level,不过实际中很少出现这种情况。

属性(atrributes)

属性即为上面所提到的一旦导出就会由内核自动生成的包含kobject内核信息的文件。结构如下:

struct attribute {
    char *name; /* 属性名,也是sysfs对应e
ntry下的文件名 */
    struct module *owner; /* 指向负责实现这个属性的模块 */
    mode_t mode; /* 权限位,在中定义 */
};

属性的导出显示及导入存储函数:

/* kobj: 需要处理的kobject
   attr: 需要处理的属性
   buffer: 存储编码后的属性信息,大小为PAGE_SIZE
   return: 实际编码的属性信息长度
   */
struct sysfs_ops {
    ssize_t (*show)(struct kobject *kobj, struct attribute *attr,char *buffer); /* 导出到用户


空间 */
    ssize_t (*store)

(struct kobject *kobj, struct attribute *attr,const char *buffer, size_t size); /* 存储进内核

空间 */
};

需要注意的是:

  • 每个属性都是用name=value表示,name即使属性的文件名,value即文件内容,如果value超过PAGE_SIZE,则应分为多个属性来处理;
  • 上述函数可以处理不同的属性。可以在内部实现时同过属性名进行区分来实现;
  • 由于store是从用户空间到内核,所以实现时首先要检查参数的合法行,以免内核崩溃及其他问题。
缺省属性(Default Attributes)

在kobject创建时都会赋予一些缺省的默认属性,即上面所提到的kobj_type中的default_attrs数组,这个数组的最后一个成员须设置成NULL,以表示数组大小。所有使用这个kobj_type的kobject都是通过kobj_type中的sfsfs_ops回调函数入口实现对缺省属性的定义。

非缺省属性(Nondefault Attributes)

一般来说,定义时就可以通过default_attrs完成所有的属性,但这里也提供了后续动态添加和删除属性的方法:

   int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
   int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);
二进制属性(Binary Attributes)

上述属性包含的可读的文本值,二进制属性很少使用,大多用在从用户空间传递一些不改动的文件如firmware给设备的情况下。

        struct bin_attribute {
            struct attribute attr; /* 定义name,owner,mode */
            size_t size; /* 属性最大长度,如没有最大长度则设为0 */
            ssize_t (*read)(struct kobject *kobj, char *buffer,loff_t pos, size_t size);
            ssize_t (*write)(struct kobject *kobj, char *buffer,loff_t pos, size_t size);
        };

read/write一次加载多次调用,每次最多PAGE_SIZE大小。注意write无法指示最后一个写操作,得通过其他方式判断操作的结束。
二进制属性不能定义为缺省值,因此需明确的创建与删除:

        int sysfs_create_bin_file(struct kobject *kobj,struct bin_attribute *attr);
        int sysfs_remove_bin_file(struct kobject *kobj,struct bin_attribute *attr);

符号连接(Symbolic Links)

方法:

    int sysfs_create_link(struct kobject *kobj, struct kobject *target,char *name);
    void sysfs_remove_link(struct kobject *kobj, char *name);

热插拔事件生成(Hotplug Event Generation)

热插拔事件即当系统配置发生改变是内核向用户空间的通知。然后用户空间会调用/sbin/hotplug通过创建节点、加载驱动等动作进行响应。这个热插拔事件的产生是在kobject_add和kobject_del时。我们可以通过上面kset中定义的uevent_ops对热插拔事件产生进行配置:

struct kset_uevent_ops {
    /* 实现事件的过滤,其返回值为0时不产生事件 */
    int (* const filter)(struct kset *kset, struct kobject *kobj);
    /* 生成传递给/sbin/hotplug的name参数 */
    const char *(* const name)(struct kset *kset, struct kobject *kobj);
    /* 其他传递给/sbin/hotplug的参数通过这种设置环境变量的方式传递 */
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
              struct kobj_uevent_env *env);
};

顶层容器

Buses, Devices, Drivers and Classes

Buses

总线Buses是处理器和设备的通道。在设备模型中,所有设备都是通过总线连接在一起的,哪怕是一个内部虚拟的platform总线。

/* defined in   */

struct bus_type {    
    const char *name; /* 总线类型名 */    
    struct bus_attribute *bus_attrs; /* 总线的属性 */    
    struct device_attribute *dev_attrs; /* 设备属性,为每个加入总线的设备建立属性链表 */    
    struct driver_attribute *drv_attrs; /* 驱动属性,为每个加入总线的驱动建立属性链表 */

    /* 驱动与设备匹配函数:当一个新设备或者驱动被添加到这个总线时,
       这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。
       必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序 */    
    int (*match)(struct device *dev, struct device_driver *drv); 

    /*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/    
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);    
    ...

    struct subsys_private *p; /* 一个很重要的域,包含了device链表和drivers链表 */
}

/* 定义bus_attrs的快捷方式 */
BUS_ATTR(name, mode, show, store);

/* bus属性文件的创建移除 */
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

/* 总线注册 */
int bus_register(struct bus_type *bus);
void bus_unregister(struct bus_type *bus);

/* 遍历总线上的设备与驱动 */
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int(*fn)

(struct device *, void *));

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int(*fn)

(struct device_driver *, void *));

Devices

Linux中,每一个底层设备都是structure device的一个实例:

struct device {
     struct device *parent; /* 父设备,总线设备指定为NULL */    
     struct device_private *p; /* 包含设备链表,driver_data(驱动程序要使用数据)等信息 */    
     struct kobject kobj;    
     const char *init_name; /* 初始默认的设备名 */
     struct bus_type *bus; /* type of bus device is on */    
     struct device_driver *driver; /* which driver has allocated this device */
     ...
     void (*release)(struct device *dev); 
};

int device_register(struct device *dev);
void device_unregister(struct device *dev);

DEVICE_ATTR(name, mode, show, store);
int device_create_file(struct device *device,struct device_attribute *entry);
void device_remove_file(struct device *dev,struct device_attribute *attr);

Drivers

设备模型跟踪所有系统已知的驱动。

struct device_driver {    
    const char *name; /* 驱动名称,在sysfs中以文件夹名出现 */    
    struct bus_type *bus; /* 驱动关联的总线类型 */    
    int (*probe) (struct device *dev); /* 查询设备的存在 */
    int (*remove) (struct device *dev); /* 设备移除回调 */   
    void (*shutdown) (struct device *dev);
    ...
}

int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

DRIVER_ATTR(name, mode, show, store);
int driver_create_file(struct device_driver *drv,struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv,struct driver_attribute *attr);

Classes

类是设备的一个高级视图,实现了底层细节。通过对设备进行分类,同类代码可共享,减少了内核代码的冗余。

struct class {
    const char      *name; /* class的名称,会在“/sys/class/”目录下体现 */

    struct class_attribute      *class_attrs;
    struct device_attribute     *dev_attrs; /* 该class下每个设备的attribute */
    struct kobject          *dev_kobj;

    /* 当该class下有设备发生变化时,会调用class的uevent回调函数 */
    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
    char *(*devnode)(struct device *dev, mode_t *mode);

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

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    struct class_private *p;
};

int class_register(struct class *cls);
void class_unregister(struct class *cls);

CLASS_ATTR(name, mode, show, store);
int class_create_file(struct class *cls,const struct class_attribute *attr);
void class_remove_file(struct class *cls,const struct class_attribute *attr);

Putting It All Together

all Together
all Together

通过本文,我们了解了设备模型在Linux设备驱动中的应用和作用,学习了如何编写、注册、操作、修改和调试设备模型。我们发现,设备模型是一种非常适合嵌入式系统开发的方法,它可以让我们方便地描述和管理设备之间关系,实现协调工作和高级功能。当然,设备模型也有一些注意事项和限制,比如需要遵循语法规范、需要注意兼容性问题、需要注意性能影响等。因此,在使用设备模型时,我们需要有一定的硬件知识和经验,以及良好的编程习惯和调试技巧。希望本文能够为你提供一个入门级的指导,让你对设备模型有一个初步的认识和理解。如果你想深入学习设备模型,建议你参考更多的资料和示例,以及自己动手实践和探索。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部