Linux设备模型linux内核编程( 2009-05-15 09:55:08 阅读235 评论0 字号:大中小 订阅 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字符设备以至于更加复杂的驱动,越看越是觉得对linux驱动的结构不清楚,越看越是迷糊。于是就停下脚步搜索一下资料理一下头绪:
以下四个方面来总结一些内容: 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结构提供了所有模型需要的最基本的功能: Kobjects 在内核中对应有一套申请,初始化,添加,注册,计数操作,释放等函数 Kset 和kobj_type Kset 在概念上是一个集合或者叫容器。实现了对象的层次。所有属于一个ksets的对象(kobject)的parent都指向该ksets的kobj.同时这个对象都连接到kset 的list表上。同时位于ksets层次之上的是subsys,在最新的内核中已经取消subsys,因为它本质上也就是一个ksets。Kset有一套类似kobject的操作,实现上只是进一步调用其自身kobj的相应操作,毕竟ksets本质上也是一个kobject。 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 { 如此 ,kobjects与ksets实现层次树的底层骨架。 二、linux设备模型层次关系:bus_type,device,device_driver 基本关系简要的概括如下: 下面看看三者数据结构的定义。 首先是总线,bus_type. struct subsystem subsys;//代表自身 struct bus_attribute * bus_attrs;//总线属性 int (*match)(struct device * dev, struct device_driver * drv);//设备驱动匹配函数 struct device { struct device * parent; //父设备,一般一个bus也对应一个设备。 void *driver_data; /* data private to the driver 指向驱动 */ ///更多字段忽略了 }; 下面是设备驱动定义: struct device_driver { struct completion unloaded; struct module * owner; int (*probe) (struct device * dev); OK。基本的东西弄明白了。通过PCI驱动中设备模型的实例来看看细节。 三、集成:PCI设备驱动模型实例及设备,设备驱动注册源码的简单分析. 先看pci总线类型定义: 然后是pci设备和驱动。pci设备和pci驱动没有直接使用device和device_driver,而是将二者封装起来,加上pci特定信息构成pci_dev和pci_driver。当然,意义是一样的。 struct pci_dev { struct pci_bus *bus; //所属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_error_handlers *err_handler; 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_register-->device_initialize(dev);//初始化设备各个字段 void device_initialize(struct device *dev) device_register-->device_add(dev); int device_add(struct device *dev) //主要流程 //设置uevent_attr: dev->uevent_attr.attr.name = "uevent"; if (dev->driver) //建立显示设备号的sysfs入口,即当前设备入口下的"dev"文件显示设备主从设备号。 if (MAJOR(dev->devt)) { //建立类的sysfs符号连接 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) driver_register(drv);-->bus_add_driver(drv); int bus_add_driver(struct device_driver * drv) driver_attach(drv);//添加驱动到总线 driver_add_attrs(bus, drv); driver_register(drv);-->bus_add_driver(drv);-->driver_attach(drv); void driver_attach(struct device_driver * drv) 对总线上的每个设备dev,调用__driver_attach(dev,drv);最终调用 driver_register(drv);-->bus_add_driver(drv);-->driver_attach(drv); int driver_probe_device(struct device_driver * drv, struct device * dev) Done: 乱七八糟的。主线还是模型的层次关系。对kobject,kset细节中关于属性,热拔插,sys入口的部分没有深入。或许,理解总体和设计思想是更重要的。人的精力真的有限。 四、面向对象的思想在linux设备模型中的应用分析. 通
过设备模型,看到了面向对象编程思想用C语言的实现。内核中常见到封装了数据和方法的结构体,这是面向对象封装特性的实现。而这里展现的更多的是继承方面
的实现。比如说pci_driver,它的父类是device_driver,而更上一层是一个kobject。在C++中,继承一个父类则子类中相应的
包含父类的一个实例。内核中也是通过包含一个父类的实体来实现这种派生关系。因此,一个pci_driver内部必然包含一个
device_driver,同样,device_driver内部必然包含一个kobject。 设
备模型源码中还能找到多态(虚函数)的思想。看到pci_driver和device_driver中提供了差不多同名的方法不觉得奇怪吗??它们不同的
地方在于参数。pci_driver中方法的参数是pci_device * dev ,而device_driver方法的参数则是 device
* dev 。这么安排是有意的! 这里设置了platform_driver包含的device_driver的函数指针。看看这些函数中的platform_drv_probe。 return drv->probe(dev); 这里出现了两个指针类型转换(通过container_of()宏实现的),然后调用platform_driver提供的probe函数。 资源链接:http://blog./User/lvembededsys/Article/6820_1.htm |
|