Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。
现在内核使用设备模型支持多种不同的任务:
(1)电源管理和系统关机 :这些需要对系统结构的理解,设备模型使OS能以正确顺序遍历系统硬件。
(2)与用户空间的通讯 :sysfs 虚拟文件系统的实现与设备模型的紧密相关, 并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过 sysfs , 也就是设备模型来完成。
(3)热插拔设备:设备模型包括了将设备分类的机制,在一个更高的功能层上描述这些设备, 并使设备对用户空间可见。
(3)对象生命周期:设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。
Linux 设备模型是一个复杂的数据结构。但对模型的大部分来说, Linux 设备模型代码会处理好这些关系, 而不是把他们强加于驱动开发者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动开发者可完全忽略设备模型, 并相信设备模型能处理好他所负责的事(但是理解Linux设备驱动模型,更有利于驱动开发,要做到:知其然而又知其所以然)。
1.kobject
1.1kobject概念
kobject是一种数据结构,定义在 ,kobject 被用来控制对大型域(domain)相关对象的访问,所以kobject 被嵌入到其他结构中。kobject 可被看作一个最顶层的基类,其他类都它的派生产物。kobject 实现了一系列方法,对自身并没有特殊作用,而对其他对象却非常有效。
struct kobject {
const char * k_name;/*kobject 的名字数组(sysfs 入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间 */
char name[KOBJ_NAME_LEN];/*kobject 的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/
struct kref kref;/*kobject 的引用计数*/
struct list_head entry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/
struct kobject * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/
struct kset * kset;/*指向所属的kset*/
struct kobj_type * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/
struct dentry * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/
wait_queue_head_t poll;/*等待队列头*/
};
kobject 是组成设备模型的基本结构,初始它只被作为一个简单的引用计数, 但随时间的推移,其任务越来越多。现在kobject 所处理的任务和支持代码包括:
(1)对象的引用计数 :跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。
(2)sysfs 表述:在 sysfs 中出现的每个对象都对应一个 kobject, 它和内核交互来创建它的可见表述。
(3)数据结构关联:整体来看, 设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。kobject 实现了该结构并将其聚合在一起。
(4)热插拔事件处理 :kobject 子系统将产生的热插拔事件通知用户空间。
1.2kobject 初始化
kobject的初始化较为复杂,但是必须的步骤如下:
(1)将整个kobject清零,通常使用memset函数。
(2)调用kobject_init()函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。
(3)设置kobject的名字
(4)直接或间接设置其它成员:ktype、kset和parent。
1.3对引用计数的操作
kobject 的一个重要函数是为包含它的结构设置引用计数。只要对这个对象的引用计数存在, 这个对象( 和支持它的代码) 必须继续存在。
struct kobject kobject_get(struct kobjectkobj);/若成功,递增 kobject 的引用计数并返回一个指向 kobject 的指针,否则返回 NULL。必须始终测试返回值以免产生竞态/
void kobject_put(struct kobject kobj);/递减引用计数并在可能的情况下释放这个对象*/
1.4cdef结构
cdev结构定义在
struct cdev {
struct kobject kobj; //内嵌的内核对象.
struct module *owner; //该字符设备所在的内核模块的对象指针.
const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
dev_t dev; //字符设备的设备号,由主设备号和次设备号构成.
unsigned int count; //隶属于同一主设备号的次设备号的个数.
};
创建一个对 cdev 结构的引用时,还需要创建包含它的模块的引用。因此, cdev_get 使用 try_module_get 来试图递增这个模块的使引用计数。如果这个操作成功, kobject_get 被同样用来递增 kobject 的引用计数。kobject_get 可能失败, 因此这个代码检查 kobject_get 的返回值,如果调用失败,则释放它的对模块的引用计数。
1.5release 函数和 kobject 类型
引用计数不由创建 kobject 的代码直接控制,当 kobject 的最后引用计数消失时,必须异步通知,而后kobject中ktype所指向的kobj_type结构体包含的release函数会被调用。通常原型如下:
void my_object_release(struct kobject *kobj)
{
struct my_object *mine = container_of(kobj, struct my_object, kobj);
kfree(mine);
}
每个 kobject 必须有一个release函数, 并且这个 kobject 必须在release函数被调用前保持不变( 稳定状态 ) 。这样,每一个 kobject 需要有一个关联的 kobj_type 结构,指向这个结构的指针能在 2 个不同的地方找到:
(1)kobject 结构自身包含一个成员(ktype)指向kobj_type
(2)如果这个 kobject 是一个 kset 的成员, kset 会提供kobj_type 指针,例如:
struct kset {
struct kobj_type * ktype; /*指向该kset对象类型的指针*/
struct list_head list;/*用于连接该kset中所有kobject以形成环形链表的链表头*/
spinlock_t list_lock;/*用于避免竞态的自旋锁*/
struct kobject kobj; /*嵌入的kobject*/
struct kset_uevent_ops * uevent_ops;
/*原有的struct kset_hotplug_ops * hotplug_ops;已经不存在,被kset_uevent_ops 结构体替换,在热插拔操作中会介绍*/
};
2.kobject 层次结构、kset 和子系统
2.1kobject 层次结构
内核通常用kobject 结构将各个对象连接起来组成一个分层的结构体系,与模型化的子系统相匹配。有 2 个独立的机制用于连接: parent 指针和 kset。
parent 是指向另外一个kobject 结构(分层结构中上一层的节点)的指针,主要用途是在 sysfs 层次中定位对象.
kset是kobj_type 结构的扩展,一个 kset 是嵌入到相同类型结构的 kobject 的集合,可认为是kobjects 的顶层容器类。每个 kset 在内部包含自己的 kobject, 并可以用多种处理kobject 的方法处理kset。 ksets 总是在 sysfs 中出现; 一旦设置了 kset 并把它添加到系统中, 将在 sysfs 中创建一个目录;kobjects 不必在 sysfs 中表示, 但kset中的每一个 kobject 成员都要在sysfs中表述。
增加 kobject 到 kset 中去,通常是在kobject 创建时完成,其过程如下:
(1)完成kobject的初始化,特别注意mane和parent和初始化。
(2)把kobject 的 kset 成员指向目标kset。
(3)将kobject 传递给下面的函数:
int kobject_add(struct kobject *kobj); /*函数可能失败(返回一个负错误码),程序应作出相应地反应*/
extern int kobject_register(struct kobject *kobj); /*仅仅是一个 kobject_init 和 kobject_add 的结合,其他成员的初始化必须在之前手动完成*/
当把一个kobject从kset中删除以清除引用时使用:
void kobject_unregister(struct kobject *kobj); /*是 kobject_del 和 kobject_put 的结合*/
2.2kset
ksets 有类似于kobjects初始化和设置接口:
struct kset {
struct kobj_type * ktype; /*指向该kset对象类型的指针*/
struct list_head list;/*用于连接该kset中所有kobject以形成环形链表的链表头*/
spinlock_t list_lock;/*用于避免竞态的自旋锁*/
struct kobject kobj; /*嵌入的kobject*/
struct kset_uevent_ops * uevent_ops;
/*原有的struct kset_hotplug_ops * hotplug_ops;已经不存在,被kset_uevent_ops 结构体替换,在热插拔操作中会介绍*/
};
void kset_init(struct kset *kset);
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
/*管理 ksets 的引用计数:*/
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);
/* kset 也有一个名字,存储于嵌入的 kobject,因此设置它的名字用:*/
kobject_set_name(&my_set->kobj, "The name");
在新的内核里, kset 不再包含一个子系统指针struct subsystem * subsys, 而且subsystem已经被kset取代。
2.3子系统
子系统是对整个内核中一些高级部分的表述。子系统通常(但不一定)出现在 sysfs分层结构中的顶层,内核子系统包括 block_subsys(/sys/block 块设备)、 devices_subsys(/sys/devices 核心设备层)以及内核已知的用于各种总线的特定子系统。
对于新的内核已经不再有subsystem数据结构了,用kset代替了。每个 kset 必须属于一个子系统,子系统成员帮助内核在分层结构中定位 kset 。
3.底层sysfs操作
kobject 是在 sysfs 虚拟文件系统后的机制。对每个在 sysfs 中的目录, 在内核中都会有一个 kobject 与之对应。每个kobject 都输出一个或多个属性, 它在 kobject 的 sysfs 目录中以文件的形式出现, 其中的内容由内核产生。 包含 sysfs 的工作代码。
(1)默认属性:当创建kobject 时, 每个 kobject 都被给定一系列默认属性
sysfs 读写这些属性是由 kobj_type->sysfs_ops 成员中的函数完成的:
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);
};
(2)非默认属性:虽然 kobject 类型的 default_attrs 成员描述了所有的 kobject 会拥有的属性。
倘若想添加新属性到 kobject 的 sysfs 目录属性只需简单地填充一个attribute结构并传递到以下函数:
int sysfs_create_file(struct kobject kobj, struct attributeattr);
若要删除属性,调用:
int sysfs_remove_file(struct kobject kobj, struct attributeattr);
(3)二进制属性
sysfs 通常要求所有属性都只包含一个可读文本格式的值,很少需要创建能够处理大量二进制数据的属性。
int sysfs_create_bin_file(struct kobject kobj, struct bin_attributeattr);/二进制属性必须显式创建,不能以默认属性被创建,创建一个二进制属性调用:/
int sysfs_remove_bin_file(struct kobject kobj, struct bin_attributeattr);/删除二进制属性调用:/
(4)符号链接
sysfs 文件系统具有树型结构, 反映 kobject之间的组织层次关系。为了表示驱动程序和所管理的设备间的关系,需要额外的指针,其在 sysfs 中通过符号链接实现。
int sysfs_create_link(struct kobject kobj, struct kobjecttarget, char name);/在 sysfs 创建一个符号链接:/
void sysfs_remove_link(struct kobjectkobj, char name);/删除符号连接调用:*/
4.热插拔事件产生
一个热插拔事件是一个从内核空间发送到用户空间的通知, 表明系统配置已经改变. 无论 kobject 被创建或删除,都会产生这种事件。热插拔事件会导致对 /sbin/hotplug 的调用, 它通过加载驱动程序, 创建设备节点, 挂载分区或其他正确动作响应事件。
若在 kobject 中不包含指定的 kset , 内核将通过 parent 指针在分层结构中进行搜索,直到发现一个包含有kset的 kobject ; 接着使用这个 kset 的热插拔操作。
热插拔事件的产生通常是由在总线驱动程序层的逻辑所控制。
5.总线
总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连, 甚至是内部的虚拟"platform"总线。总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。
在 Linux 设备模型中, 总线由 bus_type 结构表示, 定义在 :
struct bus_type {
const char * name;/*总线类型名称*/
struct module * owner;/*指向模块的指针(如果有), 此模块负责操作这个总线*/
struct kset subsys;/*与该总线相关的子系统*/
struct kset drivers;/*总线驱动程序的kset*/
struct kset devices;/* 挂在该总线的所有设备的kset*/
struct klist klist_devices;/*与该总线相关的驱动程序链表*/
struct klist klist_drivers;/*挂接在该总线的设备链表*/
struct blocking_notifier_head bus_notifier;
struct bus_attribute * bus_attrs; /*总线属性*/
struct device_attribute * dev_attrs; /*设备属性,指向为每个加入总线的设备建立的默认属性链表*/
struct driver_attribute * drv_attrs; /*驱动程序属性*/
struct bus_attribute drivers_autoprobe_attr;/*驱动自动探测属性*/
struct bus_attribute drivers_probe_attr;/*驱动探测属性*/
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
int (*probe)(struct device * dev);
int (*remove)(struct device * dev);
void (*shutdown)(struct device * dev);
int (*suspend)(struct device * dev, pm_message_t state);
int (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
nt (*resume)(struct device * dev);
/*处理热插拔、电源管理、探测和移除等事件的方法*/
unsigned int drivers_autoprobe:1;
};/*该结构会随内核版本不同而有差异*/
5.1总线的注册和删除
(1)声明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
.uevent = ldd_uevent,
};
(2)调用bus_register函数注册总线。
int bus_register(struct bus_type * bus)
5.2总线方法
在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:
int (match)(struct device dev, struct device_driver drv);/当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序/
int (uevent)(struct device *dev, char *envp, int num_envp, charbuffer, int buffer_size);/在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)/
5.3对设备和驱动的迭代
若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:
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 *));
这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。
5.4总线属性
几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 :
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *, char * buf);
ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};
可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。
5.5设备
在最底层, Linux 系统中的每个设备由一个 struct device 代表:
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;/* 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */
struct kobject kobj;/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj*/
char bus_id[BUS_ID_SIZE];/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI ID 格式, 包含:域, 总线, 设备, 和功能号.*/
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct device_attribute uevent_attr;
struct device_attribute *devt_attr;
struct semaphore sem; /* semaphore to synchronize calls to its driver. */
struct bus_type * bus; /*标识该设备连接在何种类型的总线上*/
struct device_driver *driver; /*管理该设备的驱动程序*/
void *driver_data; /*该设备驱动使用的私有数据成员*/
void *platform_data; /* Platform specific data, device core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
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. */
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
/* arch specific additions */
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
/* class_device migration path */
struct list_head node;
struct class *class;
dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device * dev);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/
};
5.6设备注册
设备的注册和注销函数为:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
5.7设备属性
sysfs 中的设备入口可有属性,相关的结构是:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};
5.8设备结构的嵌入
device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device 结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。
struct ldd_device {
char *name;
struct ldd_driver *driver;
struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);
5.9设备驱动程序
设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体 device_driver 定义如下:
struct device_driver {
const char * name;/*驱动程序的名字( 在 sysfs 中出现 )*/
struct bus_type * bus;/*驱动程序所操作的总线类型*/
struct kobject kobj;/*内嵌的kobject对象*/
struct klist klist_devices;/*当前驱动程序能操作的设备链表*/
struct klist_node knode_bus;
struct module * owner;
const char * mod_name; /* used for built-in modules */
struct module_kobject * mkobj;
int (*probe) (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);
};
/*注册device_driver 结构的函数是:*/
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
/*driver的属性结构在:*/
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *drv, char *buf);
ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count);
};
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);
5.10驱动程序结构的嵌入
对大多数驱动程序核心结构, device_driver 结构通常被嵌入到一个更高层的、总线相关的结构中。
以lddbus 子系统为例,它定义了ldd_driver 结构:
struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);
6.类
类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能, 而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在 /sys/class 目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况, 类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时, 它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。
为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。 类函数和结构与设备模型的其他部分遵循相同的模式,所以真正崭新的概念是很少的。
6.1管理类的接口
类由 struct class 的结构体来定义:
/*
* device classes
*/
struct class {
const char * name;/*每个类需要一个唯一的名字, 它将显示在 /sys/class 中*/
struct module * owner;
struct kset subsys;
struct list_head children;
struct list_head devices;
struct list_head interfaces;
struct kset class_dirs;
struct semaphore sem; /* locks both the children and interfaces lists */
struct class_attribute * class_attrs;/* 指向类属性的指针(以NULL结尾) */
struct class_device_attribute * class_dev_attrs;/* 指向类中每个设备的一组默认属性的指针 */
struct device_attribute * dev_attrs;
int (*uevent)(struct class_device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);/* 类热插拔产生时添加环境变量的函数 */
int (*dev_uevent)(struct device *dev, char **envp, int num_envp,
char *buffer, int buffer_size);/* 类中的设备热插拔时添加环境变量的函数 */
void (*release)(struct class_device *dev);/* 把设备从类中删除的函数 */
void (*class_release)(struct class *class);/* 删除类本身的函数 */
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *, pm_message_t state);
int (*resume)(struct device *);
};
/*类注册函数:*/
int class_register(struct class *cls);
void class_unregister(struct class *cls);
/*类属性的接口:*/
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *cls, char *buf);
ssize_t (*store)(struct class *cls, const char *buf, size_t count);
};
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);
6.2类设备(在新内核中已被删除)
类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由 struct class_device 来表示:
struct class_device {
struct list_head node;/*for internal use by the driver core only*/
struct kobject kobj;/*for internal use by the driver core only*/
struct class * class; /* 指向该设备所属的类,必须*/
dev_t devt; /* dev_t, creates the sysfs "dev" ,for internal use by the driver core only*/
struct class_device_attribute *devt_attr;/*for internal use by the driver core only*/
struct class_device_attribute uevent_attr;
struct device * dev; /* 指向此设备相关的 device 结构体,可选。若不为NULL,应是一个从类入口到/sys/devices 下相应入口的符号连接,以便用户空间查找设备入口*/
void * class_data; /* 私有数据指针 */
struct class_device *parent; /* parent of this child device, if there is one */
struct attribute_group ** groups; /* optional groups */
void (*release)(struct class_device *dev);
int (*uevent)(struct class_device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
char class_id[BUS_ID_SIZE]; /* 此类中的唯一的名字 */
};
/*类设备注册函数:*/
int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);
/*重命名一个已经注册的类设备入口:*/
int class_device_rename(struct class_device *cd, char *new_name);
/*类设备入口属性:*/
struct class_device_attribute {
struct attribute attr;
ssize_t (*show)(struct class_device *cls, char *buf);
ssize_t (*store)(struct class_device *cls, const char *buf,
size_t count);
};
CLASS_DEVICE_ATTR(_name, _mode, _show, _store);
/*创建和删除除struct class中设备默认属性外的属性*/
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);
6.3类接口
类子系统有一个 Linux 设备模型的其他部分找不到的附加概念,称为“接口”, 可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:
struct class_interface {
struct list_head node;
struct class *class;/* 指向该接口所属的类*/
int (*add) (struct class_device *, struct class_interface *);
/*当一个类设备被加入到在 class_interface 结构中指定的类时, 将调用接口的 add 函数,进行一些设备需要的额外设置,通常是添加更多属性或其他的一些工作*/
void (*remove) (struct class_device *, struct class_interface *);/*一个接口的功能是简单明了的. 当设备从类中删除, 将调用remove 方法来进行必要的清理*/
int (*add_dev) (struct device *, struct class_interface *);
void (*remove_dev) (struct device *, struct class_interface *);
};
/*注册或注销接口的函数,一个类可注册多个接口*/
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
以上6大章节,可以通过分析lddbus和sculld的源码理解上面的过程。一定要分析,一定要分析,一定要分析!
7.热插拔
有 2 个不同角度来看待热插拔:
从内核角度看,热插拔是在硬件、内核和内核驱动之间的交互。
从用户角度看,热插拔是内核和用户空间之间,通过调用用户空间程序(如hotplug、udev 和 mdev)的交互。
热插拔工具
hotplug:这个程序是一个典型的 bash 脚本,只传递执行权给一系列位于 /etc/hot-plug.d/ 目录树的程序。(已经基本淘汰,不使用了)
udev :用于linux2.6.13或更高版本的内核上,为用户空间提供使用固定设备名的动态/dev目录的解决方案。它通过在 sysfs 的 /class/ 和/block/ 目录树中查找一个称为 dev 的文件,以确定所创建的设备节点文件的主次设备号。所以要使用udev,驱动必须为设备在sysfs中创建类接口及其dev属性文件,方法和sculld模块中创建dev属性相同。udev较mdev复杂,不太适合嵌入式使用。
mdev:一个简化版的udev,是busybox所带的程序,十分适合嵌入式系统。
8.firmware
硬件市场的激烈竞争, 使得制造商连一点用于设备控制固件的 EEPROM 的成本都不愿意花费。因此固件一般发布在和硬件配套的驱动包中,由操作系统(其实是驱动程序)负责传送固件到设备。
9.内核固件接口
获取固件的正确方法是当需要时从用户空间获取它。一定不要试图从内核空间直接打开包含固件的文件,那是一个易出错的操作, 因为它把策略(以文件名的形式)包含进了内核。
固件接口工作原理:固件子系统使用 sysfs 和热插拔机制工作。当调用 request_firmware时, 函数将在 /sys/class/firmware 下创建一个以设备名为目录名的新目录,其中包含 3 个属性:
loading :这个属性应当被加载固件的用户空间进程设置为 1。当加载完毕, 它将被设为 0。被设为 -1 时,将中止固件加载。
data :一个用来接收固件数据的二进制属性。在设置 loading 为1后, 用户空间进程将固件写入这个属性。
device :一个链接到 /sys/devices 下相关入口项的符号链接。