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