Linux设备模型学习参考方法

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


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值