了解Linux设备驱动的设备模型

嵌入式技术

1372人已加入

描述

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鼠标相关联的设备模型的一小部分:

Linux

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

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关系:

Linux

如图所示,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对应entry下的文件名 */ 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

Linux



 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分