Chinaunix首页 | 论坛 | 博客
  • 博客访问: 330750
  • 博文数量: 85
  • 博客积分: 3433
  • 博客等级: 中校
  • 技术积分: 844
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-29 01:11
文章分类

全部博文(85)

文章存档

2013年(1)

2012年(12)

2011年(13)

2010年(59)

我的朋友

分类: LINUX

2010-10-31 16:43:33

三、集成: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 
 

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; 
 int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id); 
 void (*remove) (struct pci_dev *dev); 
 int  (*suspend) (struct pci_dev *dev, pm_message_t state); 
 int  (*resume) (struct pci_dev *dev);                
 int  (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);  
 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); 

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);
 
 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本身具有的所有特性。"

阅读(1132) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-10-31 18:58:16

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com