`
bigfirebird
  • 浏览: 128797 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

http://blog.mcuol.com/User/lvembededsys/Article/6820_1.htm
分享到:
评论

相关推荐

    Linux设备驱动程序学习(14)-Linux设备模型(各环节的整合) - Linux设备驱动程序

    总结来说,Linux设备模型是一个全面的框架,用于组织、管理和操作系统的硬件资源。通过理解设备模型的各个组成部分,开发者可以更好地编写和调试设备驱动程序,优化系统性能,提高系统的稳定性和可靠性。在实际的...

    Linux设备驱动模型详解

    本文将深入探讨Linux设备模型的核心概念、关键数据结构以及相关的API,帮助读者更好地理解和掌握Linux设备驱动开发。 #### 二、Linux设备模型概述 Linux设备模型主要由以下几部分组成: 1. **设备注册与注销**:...

    Linux设备驱动程序学习(13)-Linux设备模型(总线、设备、驱动程序和类) - Linux设备驱动程序

    总结来说,Linux设备模型为设备驱动程序提供了一个结构化的框架,使得设备管理更加有序和高效。理解这个模型对于编写和维护设备驱动程序至关重要,同时也对系统管理员进行设备故障排查和性能优化有重要指导意义。...

    linux设备模型简单分析

    总结来说,Linux设备模型是Linux内核中对硬件设备的一种抽象和管理机制,它提供了一套统一的框架,使得驱动程序可以高效、灵活地操作各种设备。通过对设备模型的理解和掌握,开发者可以更好地编写和维护驱动程序,...

    Linux设备驱动程序学习总结

    Linux设备驱动程序学习总结: *字符设备驱动程序 *调试技术 *并发和竞态 *Linux中的循环缓冲区 *内核的数据类型 *分配内存 *与硬件通信 *时间、延迟及延缓操作 *中断处理 *Linux设备模型

    linux设备模型深探

    Linux设备模型深探 在Linux操作系统中,设备模型是一个至关重要的组成部分,它为管理系统中的硬件设备提供了统一的抽象层。本文将深入探讨Linux设备模型的底层实现,包括kobject、kset和ktype等核心概念。 一、...

    Linux设备模型及Platform驱动

    ### Linux设备模型及Platform驱动知识点概述 #### 一、Linux设备模型 **1.1 devfs(Linux 2.4的设备文件系统)** - **介绍**: `devfs`是Linux 2.4内核引入的一种设备文件系统,用于简化设备文件的管理和创建。 - *...

    linux设备模型_____相关函数

    总结,Linux设备模型通过sysfs提供了一种透明的接口,使得用户可以直接操作硬件设备。理解设备模型的相关函数及其在sysfs中的体现,有助于我们更好地管理硬件资源,编写高效的驱动程序,以及解决与设备相关的各种...

    LINUX设备驱动程序

    这本书可能涵盖了如何编写和管理Linux设备驱动程序的基础知识,包括驱动模型、I/O操作、中断处理、DMA(直接内存访问)、设备树等概念。通过学习,开发者可以掌握如何为不同的硬件组件如GPIO、SPI、I2C、UART等编写...

    Linux驱动学习总结

    ### Linux驱动学习总结 #### 一、内核的相关基础概念 **1.1 Linux设备驱动的作用** Linux设备驱动是连接操作系统内核与物理硬件设备之间的桥梁。它负责控制硬件资源,提供应用程序对这些硬件资源的访问接口。一个...

    Linux设备驱动程序学习(1)-字符设备驱动程序 - Linux设备驱动程序

    要编写一个字符设备驱动程序,首先需要理解Linux设备模型。Linux将所有设备分为字符设备和块设备,每个设备都有一个唯一的设备号,由主设备号和次设备号组成,它们在/dev目录下的设备文件中体现。在注册字符设备时,...

    Linux驱动学习资料总结

    2. **设备模型**:Linux设备模型包括总线、设备、驱动等概念,通过sysfs和kobject等机制将硬件设备组织成层次结构,简化了驱动程序的开发。 3. **中断处理**:中断是硬件设备向CPU发送的信号,用于通知事件发生。...

    inux设备驱动开发详解:基于最新的Linux4.0内核配套光盘

    Linux设备模型提供了统一的接口来管理各种类型的设备。它包括设备、驱动和总线三个概念。通过使用设备模型,可以简化驱动程序的编写工作。 #### 5.2 动态内存管理 在设备驱动中,动态内存管理是非常重要的技术之一...

    Linux设备模型之uart驱动架构分析

    总结来说,Linux设备模型中的UART驱动架构是一个复杂的系统,涉及了多个层次的封装和多个数据结构的关联。通过合理的设计和实现这些数据结构,可以有效地管理串口设备的读写操作,为上层应用提供了一个简洁的API接口...

    Linux设备驱动模型(ahb-apb-pci)

    ### Linux设备驱动模型详解 #### 一、嵌入式设备基本概念及Linux设备驱动模型概述 在嵌入式系统开发中,理解设备驱动模型是非常重要的一步。本文将围绕AHB/APB/PCI总线以及相关的Linux设备驱动模型展开讨论。 **...

    linux设备驱动的理解

    ### Linux设备驱动模型的核心概念与实现 #### 一、引言 Linux设备驱动模型是Linux内核中的一个重要组成部分,它为设备驱动程序提供了一套统一的接口和框架,使得驱动程序能够更加高效地管理和控制硬件资源。对于...

    linux设备驱动详解

    总结来说,Linux设备驱动是连接硬件和操作系统的核心组件,理解其工作原理和开发方法对于Linux系统的管理和优化至关重要。从设备分类、驱动模型到驱动开发,每一个环节都涉及到具体的技术细节,需要开发者具备扎实的...

    linux 实训 总结

    以上总结了Linux实训中的关键知识点,包括Linux操作系统架构、文件系统结构、基本概念以及常用的Linux命令,这些知识对于嵌入式培训尤其是Linux驱动开发领域尤为重要,能够帮助学习者深入了解Linux系统的工作机制,...

    Linux设备驱动程序学习(9)-与硬件通信 - Linux设备驱动程序

    总结来说,Linux设备驱动程序与硬件通信涉及到硬件初始化、数据读写、中断处理、同步/异步通信模式、总线驱动框架以及调试等多个方面。掌握这些知识对于深入理解和开发Linux设备驱动至关重要。通过不断的实践和学习...

Global site tag (gtag.js) - Google Analytics