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鼠标相关联的设备模型的一小部分:
这个框图展示了设备模型最重要的四个部分的组织关系(在顶层容器中详解):
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关系:
如图所示,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