Linux设备模型 学习总结
2010-12-29 21:03
387 查看
来自:湛蓝思微 blog
看LDD3
中设备模型一章,觉得思维有些混乱。这里从整体的角度来理理思路。
本文从四个方面来总结一些内容:
1.
底层数据结构:kobject,kset.
2.linux
设备模型层次关系:bus_type,device,device_driver.
3.
集成:PCI
设备驱动模型实例及设备,设备驱动注册源码的简单分析.
4.
面向对象的思想在linux
设备模型中的应用分析.
一、
底层数据结构:kobject,kset
先说说模型的意义:
总体来说是为了系统地管理所有设备。
在具体实现方面分两个层次:
一是底层数据结构来实现基本对象及其层次关系:kobjects
和ksets
。
二是基于这两个底层数据结构上实现的设备模型:总线,设备,驱动。
kobject
结合面向对象的思维。这个kobject
属于最基础的结构,也就是最高抽象层(
有点像java
中的Cobject
类)
。任何一个设备模型如总线,设备,驱动都属于一个kobject
。在实现上这种派生关系就是在结构体中包含一个kobject
的变量。
这个在层次上处理最顶层的kobject
结构提供了所有模型需要的最基本的功能:
1
引用计数
用于内核维护其存在与消亡
2 sysfs
表示
每个sys/
下的对象对应着一个kobject
。
3
热拔插事件处理。 处理设备的热拔插事件。
Kobjects
在内核中对应有一套申请,初始化,添加,注册,计数操作,释放等函数
struct kobject {
const char * k_name;
名
char name[KOBJ_NAME_LEN];
struct kref kref;
计数
struct list_head entry;
用于连接到同类kobjects
的链表
struct kobject * parent;
用于实现层次,指向其父对象。
struct kset * kset;
用于实现层次,所属的集合
struct kobj_type * ktype;
指向对象的类型。
struct dentry * dentry;
指示在sysfs
中的目录项
wait_queue_head_t poll;
}; (linux 2.6.18)
Kset
和kobj_type
Kset
在概念上是一个集合或者叫容器。实现了对象的层次。
所有属于一个ksets
的对象(kobject)
的parent
都指向该ksets
的kobj.
同时这个对象都连接到kset
的list
表上。 同时位于ksets
层次之上的是subsys
,在最新的内核中已经取消subsys
,因为它本质上也就是一个ksets
。Kset
有一套类似kobject
的操作,实现上只是进一步调用其自身kobj
的相应操作,毕竟ksets
本质上也是一个kobject
。
struct kset {
struct subsystem * subsys;
在最新内核中已经没有subsys
概念了。统一用ksets
struct kobj_type * ktype;
类型。
struct list_head list;
同一kset
的链表
spinlock_t list_lock;
struct kobject kobj;
自身的kobjects
struct kset_uevent_ops * uevent_ops;
};(linux 2.6.18)
最后
属于同一个集合的对象可以拥有共同的属性:ktype
。
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops * sysfs_ops;
struct attribute ** default_attrs;
};
所谓的属性更具体一点说就是一些键值对。并且在sysfs_ops
中的show
函数被文件系统调用来显示sys/
下面对应入口各属性的值。
如此
,kobjects
与ksets
实现层次树的底层骨架。
进一步地,通过封装这些底层结构来实现上层的设备驱动模型。
内核设备驱动模型层次划分三个方面:总线,设备,驱动。
二、
linux
设备模型层次关系:bus_type,device,device_driver
基本关系简要的概括如下:
驱动核心可以注册多种类型的总线。
每种总线下面可以挂载许多设备。(
通过kset devices)
每种总线下可以用很多设备驱动。(
通过包含一个kset
drivers)}
每个驱动可以处理一组设备。
这种基本关系的建立源于实际系统中各种总线,设备,驱动结构的抽象。
下面看看三者数据结构的定义。
首先是总线,bus_type.
struct bus_type {
const char * name;
struct
subsystem subsys;//
代表自身
struct kset drivers; //
当前总线的设备驱动集合
struct kset devices; //
所有设备集合
struct klist klist_devices;
struct klist klist_drivers;
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 (*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 (*resume)(struct device * dev);
};
这是2.6.18
的定义。源码能说明一切。下面是设备device
的定义:
struct device {
struct device * parent; //
父设备,一般一个bus
也对应一个设备。
struct kobject kobj;//
代表自身
char bus_id[BUS_ID_SIZE];
struct bus_type * bus; /*
所属的总线
*/
struct device_driver *driver; /*
匹配的驱动*/
void *driver_data; /*
data private to the driver
指向驱动 */
void *platform_data; /* Platform specific data
,由驱动定义并使用*/
///
更多字段忽略了
};
下面是设备驱动定义:
struct device_driver {
const char * name;
struct bus_type * bus;//
所属总线
struct
completion unloaded;
struct kobject kobj;//
代表自身
struct klist klist_devices;//
设备列表
struct klist_node knode_bus;
struct
module * owner;
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);
};
OK
。基本的东西弄明白了。通过PCI
驱动中设备模型的实例来看看细节。
三、
集成:PCI
设备驱动模型实例及设备,设备驱动注册源码的简单分析.
先看pci
总线类型定义:
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.suspend = pci_device_suspend,
.shutdown = pci_device_shutdown,
.resume = pci_device_resume,
.dev_attrs = pci_dev_attrs,
};
然后是pci
设备和驱动。pci
设备和pci
驱动没有直接使用device
和device_driver
,而是将二者封装起来,加上pci
特定信息构成pci_dev
和pci_driver
。当然,意义是一样的。
struct pci_dev {
/* PCI
设备的ID
信息*/
unsigned int devfn;
unsigned short vendor;
unsigned short device;
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int class;
/* ... */
struct
pci_bus *bus; //
所属pci
总线
struct pci_driver *driver; //
所属的pci
驱动
/* ... */
struct device dev; //
设备自身
/* ... */
};
这里省略了许多PCI
设备特定的信息,如中断,资源等。。
当一个PCI
设备被发现, PCI
核心在内存中创建一个 struct pci_dev
类型的新变量。
这 个 PCI
设备的总线特定的成员被 PCI
核心初始化( devfn, vendor, device,
和其他成员),
并 且 struct device
变量的 parent
变量被设置为 PCI
总线设备(
注意总线也不仅有一个bus_type
结构,还对应一个设备 device) bus
变量被设置指向 pci_bus_type
结构.
接下来 name
和 bus_id
变量被设置,
根据读自 PCI
设 备的 name
和 ID.
在 PCI
设备结构被初始化之后, pci
设备被注册到驱动核心,
调用 device_register(&dev->dev);
在device_register
函数中,kobject
被注册到驱动核心,pci
设备被添加到pci
总线的设备列表中,
热拔插事件产生,同时kobject
被添加到parent
的链表中,sysfs
入口也被添加。
PCI
设备的发现是通过特定代码探测PCI
空间来实现的。PCI
设备由内核自动生成的。这样在注册pci
驱动的时候PCI
设备已经注册,其属性如ID
的信息都已经是被初始化好了。
最后是pci_driver:
struct pci_driver {
struct list_head node;
char *name; //
驱动name
const struct pci_device_id *id_table; /*
驱动支持的设备ID
列表 */
int (*probe) (struct pci_dev *dev, const struct pci_device_id
*id); /* New device inserted */
void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not
a hot-plug capable driver) */
int (*suspend) (struct pci_dev *dev, pm_message_t state); /*
Device suspended */
int (*resume) (struct pci_dev
*dev);
/* Device woken up */
int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int
enable); /* Enable wake event */
void (*shutdown) (struct pci_dev *dev);
struct pci_error_handlers
*err_handler;
struct device_driver driver; //
设备驱动
struct pci_dynids dynids;
};
这里列出了pci_bus
,pci_dev,pci_driver
的定义。它们的关系与bus,device,driver
一样。pci_bus
直接是一个bus_type
结构初始化的实体。
pci_dev
由内核探测,并且注册到驱动核心。pci
设备的初始化和注册分两个方面,一是pci
设备信息如ID
,资源等,二是pci_dev.dev
的注册。调用register_device(struct device * dev)
来完成。
pci_driver
一般由模块定义并且在模块初始化函数中向内核注册。也要两个方面,一是pci_driver
中特定于PCI
的方法,支持的ID
列表等的初始化;二是内嵌的 device_driver
的注册,使用register_driver(struct
device_driver * drv)
。
这就有点像面向对象中子类与父类的关系,子类构造函数的调用隐含父类构造函数的调用。
没有register_device(dev)
和register_driver(drv)
的注册,驱动核心就不知道设备和驱动的存在,sysfs
也没有相关的入口。
最后一件事,看看register_device(dev)
和register_driver(drv)
的代码。
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
device_register-->device_initialize(dev);//
初始化设备各个字段
void device_initialize(struct
device *dev)
{
kobj_set_kset_s(dev, devices_subsys); //
所有的dev
属于devices_subsys
这个集合
kobject_init(&dev->kobj); //
初始kobj
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
device_init_wakeup(dev, 0);
}
device_register-->device_add(dev);
int device_add(struct device *dev)
//
主要流程
{
dev = get_device(dev);
parent = get_device(dev->parent);
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
dev->kobj.parent = &parent->kobj;
kobject_add(&dev->kobj);//
将自身kobject
加入到层次结构中,并且建立sysfs entry.
//
设置uevent_attr:
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IWUSR;
if (dev->driver)
dev->uevent_attr.attr.owner = dev->driver->owner;
dev->uevent_attr.store = store_uevent;
device_create_file(dev, &dev->uevent_attr);
//
建立显示设备号的sysfs
入口,即当前设备入口下的"dev"
文件显示设备主从设备号。
if (MAJOR(dev->devt)) {
attr->attr.name = "dev";
attr->attr.mode = S_IRUGO;
if (dev->driver)
attr->attr.owner = dev->driver->owner;
attr->show = show_dev;
error = device_create_file(dev, attr);
}
//
建立类的sysfs
符号连接
if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,
"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
dev->bus_id);}
sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
class_name = make_class_name(dev->class->name, &dev->kobj);
sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
}
error = bus_add_device(dev);//
添加一些bus
相关的sysfs
符号连接
/*
设置环境变量,然后调用call_usermodehelper (argv[0], argv, envp, 0);
引起热拔插事件用户空间脚本执行。*/
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev); /*
如 果dev->driver
已经存在,调用device_bind_driver(dev);
进行绑定,否则遍历dev->bus
上 drivers
列表,调用dev->bus.match(dev,drv)
来看是否有一个驱动与该dev
匹配。如果匹配则绑定。*/
} OK
,上述是主要流程。。
下面是 register_driver(drv)
函数:
int driver_register(struct
device_driver * drv)
{
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown &&
drv->shutdown)) {
printk(KERN_WARNING "Driver ''''%s'''' needs updating - please
use bus_type methods/n", drv->name);
}
klist_init(&drv->klist_devices, klist_devices_get,
klist_devices_put);
init_completion(&drv->unloaded);
return bus_add_driver(drv);
}
driver_register(drv);-->bus_add_driver(drv);
int bus_add_driver(struct
device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
error =
kobject_set_name(&drv->kobj, "%s", drv->name);
drv->kobj.kset
= &bus->drivers; //
驱动隶属于总线的驱动集合
error = kobject_register(&drv->kobj);//
注册自身kobject
driver_attach(drv);//
添加驱动到总线
klist_add_tail(&drv->knode_bus,
&bus->klist_drivers);
module_add_driver(drv->owner, drv);
driver_add_attrs(bus,
drv);
add_bind_files(drv);
}
driver_register(drv);-->bus_add_driver(drv);-->driver_attach(drv);
void driver_attach(struct
device_driver * drv)
{
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
对总线上的每个设备dev,
调用__driver_attach(dev,drv);
最终调用
driver_probe_device(drv, dev);
driver_register(drv);-->bus_add_driver(drv);-->
driver_attach(drv);
-->__driver_attach(dev,drv);-->driver_probe_device(drv, dev);
int driver_probe_device(struct
device_driver * drv, struct device * dev)
{
if (drv->bus->match && !drv->bus->match(dev, drv))
goto Done;//
优先调用总线提供匹配方法
dev->driver = drv;
if (dev->bus->probe) {
ret = dev->bus->probe(dev);//
总线的探测方法
}
else if (drv->probe)
{
ret = drv->probe(dev); //
用dev->driver
的探测方法
}
device_bind_driver(dev); /*
探测成功则绑定设备到驱动,添加dev
到drv
的设备列表并且建立驱动与设备在sys/
入口中相互关联的符号连接*/
goto Done;
Done:
return ret;
}
乱七八糟的。主线还是模型的层次关系。对kobject
,kset
细节中关于属性,热拔插,sys
入口的部分没有深入。或许,理解总体和设计思想是更重要的。人的精力真的有限。
四、
面向对象的思想在linux
设备模型中的应用分析.
通 过设备模型,看到了面向对象编程思想用C
语言的实现。内核中常见到封装了数据和方法的结构体,这是面向对象封装特性的实现。而这里展现的更多的是继承方面
的实现。比如说pci_driver
,它的父类是device_driver
,而更上一层是一个kobject
。在C++
中,继承一个父类则子类中相应的 包含父类的一个实例。内核中也是通过包含一个父类的实体来实现这种派生关系。因此,一个pci_driver
内部必然包含一个 device_driver,
同样,device_driver
内部必然包含一个kobject
。
上面提到过,注册一个模型的过程类似于面向对象中构造函数的调用。子类需要调用父类构造函数来完成自身的构造。再来看看注册一个pci_driver
的过程:
pci_register_driver(struct pci_driver *driver)
-->driver_register(&drv->driver);
-->kobject_register(&drv->kobj);
这不是OO
中的继承么?
设 备模型源码中还能找到多态(
虚函数)
的思想。看到pci_driver
和device_driver
中提供了差不多同名的方法不觉得奇怪吗??它们不同的
地方在于参数。pci_driver
中方法的参数是pci_device *
dev ,
而device_driver
方法的参数则是
device * dev
。这么安排是有意的!
最典型的例子莫过于platform_driver
和device_driver
。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
这显然比pci_driver
来得简洁。platform_driver
除了包含一个device_driver
,其它就是5
个与device_driver
同名的方法。
注册一个platform_driver
的过程:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
这里设置了platform_driver
包含的device_driver
的函数指针。看看这些函数中的platform_drv_probe
。
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return
drv->probe(dev);
}
这里出现了两个指针类型转换(
通过container_of()
宏实现的)
,然后调用platform_driver
提供的probe
函数。
考 虑一下platform_driver
的注册过程。每个驱动注册过程相同。如前面分析过的,进入到driver_register
后,设备驱动 device_driver
层的probe
将会被调用来探测设备,这个函数像上面源码所指示的那样完成类型转化调用其子类platform_driver
层的probe
函数来完成具体的功能。 那么,从device_driver
层看来,相同的函数调用由子类来完成了不同的具体功能。这不是多态的思想么??
这里非常粗浅的分析了linux
设备模型中使用C
实现面向对象的三大要素(
封装,继承,多态)
的基本思想。用C
来实现确实做的工作要多一些,不过灵活性更高了。怪不得linus
炮轰C++.
"
使用优秀的、高效的、系统级的和可移植的C++
的唯一方式,最终还是限于使用C
本身具有的所有特性。"
看LDD3
中设备模型一章,觉得思维有些混乱。这里从整体的角度来理理思路。
本文从四个方面来总结一些内容:
1.
底层数据结构:kobject,kset.
2.linux
设备模型层次关系:bus_type,device,device_driver.
3.
集成:PCI
设备驱动模型实例及设备,设备驱动注册源码的简单分析.
4.
面向对象的思想在linux
设备模型中的应用分析.
一、
底层数据结构:kobject,kset
先说说模型的意义:
总体来说是为了系统地管理所有设备。
在具体实现方面分两个层次:
一是底层数据结构来实现基本对象及其层次关系:kobjects
和ksets
。
二是基于这两个底层数据结构上实现的设备模型:总线,设备,驱动。
kobject
结合面向对象的思维。这个kobject
属于最基础的结构,也就是最高抽象层(
有点像java
中的Cobject
类)
。任何一个设备模型如总线,设备,驱动都属于一个kobject
。在实现上这种派生关系就是在结构体中包含一个kobject
的变量。
这个在层次上处理最顶层的kobject
结构提供了所有模型需要的最基本的功能:
1
引用计数
用于内核维护其存在与消亡
2 sysfs
表示
每个sys/
下的对象对应着一个kobject
。
3
热拔插事件处理。 处理设备的热拔插事件。
Kobjects
在内核中对应有一套申请,初始化,添加,注册,计数操作,释放等函数
struct kobject {
const char * k_name;
名
char name[KOBJ_NAME_LEN];
struct kref kref;
计数
struct list_head entry;
用于连接到同类kobjects
的链表
struct kobject * parent;
用于实现层次,指向其父对象。
struct kset * kset;
用于实现层次,所属的集合
struct kobj_type * ktype;
指向对象的类型。
struct dentry * dentry;
指示在sysfs
中的目录项
wait_queue_head_t poll;
}; (linux 2.6.18)
Kset
和kobj_type
Kset
在概念上是一个集合或者叫容器。实现了对象的层次。
所有属于一个ksets
的对象(kobject)
的parent
都指向该ksets
的kobj.
同时这个对象都连接到kset
的list
表上。 同时位于ksets
层次之上的是subsys
,在最新的内核中已经取消subsys
,因为它本质上也就是一个ksets
。Kset
有一套类似kobject
的操作,实现上只是进一步调用其自身kobj
的相应操作,毕竟ksets
本质上也是一个kobject
。
struct kset {
struct subsystem * subsys;
在最新内核中已经没有subsys
概念了。统一用ksets
struct kobj_type * ktype;
类型。
struct list_head list;
同一kset
的链表
spinlock_t list_lock;
struct kobject kobj;
自身的kobjects
struct kset_uevent_ops * uevent_ops;
};(linux 2.6.18)
最后
属于同一个集合的对象可以拥有共同的属性:ktype
。
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops * sysfs_ops;
struct attribute ** default_attrs;
};
所谓的属性更具体一点说就是一些键值对。并且在sysfs_ops
中的show
函数被文件系统调用来显示sys/
下面对应入口各属性的值。
如此
,kobjects
与ksets
实现层次树的底层骨架。
进一步地,通过封装这些底层结构来实现上层的设备驱动模型。
内核设备驱动模型层次划分三个方面:总线,设备,驱动。
二、
linux
设备模型层次关系:bus_type,device,device_driver
基本关系简要的概括如下:
驱动核心可以注册多种类型的总线。
每种总线下面可以挂载许多设备。(
通过kset devices)
每种总线下可以用很多设备驱动。(
通过包含一个kset
drivers)}
每个驱动可以处理一组设备。
这种基本关系的建立源于实际系统中各种总线,设备,驱动结构的抽象。
下面看看三者数据结构的定义。
首先是总线,bus_type.
struct bus_type {
const char * name;
struct
subsystem subsys;//
代表自身
struct kset drivers; //
当前总线的设备驱动集合
struct kset devices; //
所有设备集合
struct klist klist_devices;
struct klist klist_drivers;
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 (*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 (*resume)(struct device * dev);
};
这是2.6.18
的定义。源码能说明一切。下面是设备device
的定义:
struct device {
struct device * parent; //
父设备,一般一个bus
也对应一个设备。
struct kobject kobj;//
代表自身
char bus_id[BUS_ID_SIZE];
struct bus_type * bus; /*
所属的总线
*/
struct device_driver *driver; /*
匹配的驱动*/
void *driver_data; /*
data private to the driver
指向驱动 */
void *platform_data; /* Platform specific data
,由驱动定义并使用*/
///
更多字段忽略了
};
下面是设备驱动定义:
struct device_driver {
const char * name;
struct bus_type * bus;//
所属总线
struct
completion unloaded;
struct kobject kobj;//
代表自身
struct klist klist_devices;//
设备列表
struct klist_node knode_bus;
struct
module * owner;
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);
};
OK
。基本的东西弄明白了。通过PCI
驱动中设备模型的实例来看看细节。
三、
集成:PCI
设备驱动模型实例及设备,设备驱动注册源码的简单分析.
先看pci
总线类型定义:
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.suspend = pci_device_suspend,
.shutdown = pci_device_shutdown,
.resume = pci_device_resume,
.dev_attrs = pci_dev_attrs,
};
然后是pci
设备和驱动。pci
设备和pci
驱动没有直接使用device
和device_driver
,而是将二者封装起来,加上pci
特定信息构成pci_dev
和pci_driver
。当然,意义是一样的。
struct pci_dev {
/* PCI
设备的ID
信息*/
unsigned int devfn;
unsigned short vendor;
unsigned short device;
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int class;
/* ... */
struct
pci_bus *bus; //
所属pci
总线
struct pci_driver *driver; //
所属的pci
驱动
/* ... */
struct device dev; //
设备自身
/* ... */
};
这里省略了许多PCI
设备特定的信息,如中断,资源等。。
当一个PCI
设备被发现, PCI
核心在内存中创建一个 struct pci_dev
类型的新变量。
这 个 PCI
设备的总线特定的成员被 PCI
核心初始化( devfn, vendor, device,
和其他成员),
并 且 struct device
变量的 parent
变量被设置为 PCI
总线设备(
注意总线也不仅有一个bus_type
结构,还对应一个设备 device) bus
变量被设置指向 pci_bus_type
结构.
接下来 name
和 bus_id
变量被设置,
根据读自 PCI
设 备的 name
和 ID.
在 PCI
设备结构被初始化之后, pci
设备被注册到驱动核心,
调用 device_register(&dev->dev);
在device_register
函数中,kobject
被注册到驱动核心,pci
设备被添加到pci
总线的设备列表中,
热拔插事件产生,同时kobject
被添加到parent
的链表中,sysfs
入口也被添加。
PCI
设备的发现是通过特定代码探测PCI
空间来实现的。PCI
设备由内核自动生成的。这样在注册pci
驱动的时候PCI
设备已经注册,其属性如ID
的信息都已经是被初始化好了。
最后是pci_driver:
struct pci_driver {
struct list_head node;
char *name; //
驱动name
const struct pci_device_id *id_table; /*
驱动支持的设备ID
列表 */
int (*probe) (struct pci_dev *dev, const struct pci_device_id
*id); /* New device inserted */
void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not
a hot-plug capable driver) */
int (*suspend) (struct pci_dev *dev, pm_message_t state); /*
Device suspended */
int (*resume) (struct pci_dev
*dev);
/* Device woken up */
int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int
enable); /* Enable wake event */
void (*shutdown) (struct pci_dev *dev);
struct pci_error_handlers
*err_handler;
struct device_driver driver; //
设备驱动
struct pci_dynids dynids;
};
这里列出了pci_bus
,pci_dev,pci_driver
的定义。它们的关系与bus,device,driver
一样。pci_bus
直接是一个bus_type
结构初始化的实体。
pci_dev
由内核探测,并且注册到驱动核心。pci
设备的初始化和注册分两个方面,一是pci
设备信息如ID
,资源等,二是pci_dev.dev
的注册。调用register_device(struct device * dev)
来完成。
pci_driver
一般由模块定义并且在模块初始化函数中向内核注册。也要两个方面,一是pci_driver
中特定于PCI
的方法,支持的ID
列表等的初始化;二是内嵌的 device_driver
的注册,使用register_driver(struct
device_driver * drv)
。
这就有点像面向对象中子类与父类的关系,子类构造函数的调用隐含父类构造函数的调用。
没有register_device(dev)
和register_driver(drv)
的注册,驱动核心就不知道设备和驱动的存在,sysfs
也没有相关的入口。
最后一件事,看看register_device(dev)
和register_driver(drv)
的代码。
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
device_register-->device_initialize(dev);//
初始化设备各个字段
void device_initialize(struct
device *dev)
{
kobj_set_kset_s(dev, devices_subsys); //
所有的dev
属于devices_subsys
这个集合
kobject_init(&dev->kobj); //
初始kobj
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
device_init_wakeup(dev, 0);
}
device_register-->device_add(dev);
int device_add(struct device *dev)
//
主要流程
{
dev = get_device(dev);
parent = get_device(dev->parent);
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
dev->kobj.parent = &parent->kobj;
kobject_add(&dev->kobj);//
将自身kobject
加入到层次结构中,并且建立sysfs entry.
//
设置uevent_attr:
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IWUSR;
if (dev->driver)
dev->uevent_attr.attr.owner = dev->driver->owner;
dev->uevent_attr.store = store_uevent;
device_create_file(dev, &dev->uevent_attr);
//
建立显示设备号的sysfs
入口,即当前设备入口下的"dev"
文件显示设备主从设备号。
if (MAJOR(dev->devt)) {
attr->attr.name = "dev";
attr->attr.mode = S_IRUGO;
if (dev->driver)
attr->attr.owner = dev->driver->owner;
attr->show = show_dev;
error = device_create_file(dev, attr);
}
//
建立类的sysfs
符号连接
if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,
"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
dev->bus_id);}
sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
class_name = make_class_name(dev->class->name, &dev->kobj);
sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
}
error = bus_add_device(dev);//
添加一些bus
相关的sysfs
符号连接
/*
设置环境变量,然后调用call_usermodehelper (argv[0], argv, envp, 0);
引起热拔插事件用户空间脚本执行。*/
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev); /*
如 果dev->driver
已经存在,调用device_bind_driver(dev);
进行绑定,否则遍历dev->bus
上 drivers
列表,调用dev->bus.match(dev,drv)
来看是否有一个驱动与该dev
匹配。如果匹配则绑定。*/
} OK
,上述是主要流程。。
下面是 register_driver(drv)
函数:
int driver_register(struct
device_driver * drv)
{
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown &&
drv->shutdown)) {
printk(KERN_WARNING "Driver ''''%s'''' needs updating - please
use bus_type methods/n", drv->name);
}
klist_init(&drv->klist_devices, klist_devices_get,
klist_devices_put);
init_completion(&drv->unloaded);
return bus_add_driver(drv);
}
driver_register(drv);-->bus_add_driver(drv);
int bus_add_driver(struct
device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
error =
kobject_set_name(&drv->kobj, "%s", drv->name);
drv->kobj.kset
= &bus->drivers; //
驱动隶属于总线的驱动集合
error = kobject_register(&drv->kobj);//
注册自身kobject
driver_attach(drv);//
添加驱动到总线
klist_add_tail(&drv->knode_bus,
&bus->klist_drivers);
module_add_driver(drv->owner, drv);
driver_add_attrs(bus,
drv);
add_bind_files(drv);
}
driver_register(drv);-->bus_add_driver(drv);-->driver_attach(drv);
void driver_attach(struct
device_driver * drv)
{
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
对总线上的每个设备dev,
调用__driver_attach(dev,drv);
最终调用
driver_probe_device(drv, dev);
driver_register(drv);-->bus_add_driver(drv);-->
driver_attach(drv);
-->__driver_attach(dev,drv);-->driver_probe_device(drv, dev);
int driver_probe_device(struct
device_driver * drv, struct device * dev)
{
if (drv->bus->match && !drv->bus->match(dev, drv))
goto Done;//
优先调用总线提供匹配方法
dev->driver = drv;
if (dev->bus->probe) {
ret = dev->bus->probe(dev);//
总线的探测方法
}
else if (drv->probe)
{
ret = drv->probe(dev); //
用dev->driver
的探测方法
}
device_bind_driver(dev); /*
探测成功则绑定设备到驱动,添加dev
到drv
的设备列表并且建立驱动与设备在sys/
入口中相互关联的符号连接*/
goto Done;
Done:
return ret;
}
乱七八糟的。主线还是模型的层次关系。对kobject
,kset
细节中关于属性,热拔插,sys
入口的部分没有深入。或许,理解总体和设计思想是更重要的。人的精力真的有限。
四、
面向对象的思想在linux
设备模型中的应用分析.
通 过设备模型,看到了面向对象编程思想用C
语言的实现。内核中常见到封装了数据和方法的结构体,这是面向对象封装特性的实现。而这里展现的更多的是继承方面
的实现。比如说pci_driver
,它的父类是device_driver
,而更上一层是一个kobject
。在C++
中,继承一个父类则子类中相应的 包含父类的一个实例。内核中也是通过包含一个父类的实体来实现这种派生关系。因此,一个pci_driver
内部必然包含一个 device_driver,
同样,device_driver
内部必然包含一个kobject
。
上面提到过,注册一个模型的过程类似于面向对象中构造函数的调用。子类需要调用父类构造函数来完成自身的构造。再来看看注册一个pci_driver
的过程:
pci_register_driver(struct pci_driver *driver)
-->driver_register(&drv->driver);
-->kobject_register(&drv->kobj);
这不是OO
中的继承么?
设 备模型源码中还能找到多态(
虚函数)
的思想。看到pci_driver
和device_driver
中提供了差不多同名的方法不觉得奇怪吗??它们不同的
地方在于参数。pci_driver
中方法的参数是pci_device *
dev ,
而device_driver
方法的参数则是
device * dev
。这么安排是有意的!
最典型的例子莫过于platform_driver
和device_driver
。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
这显然比pci_driver
来得简洁。platform_driver
除了包含一个device_driver
,其它就是5
个与device_driver
同名的方法。
注册一个platform_driver
的过程:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
这里设置了platform_driver
包含的device_driver
的函数指针。看看这些函数中的platform_drv_probe
。
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return
drv->probe(dev);
}
这里出现了两个指针类型转换(
通过container_of()
宏实现的)
,然后调用platform_driver
提供的probe
函数。
考 虑一下platform_driver
的注册过程。每个驱动注册过程相同。如前面分析过的,进入到driver_register
后,设备驱动 device_driver
层的probe
将会被调用来探测设备,这个函数像上面源码所指示的那样完成类型转化调用其子类platform_driver
层的probe
函数来完成具体的功能。 那么,从device_driver
层看来,相同的函数调用由子类来完成了不同的具体功能。这不是多态的思想么??
这里非常粗浅的分析了linux
设备模型中使用C
实现面向对象的三大要素(
封装,继承,多态)
的基本思想。用C
来实现确实做的工作要多一些,不过灵活性更高了。怪不得linus
炮轰C++.
"
使用优秀的、高效的、系统级的和可移植的C++
的唯一方式,最终还是限于使用C
本身具有的所有特性。"
相关文章推荐
- Linux内核与驱动开发学习总结:设备驱动模型(九)
- Linux设备模型 学习总结682057749
- Linux设备模型 学习总结(转)
- Linux设备模型 学习总结682057749
- Linux设备模型 学习总结682057749
- Linux设备模型 学习总结
- Linux设备驱动程序学习(12) -Linux设备模型(底层原理简介)
- Linux设备驱动程序学习-Linux设备模型(总线、设备、驱动程序和类)
- Linux设备模型学习参考方法
- Linux设备模型(总结)
- 【加入自己的部分内容】Linux设备驱动模型学习之基础篇--Kobject.txt翻译
- Linux设备驱动程序学习之设备模型一
- LINUX 驱动学习之路 -设备模型之别人的理解(3)
- Linux设备驱动程序学习之设备模型二
- 嵌入式学习-驱动开发-lesson3-混杂设备驱动模型与linux中断处理流程
- linux设备驱动学习(11) linux设备模型1
- Linux设备驱动模型学习之基础篇--Kobject.txt翻译
- Linux设备驱动程序学习(13)-Linux设备模型(总线、设备、驱动程序和类)
- Linux设备驱动程序学习(13) -Linux设备模型(总线、设备、驱动程序和类)
- Linux驱动模型学习(二)---字符设备驱动模型之二---初窥字符设备驱动