Linux 2.6内核的一个重要特色是提供了统一的内核设备模型。随着技术的不断进步,系统的拓扑结构越来越复杂,对智能电源管理、热插拔以及plug and play的支持要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,2.6内核开发了全新的设备模型。

2.6 设备模型提供了这个抽象. 现在它用在内核来支持广泛的任务, 包括:

电源管理和系统关机

这些需要一个对系统的结构的理解. 例如, 一个 USB 宿主适配器不可能被关闭, 在处理所有的连接到这个适配器的设备之前. 这个设备模型使能了一个按照正确顺序的系统硬件的遍历.

与用户空间的通讯

sysfs 虚拟文件系统的实现被紧密地捆绑进设备模型, 并且暴露它所代表的结构. 关于系统到用户空间的信息提供和改变操作参数的旋纽正越来越多地通过 sysfs 和 通过设备模型来完成.

可热插拔设备

计算机硬件正更多地动态变化; 外设可因用户的一时念头而进出. 在内核中使用的来处理和(特别的)与用户空间关于设备插入和拔出的通讯, 是由设备模型来管理.

设备类别

系统的许多部分对设备如何连接没有兴趣, 但是它们需要知道什么类型的设备可用. 设备模型包括一个机制来分配设备给类别, 它在一个更高的功能性的级别描述了这些设备, 并且允许它们从用户空间被发现.

对象生命期

许多上面描述的功能, 包括热插拔支持和 sysfs, 使在内核中创建和操作对象复杂了. 设备模型的实现要求创建一套机制来处理对象生命期, 它们之间的关系, 和它们在用户空间的表示.

1. Sysfs文件系统
Sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。其顶层目录主要有:
Block目录:包含所有的块设备
Devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
Bus目录:包含系统中所有的总线类型
Drivers目录:包括内核中所有已注册的设备驱动程序
Class目录:系统中的设备类型(如网卡设备,声卡设备等)

2. 内核对象机制关键数据结构
2.1 kobject内核对象
Kobject 是Linux 2.6引入的新的设备管理机制,在内核中由struct kobject表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux 2.6设备模型的核心结构,它与sysfs文件系统紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。

Kobject 是基础的结构, 它保持设备模型在一起. 初始地它被作为一个简单的引用计数, 但是它的责任已随时间增长, 并且因此有了它自己的战场. struct kobject 所处理的任务和它的支持代码现在包括:

对象的引用计数

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

sysfs 表示

在 sysfs 中出现的每个对象在它的下面都有一个 kobject, 它和内核交互来创建它的可见表示.

数据结构粘和

设备模型是, 整体来看, 一个极端复杂的由多级组成的数据结构, 各级之间有许多连接. kobject 实现这个结构并且保持它在一起.

热插拔事件处理

kobject 子系统处理事件的产生, 事件通知用户空间关于系统中硬件的来去.

你可能从前面的列表总结出 kobject 是一个复杂的结构. 这可能是对的. 通过一次看一部分, 但是, 是有可能理解这个结构和它如何工作的.

Kobject结构定义为:

struct kobject {
char * k_name; 指向设备名称的指针
char name[KOBJ_NAME_LEN]; 设备名称
struct kref kref; 对象引用计数
struct list_head entry; 挂接到所在kset中去的单元
struct kobject * parent; 指向父对象的指针
struct kset * kset; 所属kset的指针
struct kobj_type * ktype; 指向其对象类型描述符的指针
struct dentry * dentry; sysfs文件系统中与该对象对应的文件节点路径指针
};

其中的kref域表示该对象引用的计数,内核通过kref实现对象引用计数管理,内核提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为0时,所有该对象使用的资源将被释放。
Ktype域是一个指向kobj_type结构的指针,表示该对象的类型。Kobj_type数据结构包含三个域:

一个release方法用于释放kobject占用的资源;

一个sysfs_ops指针指向sysfs操作表和一个sysfs文件系统缺省属性列表。

Sysfs操作表包括两个函数store()和 show()。当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer中返回给用户态;而store()函数用于存储用户态传入的属性值。

2.2 kset内核对象集合
Kobject通常通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合,在内核中用kset数据结构表示,定义为:

