Linux设备模型 学习总结

来自:湛蓝思微 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 结构提供了所有模型需要的最基本的功能:
引用计数   用于内核维护其存在与消亡
2 sysfs
表示   每个sys/ 下的对象对应着一个kobject
热拔插事件处理。 处理设备的热拔插事件。

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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值