`
bigfirebird
  • 浏览: 125066 次
  • 性别: 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设备驱动程序学习总结

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

    学习嵌入式Linux开发——韦东山升级版全系列嵌入式视频之总线设备驱动模型代码分析

    文章目录概述函数关系图模型分析资源层->设备层设备层->驱动层总结 概述 今天看了《韦东山升级版全系列嵌入式视频之总线设备驱动模型》这一节的视频,看完之后感觉有一种似懂非懂的感觉,因此我对改节视频对应源码...

    Linux那些事儿之我是USB(第2版).pdf

    对于Linux驱动开发者,可以通过本书对设备模型有形象深刻的理解;对于USB开发者,可以通过本书全面的理解USB在一个操作系统中的实现;对于Linux内核开发者,也可以通过本书学习到很多Linux高手开发维护一个完整子...

    史上最强的嵌入式底层驱动开发课程 Linux系统开发+Linux高级程序+主板开发+ARM等

    │ ├13 - ARM体系结构-学习方法.mp4 │ ├14 - ARM体系结构-处理器和名词1.mp4 │ ├15 - ARM体系结构-处理器和名词2.avi │ ├16 - ARM体系结构-工具和交叉工具链.mp4 │ ├17 - 处理器模式和片内寄存器1.avi │ ...

    Linux程序设计 第4版.haozip01

    13.7.6 对cd数据库应用程序的总结 487 13.8 小结 487 第14章 信号量、共享内存和消息队列 488 14.1 信号量 488 14.1.1 信号量的定义 489 14.1.2 一个理论性的例子 489 14.1.3 linux的信号量机制 490 14.1.4 ...

    Linux程序设计 第4版.haozip02

    13.7.6 对cd数据库应用程序的总结 487 13.8 小结 487 第14章 信号量、共享内存和消息队列 488 14.1 信号量 488 14.1.1 信号量的定义 489 14.1.2 一个理论性的例子 489 14.1.3 linux的信号量机制 490 14.1.4 ...

    qt必备学习手册初级

    Linux Host 5 OS X Host 5 Windows Host 5 1.6 Qt的优点 5 2 创建Qt项目 6 2.1 使用向导创建 6 2.2 手动创建 9 2.3 .pro文件 10 2.4 一个最简单的Qt应用程序 12 3 信号和槽机制 13 3.1 信号和槽 13 3.2 自定义信号槽...

    嵌入式实时Hypervisor:XtratuM

    本论文是作者对XtratuM Hypervisor进行学习和研究的工作总结,涵盖了作者为XtratuM Hypervisor所做的研究内容:中断管理、Hypercall、任务管理、虚拟内存管理等模块向不同Linux内核上的移植,域间通信工具、设备驱动...

    网络安全思维导图

    Windows_Hacker学习路线图.jpg cheat sheet reverse v5.png 计算机病毒.png 安全人员技术要求.jpg 木马攻击与防御技术.png 欺骗攻击与防御技术.png 入门二进制漏洞分析脑图.png 缓冲区溢出攻击与防御技术....

    通信专业人才需求调研报告.doc

    根据生产企业对毕业生适宜职业岗位的要求,总结出对通信技术专业的毕业生的知识 结构和能力要求,要求学生具备以下及方面的条件: 1、具有良好的职业道德修养,掌握分析问题、解决问题的立场、观点和方法;...

    操作系统实验报告

    进程的数目、进程的状态模型(三状态、五状态、七状态或其它)以及PCB的组织形式可自行选择。 3)合理设计与进程PCB相对应的数据结构。PCB的内容要涵盖进程的基本信息、控制信息、资源需求及现场信息。 4)设计出可视...

    软件测试经典面试题 (超实用)

    10、在即将完成这次笔试前,您是否愿意谈一些自己在以往的学习和工作中获得的工作经验和心得体会?(可以包括软件测试、过程改进、软件开发或者与此无关的其他方面) 35 11、为什么选择测试这行? 35 12、你的工作...

    电子商务调研报告.docx

    市场推广助理/主任 (1) 大专或以上程度,懂英语及能操粤语 (2) 拥有2-3年市场推广工作经验,做过大型市场推广及论坛举办优先 (3) 对市场有敏锐的观察及总结能力,并有较强的沟通能力 (4) 精通Photoshop或CorelDRAW...

    vc++ 应用源码包_1

    系统硬件信息、存储设备管理、鼠标及键盘、声音和视频、图形和图像、网络、数据库) 《远程控制编程技术》源代码 内含(重启、图片操作、ip操作、键盘与鼠标、客户端以及服务端、文件传输等实例源码) 多个VC++...

    vc++ 应用源码包_2

    系统硬件信息、存储设备管理、鼠标及键盘、声音和视频、图形和图像、网络、数据库) 《远程控制编程技术》源代码 内含(重启、图片操作、ip操作、键盘与鼠标、客户端以及服务端、文件传输等实例源码) 多个VC++...

    vc++ 应用源码包_6

    系统硬件信息、存储设备管理、鼠标及键盘、声音和视频、图形和图像、网络、数据库) IOCP 完成端口编程 《远程控制编程技术》源代码 内含(重启、图片操作、ip操作、键盘与鼠标、客户端以及服务端、文件传输等实例...

    vc++ 应用源码包_5

    系统硬件信息、存储设备管理、鼠标及键盘、声音和视频、图形和图像、网络、数据库) IOCP 完成端口编程 《远程控制编程技术》源代码 内含(重启、图片操作、ip操作、键盘与鼠标、客户端以及服务端、文件传输等实例...

    vc++ 应用源码包_3

    系统硬件信息、存储设备管理、鼠标及键盘、声音和视频、图形和图像、网络、数据库) IOCP 完成端口编程技术 《远程控制编程技术》源代码 内含(重启、图片操作、ip操作、键盘与鼠标、客户端以及服务端、文件传输等...

    vc++ 开发实例源码包

    系统硬件信息、存储设备管理、鼠标及键盘、声音和视频、图形和图像、网络、数据库) 《远程控制编程技术》源代码 内含(重启、图片操作、ip操作、键盘与鼠标、客户端以及服务端、文件传输等实例源码) 多个VC++...

Global site tag (gtag.js) - Google Analytics