struct kset {
struct subsystem * subsys; 所在的subsystem的指针
struct kobj_type * ktype; 指向该kset对象类型描述符的指针
struct list_head list; 用于连接该kset中所有kobject的链表头
struct kobject kobj; 嵌入的kobject
struct kset_hotplug_ops * hotplug_ops; 指向热插拔操作表的指针
};

包含在kset中的所有kobject被组织成一个双向循环链表,list域正是该链表的头。Ktype域指向一个kobj_type结构,被该 kset中的所有kobject共享,表示这些对象的类型。Kset数据结构还内嵌了一个kobject对象(由kobj域表示),所有属于这个kset 的kobject对象的parent域均指向这个内嵌的对象。此外,kset还依赖于kobj维护引用计数:kset的引用计数实际上就是内嵌的 kobject对象的引用计数。

2.3 subsystem内核对象子系统
Subsystem是一系列kset的集合,描述系统中某一类设备子系统,如block_subsys表示所有的块设备,对应于sysfs文件系统中的block目录。类似的,devices_subsys对应于sysfs中的devices目录,描述系统中所有的设备。Subsystem由struct subsystem数据结构描述,定义为:

struct subsystem {
struct kset kset; 内嵌的kset对象
struct rw_semaphore rwsem; 互斥访问信号量
};

每个kset必须属于某个subsystem,通过设置kset结构中的subsys域指向指定的subsystem可以将一个kset加入到该subsystem。所有挂接到同一subsystem的kset共享同一个rwsem信号量,用于同步访问kset中的链表。

3. 内核对象机制主要相关函数
针对内核对象不同层次的数据结构,linux 2.6内核定义了一系列操作函数,定义于lib/kobject.c文件中。
3.1 kobject相关函数
void kobject_init(struct kobject * kobj);
kobject初始化函数。设置kobject引用计数为1,entry域指向自身,其所属kset引用计数加1。

int kobject_set_name(struct kobject *kobj, const char *format, ...);
设置指定kobject的名称。

void kobject_cleanup(struct kobject * kobj)和void kobject_release(struct kref *kref);
kobject清除函数。当其引用计数为0时,释放对象占用的资源。

struct kobject *kobject_get(struct kobject *kobj);
将kobj 对象的引用计数加1,同时返回该对象的指针。

void kobject_put(struct kobject * kobj);
将kobj对象的引用计数减1,如果引用计数降为0,则调用kobject_release()释放该kobject对象。

int kobject_add(struct kobject * kobj);
将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数。

int kobject_register(struct kobject * kobj);
kobject注册函数。通过调用kobject_init()初始化kobj,再调用kobject_add()完成该内核对象的注册。

void kobject_del(struct kobject * kobj);
从Linux设备层次(hierarchy)中删除kobj对象。

void kobject_unregister(struct kobject * kobj);
kobject注销函数。与kobject_register()相反,它首先调用kobject_del从设备层次中删除该对象,再调用kobject_put()减少该对象的引用计数,如果引用计数降为0,则释放该kobject对象。

3.2 kset相关函数
与kobject 相似,kset_init()完成指定kset的初始化,kset_get()和kset_put()分别增加和减少kset对象的引用计数。 Kset_add()和kset_del()函数分别实现将指定keset对象加入设备层次和从其中删除;kset_register()函数完成 kset的注册而kset_unregister()函数则完成kset的注销。

3.3 subsystem相关函数
subsystem有一组完成类似的函数,分别是:

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);

4. 设备模型组件
在上述内核对象机制的基础上,Linux的设备模型建立在几个关键组件的基础上,下面我们详细阐述这些组件。
4.1 devices
系统中的任一设备在设备模型中都由一个device对象描述,其对应的数据结构struct device定义为:

struct device {
struct list_head g_list; 
struct list_head node;
struct list_head bus_list;
struct list_head driver_list;
struct list_head children;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
struct bus_type *bus;
struct device_driver *driver;
void *driver_data;
/* Several fields omitted */
};

g_list 将该device对象挂接到全局设备链表中,所有的device对象都包含在devices_subsys中,并组织成层次结构。Node域将该对象挂接到其兄弟对象的链表中,而bus_list则用于将连接到相同总线上的设备组织成链表,driver_list则将同一驱动程序管理的所有设备组织为链表。此外,children域指向该device对象子对象链表头,parent域则指向父对象。Device对象还内嵌一个kobject对象,用于引用计数管理并通过它实现设备层次结构。Driver域指向管理该设备的驱动程序对象,而driver_data则是提供给驱动程序的数据。Bus域描述设备所连接的总线类型。
内核提供了相应的函数用于操作device对象。其中Device_register()函数将一个新的device对象插 入设备模型,并自动在/sys/devices下创建一个对应的目录。Device_unregister()完成相反的操作,注销设备对象。Get_device()和put_device()分别增加与减少设备对象的引用计数。通常device结构不单独使用,而是包含在更大的结构中作为一个子结构使用,比如描述PCI设备的struct pci_dev,其中的dev域就是一个device对象。

4.2 drivers
系统中的每个驱动程序由一个device_driver对象描述,对应的数据结构定义为:

struct device_driver {
char *name; 设备驱动程序的名称
struct bus_type *bus; 该驱动所管理的设备挂接的总线类型
struct kobject kobj; 内嵌kobject对象
struct list_head devices; 该驱动所管理的设备链表头
int (*probe)(struct device *dev); 指向设备探测函数,用于探测设备是否可以被该驱动程序管理
int (*remove)(struct device *dev); 用于删除设备的函数
/* some fields omitted*/
};

与device结构类似,device_driver对象依靠内嵌的kobject对象实现引用计数管理和层次结构组织。内核提供类似的函数用于操作 device_driver对象,如get_driver()增加引用计数,driver_register()用于向设备模型插入新的driver对象,同时在sysfs文件系统中创建对应的目录。Device_driver()结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件。

4.3 buses
系统中总线由struct bus_type描述,定义为:

struct bus_type { 
char * name; 总线类型的名称
struct subsystem subsys; 与该总线相关的subsystem
struct kset drivers; 所有与该总线相关的驱动程序集合
struct kset devices; 所有挂接在该总线上的设备集合
struct bus_attribute * bus_attrs; 总线属性
struct device_attribute * dev_attrs; 设备属性
struct driver_attribute * drv_attrs; 驱动程序属性
int (*match)(struct device * dev, struct device_driver * drv);
int (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer,int buffer_size); 
int (*suspend)(struct device * dev, u32 state);
int (*resume)(struct device * dev);
};

每个bus_type对象都内嵌一个subsystem对象,bus_subsys对象管理系统中所有总线类型的subsystem对象。每个bus_type对象都对应/sys/bus目录下的一个子目录,如PCI总线类型对应于/sys/bus/pci。在每个这样的目录下都存在两个子目 录:devices和drivers(分别对应于bus_type结构中的devices和drivers域)。其中devices子目录描述连接在该总线上的所有设备,而drivers目录则描述与该总线关联的所有驱动程序。与device_driver对象类似,bus_type结构还包含几个函数(match()、hotplug()等)处理相应的热插拔、即插即拔和电源管理事件。

4.4 classes
系统中的设备类由 struct class描述,表示某一类设备。所有的class对象都属于class_subsys子系统,对应于sysfs文件系统中的/sys/class目录。 每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备,并通过struct class_device中的dev域(一个指向struct device的指针)关联一个物理设备。这样,一个逻辑设备总是对应于一个物理设备,但是一个物理设备却可能对应于多个逻辑设备。此外,class结构中 还包括用于处理热插拔、即插即拔和电源管理事件的函数,这与device对象和driver对象相似。

总线, 设备, 和驱动

至今, 我们已经看到大量低级框架和一个相对少的例子. 我们试图在本章剩下部分中补充, 随着我们进入 Linux 设备模型的更高级. 为此, 我们介绍一个新的虚拟总线, 我们称为 lddbus, [46 ]并且修改 scullp 驱动来 "接入" 到这个总线.

再一次, 许多驱动作者将不会需要这里涉及的材料. 这个水平的细节通常在总线级别处理, 并且很少作者需要添加一个新总线类型. 这个信息是有用的, 但是, 对任何人好奇在 PCI, USB 等层面的里面发生了什么或者谁需要在那个级别做改变.

14.4.1. 总线

一个总线是处理器和一个或多个设备之间的通道. 为设备模型的目的, 所有的设备都通过一个总线连接, 甚至当它是一个内部的虚拟的,"平台"总线. 总线可以插入另一个 - 一个 USB 控制器常常是一个 PCI 设备, 例如. 设备模型表示在总线和它们控制的设备之间的实际连接.

在 Linux 设备模型中, 一个总线由 bus_type 结构代表, 定义在 <linux/device.h>. 这个结构看来象:

struct bus_type {

char *name;

struct subsystem subsys;

struct kset drivers;

struct kset devices;

int (*match)(struct device *dev, struct device_driver *drv);

struct device *(*add)(struct device * parent, char * bus_id);

int (*hotplug) (struct device *dev, char **envp,

int num_envp, char *buffer, int buffer_size);

/* Some fields omitted */

};

name 成员是总线的名子, 有些同 pci. 你可从这个结构中见到每个总线是它自己的子系统; 这个子系统不位于 sysfs 的顶层, 但是. 相反, 它们在总线子系统下面. 一个总线包含 2 个 ksets, 代表已知的总线的驱动和所有插入总线的设备. 所以, 有一套方法我们马上将涉及.

14.4.1.1. 总线注册

如同我们提过的, 例子源码包含一个虚拟总线实现称为 lddbus. 这个总线建立它的 bus_type 结构, 如下:

struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .hotplug = ldd_hotplug, };

注意很少 bus_type 成员要求初始化; 大部分由设备模型核心处理. 但是, 我们确实不得不指定总线的名子, 以及任何伴随它的方法.

不可避免地, 一个新总线必须注册到系统, 通过一个对 bus_register 的调用. lddbus 代码这样做以这样的方式:

ret = bus_register(&ldd_bus_type);

if (ret)

return ret;

这个调用可能失败, 当然, 因此返回值必须一直检查. 如果它成功, 新总线子系统已被添加到系统; 在 sysfs 中 /sys/bus 的下面可以见到, 并且可能启动添加设备.

如果有必要从系统中去除一个总线(当关联模块被去除, 例如), 调用调用 bus_unregister:

void bus_unregister(struct bus_type *bus);

14.4.1.2. 总线方法

有几个给 bus_type 结构定义的方法; 它们允许总线代码作为一个设备核心和单独驱动之间的中介. 在 2.6.10 内核中定义的方法是:

int (*match)(struct device *device, struct device_driver *driver);

这个方法被调用, 大概多次, 无论何时一个新设备或者驱动被添加给这个总线. 它应当返回一个非零值如果给定的设备可被给定的驱动处理. (我们马上进入设备和 device_driver 结构的细节). 这个函数必须在总线级别处理, 因为那是合适的逻辑存在的地方; 核心内核不能知道如何匹配每个可能总线类型的设备和驱动.

int (*hotplug) (struct device *device, char **envp, int num_envp, char *buffer, int buffer_size);

这个模块允许总线添加变量到环境中, 在产生一个热插拔事件在用户空间之前. 参数和 kset 热插拔方法相同( 在前面的 "热插拔事件产生" 一节中描述 ).

lddbus 驱动有一个非常简单的匹配函数, 它仅仅比较驱动和设备的名子:

static int ldd_match(struct device *dev, struct device_driver *driver)

{

return !strncmp(dev->bus_id, driver->name, strlen(driver->name));

}

当涉及到真实硬件, match 函数常常在有设备自身提供的硬件 ID 和驱动提供的 ID 之间, 做一些比较.

lddbus 热插拔方法看来象这样:

static int ldd_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)

{

envp[0] = buffer;

if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s",

Version) >= buffer_size)

return -ENOMEM;

envp[1] = NULL;

return 0;

}

这里, 我们加入 lddbus 源码的当前版本号, 只是以防有人好奇.

14.4.1.3. 列举设备和驱动

如果你在编写总线级别的代码, 你可能不得不对所有已经注册到你的总线的设备或驱动进行一些操作. 它可能会诱惑人直接进入 bus_type 结构中的各种结构, 但是最好使用已经提供的帮助函数.

为操作每个对总线已知的设备, 使用:

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));

这个函数列举总线上的每个设备, 传递关联的设备结构给 fn, 连同作为 data 来传递的值. 如果 start 是 NULL, 列举从总线的第一个设备开始; 否则列举从 start 之后的第一个设备开始. 如果 fn 返回一个非零值, 列举停止并且那个值从 bus_for_each_dev 返回.

有一个类似的函数来列举驱动:

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

这个函数就像 buf_for_each_dev, 除了, 当然, 它替之作用于驱动.

应当注意, 这 2 个函数持有总线子系统的读者/写者旗标在工作期间. 因此试图一起使用这 2 个会死锁 -- 每个将试图获取同一个旗标. 修改总线的操作( 例如注销设备 )也将锁住. 因此, 小心使用 bus_for_each 函数.

14.4.1.4. 总线属性

几乎 Linux 驱动模型中的每一层都提供一个添加属性的接口, 并且总线层不例外. bus_attribute 类型定义在 <linux/device.h> 如下:

struct bus_attribute {

struct attribute attr;

ssize_t (*show)(struct bus_type *bus, char *buf);

ssize_t (*store)(struct bus_type *bus, const char *buf,

size_t count);

};

我们已经见到 struct attribute 在 "缺省属性" 一节. bus_attribute 类型也包含 2 个方法来显示和设置属性值. 大部分在 kobject 之上的设备模型层以这种方式工作.

已经提供了一个方便的宏为在编译时间创建和初始化 bus_attribute 结构:

BUS_ATTR(name, mode, show, store);

这个宏声明一个结构, 产生它的名子通过前缀字符串 bus_attr_ 到给定的名子.

任何属于一个总线的属性应当明确使用 bus_create_file 来创建:

int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);

属性也可被去除, 使用:

void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

lddbus 驱动创建一个简单属性文件, 再次, 包含源码版本号. show 方法和 bus_attribute 结构设置如下:

static ssize_t show_bus_version(struct bus_type *bus, char *buf)

{

return snprintf(buf, PAGE_SIZE, "%s\n", Version);

}

static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);

创建属性文件在模块加载时间完成:

if (bus_create_file(&ldd_bus_type, &bus_attr_version))

printk(KERN_NOTICE "Unable to create version attribute\n");

这个调用创建一个属性文件(/sys/busldd/version) 包含 lddbus 代码的版本号.

14.4.2. 设备

在最低层, Linux 系统中的每个设备由一个 struct device 代表:

struct device { struct device *parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; struct bus_type *bus; struct device_driver *driver; void *driver_data; void (*release)(struct device *dev); /* Several fields omitted */ };

有许多其他的 struct device 成员只对设备核心代码感兴趣. 但是, 这些成员值得了解:

struct device *parent

设备的 "parent" 设备 -- 它所附着到的设备. 在大部分情况, 一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 设备是一个顶层设备, 这常常不是你所要的.

struct kobject kobj;

代表这个设备并且连接它到层次中的 kobject. 注意, 作为一个通用的规则, device->kobj->parent 等同于 device->parent->kobj.

char bus_id[BUS_ID_SIZE];

唯一确定这个总线上的设备的字符串. PCI 设备, 例如, 使用标准的 PCI ID 格式, 包含域, 总线, 设备, 和功能号.

struct bus_type *bus;

确定设备位于哪种总线.

struct device_driver *driver;

管理这个设备的驱动; 我们查看 struct device_driver 在下一节.

void *driver_data;

一个可能被设备驱动使用的私有数据成员.

void (*release)(struct device *dev);

当对这个设备的最后引用被去除时调用的方法; 它从被嵌入的 kobject 的 release 方法被调用. 注册到核心的所有的设备结构必须有一个 release 方法, 否则内核打印出慌乱的抱怨.

最少, parent, bus_id, bus, 和 release 成员必须在设备结构被注册前设置.

14.4.2.1. 设备注册

通常的注册和注销函数在:

int device_register(struct device *dev);

void device_unregister(struct device *dev);

我们已经见到 lddbus 代码如何注册它的总线类型. 但是, 一个实际的总线是一个设备并且必须单独注册. 为简单起见, lddbus 模块只支持一个单个虚拟总线, 因此这个驱动在编译时建立它的设备:

static void ldd_bus_release(struct device *dev)

{

printk(KERN_DEBUG "lddbus release\n");

}

struct device ldd_bus = {

.bus_id = "ldd0",

.release = ldd_bus_release

};

这是顶级总线, 因此 parent 和 bus 成员留为 NULL. 我们有一个简单的, no-op release 方法, 并且, 作为第一个(并且唯一)总线, 它的名子时 ldd0. 这个总线设备被注册, 使用:

ret = device_register(&ldd_bus);

if (ret)

printk(KERN_NOTICE "Unable to register ldd0\n");

一旦调用完成, 新总线可在 sysfs 中 /sys/devices 下面见到. 任何加到这个总线的设备接着在 /sys/devices/ldd0 下显示.

14.4.2.2. 设备属性

sysfs 中的设备入口可有属性. 相关的结构是:

struct device_attribute {

struct attribute attr;

ssize_t (*show)(struct device *dev, char *buf);

ssize_t (*store)(struct device *dev, const char *buf,

size_t count);

};

这些属性结构可在编译时建立, 使用这些宏:

DEVICE_ATTR(name, mode, show, store);

结果结构通过前缀 dev_attr_ 到给定名子上来命名. 属性文件的实际管理使用通常的函数对来处理:

int device_create_file(struct device *device, struct device_attribute *entry);

void device_remove_file(struct device *dev, struct device_attribute *attr);

struct bus_type 的 dev_attrs 成员指向一个缺省的属性列表, 这些属性给添加到总线的每个设备创建.

14.4.2.3. 设备结构嵌入

设备结构包含设备模型核心需要的来模型化系统的信息. 大部分子系统, 但是, 跟踪关于它们驻留的设备的额外信息. 结果, 对设备很少由空设备结构所代表; 相反, 这个结构, 如同 kobject 结构, 常常是嵌入一个更高级的设备表示中. 如果你查看 struct pci_dev 的定义或者 struct usb_device 的定义, 你会发现一个 struct device 埋在其中. 常常地, 低层驱动甚至不知道 struct device, 但是有例外.

lddbus 驱动创建它自己的设备类型( struct ldd_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);

这个结构允许驱动提供一个实际的名子给设备( 这可以清楚地不同于它的总线 ID, 存储于设备结构) 以及一个这些驱动信息的指针. 给真实设备的结构常常还包含关于供应者信息, 设备型号, 设备配置, 使用的资源, 等等. 可以在 struct pci_dev (<linux/pci.h>) 或者 struct usb_device (<linux/usb.h>) 中找到好的例子. 一个方便的宏( to_ldd_device ) 也为 struct ldd_device 定义, 使得容易转换指向被嵌入的结构的指针为 ldd_device 指针.

lddbus 输出的注册接口看来如此:

int register_ldd_device(struct ldd_device *ldddev)

{

ldddev->dev.bus = &ldd_bus_type;

ldddev->dev.parent = &ldd_bus;

ldddev->dev.release = ldd_dev_release;

strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);

return device_register(&ldddev->dev);

}

EXPORT_SYMBOL(register_ldd_device);

这里, 我们简单地填充一些嵌入的设备结构成员( 单个驱动不应当需要知道这个 ), 并且注册这个设备到驱动核心. 如果我们想添加总线特定的属性到设备, 我们可在这里做.

为显示这个接口如何使用, 我们介绍另一个例子驱动, 我们称为 sculld. 它是在第 8 章介绍的 scullp 驱动上的另一个变体. 它实现通用的内存区设备, 但是 sculld 也使用 Linux 设备模型, 通过 lddbus 接口.

sculld 驱动添加一个它自己的属性到它的设备入口; 这个属性, 称为 dev, 仅仅包含关联的设备号. 这个属性可被一个模块用来加载脚本或者热插拔子系统, 来自动创建设备节点, 当设备被添加到系统时. 这个属性的设置遵循常用模式:

static ssize_t sculld_show_dev(struct device *ddev, char *buf)

{

struct sculld_dev *dev = ddev->driver_data;

return print_dev_t(buf, dev->cdev.dev);

}

static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);

接着, 在初始化时间, 设备被注册, 并且 dev 属性被创建通过下面的函数:

static void sculld_register_dev(struct sculld_dev *dev, int index)

{

sprintf(dev->devname, "sculld%d", index);

dev->ldev.name = dev->devname;

dev->ldev.driver = &sculld_driver;

dev->ldev.dev.driver_data = dev;

register_ldd_device(&dev->ldev);

device_create_file(&dev->ldev.dev, &dev_attr_dev);

}

注意, 我们使用 driver_data 成员来存储指向我们自己的内部的设备结构的指针.

14.4.3. 设备驱动

设备模型跟踪所有对系统已知的驱动. 这个跟踪的主要原因是使驱动核心能匹配驱动和新设备. 一旦驱动在系统中是已知的对象, 但是, 许多其他的事情变得有可能. 设备驱动可输出和任何特定设备无关的信息和配置变量, 例如:

驱动由下列结构定义:

struct device_driver {

char *name;

struct bus_type *bus;

struct kobject kobj;

struct list_head devices;

int (*probe)(struct device *dev);

int (*remove)(struct device *dev);

void (*shutdown) (struct device *dev);

};

再一次, 几个结构成员被忽略( 全部内容见 <linux/device.h> ). 这里, name 是驱动的名子( 它在 sysfs 中出现 ), bus 是这个驱动使用的总线类型, kobj 是必然的 kobject, devices 是当前绑定到这个驱动的所有设备的列表, probe 是一个函数被调用来查询一个特定设备的存在(以及这个驱动是否可以使用它), remove 当设备从系统中去除时被调用, shutdown 在关闭时被调用来关闭设备.

使用 device_driver 结构的函数的形式, 现在应当看来是类似的(因此我们快速涵盖它们). 注册函数是:

int driver_register(struct device_driver *drv);

void driver_unregister(struct device_driver *drv);

通常的属性结构在:

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);

bus_type 结构含有一个成员( drv_attrs ) 指向一套缺省属性, 对所有关联到这个总线的驱动都创建.

14.4.3.1. 驱动结构嵌入

如同大部分驱动核心结构的情形, 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);

这里, 我们要求每个驱动提供特定当前软件版本, 并且 lddbus 输出这个版本字串为它知道的每个驱动. 总线特定的驱动注册函数是:

int register_ldd_driver(struct ldd_driver *driver)

{

int ret;

driver->driver.bus = &ldd_bus_type;

ret = driver_register(&driver->driver);

if (ret)

return ret;

driver->version_attr.attr.name = "version";

driver->version_attr.attr.owner = driver->module;

driver->version_attr.attr.mode = S_IRUGO;

driver->version_attr.show = show_version;

driver->version_attr.store = NULL;

return driver_create_file(&driver->driver, &driver->version_attr);

}

这个函数的第一部分只注册低级的 device_driver 结构到核心; 剩下的建立版本属性. 因为这个属性在运行时被创建, 我们不能使用 DRIVER_ATTR 宏; 反之, driver_attribute 结构必须手工填充. 注意我们设定属性的拥有者为驱动模块, 不是 lddbus 模块; 这样做的理由是可以在为这个属性的 show 函数的实现中见到:

static ssize_t show_version(struct device_driver *driver, char *buf)

{

struct ldd_driver *ldriver = to_ldd_driver(driver);

sprintf(buf, "%s\n", ldriver->version);

return strlen(buf);

}

有人可能认为属性拥有者应当是 lddbus 模块, 因为实现这个属性的函数在那里定义. 这个函数, 但是, 是使用驱动自身所创建的 ldd_driver 结构. 如果那个结构在一个用户空间进程试图读取版本号时要消失, 事情会变得麻烦. 指定驱动模块作为属性的拥有者阻止了模块被卸载, 在用户空间保持属性文件打开时. 因为每个驱动模块创建一个对 lddbus 模块的引用, 我们能确信 lddbus 不会在一个不合适的时间被卸载.

为完整起见, sculld 创建它的 ldd_driver 结构如下:

static struct ldd_driver sculld_driver = { .version = "$Revision: 1.1 $", .module = THIS_MODULE, .driver = { .name = "sculld", }, };

一个简单的对 register_ldd_driver 的调用添加它到系统中. 一旦完成初始化, 驱动信息可在 sysfs 中见到:

$ tree /sys/bus/ldd/drivers

/sys/bus/ldd/drivers

`-- sculld

|-- sculld0 -> ../../../../devices/ldd0/sculld0

|-- sculld1 -> ../../../../devices/ldd0/sculld1

|-- sculld2 -> ../../../../devices/ldd0/sculld2

|-- sculld3 -> ../../../../devices/ldd0/sculld3

`-- version

linux设备模型详解相关推荐

  1. Linux设备树详解

    Linux设备树详解 设备树小故事 设备树文件 使用设备树 修改设备树文件 编译设备树 异常处理 编写驱动文件 参考资料 设备树小故事 设备树(Device Tree),将这个词分开就是"设 ...

  2. 【Linux驱动开发】Linux设备树详解

    目录 一.设备树基础 1.概念 2.文件格式 3.编译工具 二.DTS语法 1..dtsi 头文件 2. 设备节点 3.标准属性 4.compatible 属性详解 5.修改设备树文件,增加或修改节点 ...

  3. 奇小葩讲设备树(4/5)-- Linux设备树详解(四)kernel的解析

    uboot将一些参数,设备树文件传给内核,那么内核如何处理这些设备树文件呢?本章就kernel解析设备树的过程和原理,本章的主要内容以Device Tree相关的数据流分析为索引,对ARM linux ...

  4. linux 设备树详解

    2 Linux 设备树 2.1 什么是设备树? 设备树(Device Tree),将这个词分开就是"设备"和"树",描述设备树的文件叫做DTS(Device T ...

  5. 正点原子----Linux设备树详解

    linux设备树 1.什么是设备树 2.DTS.DTB.DTC的关系 3.如何编译设备树 4.DTS基本结构 4.1.语法 4.2.设备树在系统中的体现 4.3.尝试自己添加节点 4.4.尝试对根节点 ...

  6. linux 视频教程 韦山东,韦东山 linux 设备树详解

    简 介 设备树视频录制完毕,29节,现在只要69元.学员对此课程的评价:这是最翔实最实惠最精益求精的设备树教程,感兴趣的了解一下, 以下是课程详情~ [设备树是什么?] 设备树是一种机制,用文本的方式 ...

  7. linux epoll 模型详解

    在linux的网络编程中,很长的时间都在使用select来做事件触发.在linux新的内核中,有了一种替换它的机制,就是epoll. 相比于select,epoll最大的好处在于它不会随着监听fd数目 ...

  8. 奇小葩讲设备树(5/5)-- Linux设备树详解(五)设备树的使用

    对于任何的知识来说,了解了理论的知识,知道了设备树怎么解析用以代替传统的范式之后,我们需要知道怎么使用设备树.对于使用我们分两部分,一部分是它有哪些接口,能做些什么,至于怎么编写dts文件本章不讨论. ...

  9. 奇小葩讲设备树(2/5)-- Linux设备树详解(二)文件构成

    设备树就是描述单板资源以及设备的一种文本文件.至于出现的原因,基本的语法和使用方法,上一章节做了基本的介绍.本篇文章主要是更深层次的探讨设备文件的构成. 1. devie tree的编译 Device ...

最新文章

  1. thinkphp-比较标签-eq
  2. POJ 2485-Highways
  3. 利用TensorFlow2.0为胆固醇、血脂、血压数据构建时序深度学习模型(python完整源代码)
  4. 在Win2003、Win7、XP中配置IIS搭建ASP.NET Web网站或应用程序(1)
  5. 第三次学JAVA再学不好就吃翔(part91)--Map接口
  6. Ubuntu 12.04(32位)安装Oracle 11g(32位)全过程以及几乎所有问题的解决办法
  7. 面试题,你做了哪些事情来提升自己的沟通能力?
  8. mysql表空间自增_Oracle 默认表空间问题及自增变量设置
  9. 前端宽度一至显示宽度不一致_html2canvas在不同设备生成图片大小不一致问题
  10. Parse error: syntax error, unexpected '__data' (T_STRING), expecting ',' or ')'
  11. 关于 创建网络套接字失败 10106
  12. 最棒的SpringCloud微服务后台管理系统
  13. 2020计算机网络练习题记录(1)
  14. OV5640的初始化
  15. 数理统计复习笔记九——正态性检验
  16. 解决win10新建文本文档内容可以打开但显示图标不对的问题
  17. 详解 Win8 KMS 激活原理_-Chaz-_新浪博客
  18. linux 下不错的html编辑器bluefish
  19. pig的安装、pig的数据模型和pig的常用命令
  20. 计算机设计大赛软件开发类作品填写模板

热门文章

  1. 浏览器同源政策之ajax请求不能发送
  2. 祝大家新春快乐,阖家安康
  3. Python3-基础语法
  4. 小红书笔记上精选方法技巧有哪些
  5. [论文阅读笔记31]UER: An Open-Source Toolkit for Pre-training Models
  6. OpenCV粘连对象计数
  7. Linux用户态与内核态通信的几种方式(待完善)
  8. 虹科云课堂 | 干货不断!汽车专题直播月本周课程预告
  9. Android程序员必看,资深Android面试题
  10. vim 配置及插件安装管理(超级详细)