内核驱动模型

内核驱动模型的初衷

2.6 内核增加了一个引人注目的新特性——统一设备模型 (device model) 。设备模型提供了一个独立的机制专门来表示设备,并描述其在系统中的拓扑结构,从而使得系统具有以下优点:
l         
代码重复最小化。
l        
 提供诸如引用计数这样的统一机制。
l        
 可以列举系统中所有的设备,观察它们的状态,并且查看它们连接的总线。
l        
 可以将系统中的全部设备结构以树的形式完整、有效的展  现出来——包括所有的总线和内部连接。
l        
 可以将设备和其对应的驱动联系起来,反之亦然。
l        
 可以将设备按照类型加以归类,比如分类为输入设备,而无需理解物理设备的拓扑结构。
l        
 可以沿设备树的叶子向其根的方向依次遍历,以保证能以正确顺序关闭各设备的电源。
   最后一点是实现设备模型的最初动机。若想在内核中实现智能的电源管理,就需要来建立表示系统中设备拓扑关系的树结构。当在树上端的设备关闭电源时,内核必须首先关闭该设备节点以下的(处于叶子上的)设备电源。比如内核需要先关闭一个 USB 鼠标,然后才可关闭 USB 控制器;同样内核也必须在关闭 PCI 总线前先关闭 USB 控制器。简而言之,若要准确而又高效的完成上述电源管理目标,内核无疑需要一颗设备树。

  ( 注:设备模型与电源管理相关联,貌似匪夷所思,可实际上,一个新观点或模型出现,在其背后都是需求的刺激。最近以来,节电一直是 Intel 、 IBM 等大公司孜孜追求的目标,虚拟化技术的本质其实就是节能。或者说,在能源日趋紧张的今日,节能是人类共同追求的目标 ) 。

为什么驱动模型有助于电源管理,再说两句: 
   
    
系统中所有硬件设备由内核全权负责电源管理。例如,在以电池供电的计算机进入“待机”状态时,内核应立刻强制每个硬件设备(硬盘,显卡,声卡,网卡,总 线控制器等等)处于低功率状态。因此,每个能够响应“待机”状态的设备驱动程序必须包含一个回调函数,它能够使得硬件设备处于低功率状态。而且,硬件设备 必须按准确的顺序进入“待机”状态,否则一些设备可能会处于错误的电源状态。例如,内核必须首先将硬盘置于“待机”状态,然后才是它们的磁盘控制器,因为 若按照相反的顺序执行,磁盘控制器就不能向硬盘发送命令。

追根溯源:

 

虽然设备模型的初衷是为了方便电源管理而提供出的一种设备拓扑结构,但是 , 为了方便调试,设备模型的开发者决定将设备结构树导出为一个文件系统,这就是sysfs 文件系统,它可以帮助用户能以一个简单文件系统的方式来观察系统中各种设备的拓扑结构。这个举措很快被证明是非常明智的,首先 sysfs 代替了先前处于 /proc 下的设备相关文件;另外它为系统对象提供了一个很有效的视图。实际上, sysfs 起初被称为 driverfs 。最终 sysfs 使得我们认识到一个全新的对象模型非常有利于系统。今天所有 2.6 内核的系统都拥有sysfs 文件系统,而且几乎都毫无例外的将其挂载。
              图一是挂载于 /sys 目录下的 sysfs 文件系统的局部视图。
   
  /sys 
     | —block/
     |   |
 –fd0
     |   |
 –hda
     |   |
 –dev
     |   |
 –device->../../devices/pci0000:00/0000:00:1f.1/ide0/0.0
     |    

     |
 –bus/
     |
 –class/
     |
 –devices/
     |
 –firmware/
     |
 –power/
     |
 –module/
   

Sysfs 的根目录下包含了七个目录: block  bus  class , devices  firmware , , module 和power 。 block 目录下的每个子目录都对应着系统中的一个块设备。反过来,每个目录下又都包含了该块设备的所有分区。bus 目录提供了一个系统总线视图。 class 目录包含了以高层功能逻辑组织起来的系统设备视图。devices 目录是系统中设备拓扑结构视图,它直接映射出了内核中设备结构体的组织层次。 firmware 目录包含了一些诸如ACPI,EDD,EFI 等低层子系统的特殊树。power 目录包含了系统范围的电源管理数据。
其中最重要的目录是devices ,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。其它目录中的很多数据都是将devices 目录下的数据加以转换加工而得。比如,/sys/class/net/ 目录是以注册网络接口这一高层概念来组织设备关系的,在这个目中可能会有目录eth0 ,它里面包含的devices 文件其实就是一个指回到devices 下实际设备目录的符号连接。
   
 随便看看任何你可访问到的Linux 系统,这种系统设备视图相当准确和漂亮,而且可以看到class 中的高层概念与devices 中的低层物理设备,以及bus 中的实际驱动程序之间互相联络是非常广泛的。

软件设计的根本是把现实世界的事物用计算机世界的模型表示出来,Linux 设备模型的设计采用了面向对象的思想。

在前一讲中,提到sysfs 文件系统,Sysfs 文件系统的目标就是要展现设备驱动模型组件之间的层次关系。在Linux 中,sysfs 文件系统被安装于/sys 目录下:

mount -t sysfs sysfs /sys
/sys
     |
 —block/
     |   |
 –fd0
     |   |
 –hda
     |   |
 –dev
     |   |
 –device->../../devices/pci0000:00/0000:00:1f.1/ide0/0.0
     |    

     |
 –bus/
     |
 –class/
     |
 –devices/
     |
 –firmware/ 
     | –power/
     |
 –module/


    那么,在这样的目录树中,哪些目录是驱动模型要关注的对象? 

bus - 系统中用于连接设备的总线,在内核中对应的结构体为  struct bus_type {   }; 
device - 内核所识别的所有设备,依照连接它们的总线对其进行组织对应的结构体为 struct device {   }; 
class - 系统中设备的类型(声卡,网卡,显卡,输入设备等),同一类中包含的设备可能连接到不同的总线对应的结构体为  struct class {   }; 
为什么不对Power 进行单独描述?实际上,Power 与device 有关,它只是device 中的一个字段。
除此之外,立马闪现在我们脑子里的对象还有:
driver - 在内核中注册的设备驱动程序对应的结构体为
struct device_driver{   }; 

以上bus,device,class,driver ,是可以感受到的对象,在内核中都用相应的结构体来描述。而实际上,按照面向对象的思想,我们需要抽象出一个最基本的对象,这就是设备模型的核心对象kobject.
   Kobject 
 是Linux 2.6 引入的新的设备管理机制,在内核中就是一个struct kobject 结构体。有了这个数据结构,内核中所有设备在底层都具有统一的接口,kobject 提供基本的对象管理,是构成Linux2.6 设备模型的核心结构,它与sysfs 文件系统紧密关联,每个在内核中注册的kobject 对象都对应于sysfs 文件系统中的一个目录。Kobject 是组成设备模型的基本结构。类似于C++ 中的基类,它嵌入于更大的对象中,即所谓的容器,如上面提到的 bus,class ,devices, drivers   都是典型的容器,它们是描述设备模型的组件。
  
下一讲,将围绕 kobject 展开。

话说 kboject 是驱动模型的核心对象,但在 sysfs 文件系统中似乎并没有对应的项,而这种看似“无”,实际上蕴藏着“有”。 
这“有”从何说起。回想文件系统中的核心对象“索引节点( indoe )”和目录项“ dentry  : 
Inode  与文件系统中的一个文件相对应(而实际上,只有文件被访问时,才在内存创建索引节点)。 
Dentry - 每个路径中的一个分量,例如路径 /bin/ls ,其中 / 、  bin  和 ls 三个都是目录项,只是前两个是目录,而最后一个是普通文件。也就是说,目录项目录项或者是一子目录,或者是一个文件。
从上面的定义可以看出, indoe 和 dentry 谁的包容性更大?当然是 dentry !
那么, kobject 与 dentry 有何关系?由此我们可以推想,把 dentry 作为 kobject 中的一个字段,恍然间, kobject 变得强大起来了。何谓“强大“,因为这样以来,就可以方便地将 kobject 映射到一个 dentry 上,也就是说, kobject 与 /sys 下的任何一个目录或文件相对应了,进一步说,把 kobject 导出形成文件系统就变得如同在内存中构建目录项一样简单。由此可知, kobject 其实已经形成一棵树了。这就是以隐藏在背后的对象模型为桥梁,将驱动模型和 sysfs 文件系统全然联系起来。由于 kobjec t 被映射到目录项,同时对象模型层次结构也已经在内存形成一个树,因此 sysfs 的形成就水到渠成了。 
既然 , kobject 要形成一颗树,那么其中的字段就要有 parent ,以表示树的层次关系;另外, kobject 不能是无名氏,得有 name 字段,按说,目录或文件名并不会很长,但是, sysfs 文件系统为了表示对象之间复杂的关系,需要通过软链接达到,而软链接常常有较长的名字,通过以上的分析,目前可以得知 kobject 对象包含的字段有: 

struct kobject {      char       *k_name;  /* 长名字*/   
                    char        name[kOBJ_NAME _LEN]; /* 短名字*/                         struct kobject   *parent; /* 表示对象的层次关系*/                 struct dentry     *dentry; /* 表示sysfs 中的一个目录项 */}; 

分析到这里,似乎已经知道 kobject 说包含的字段了,但且慢,查看 kobject.h 头文件,看到它还包含以下字段: 

struct kobject {        struct kref             kref;
                      struct list_head        entry;
                      struct kset             *kset;
                      struct kobj_type        *ktype;};
  
 这四个字段,每一个都是结构体,其中struct list_head 是内核中形成双向链表的基本结构,而其他三个结构体存在的理由是什么?

1.     引用计数kref    
kobject 的主要功能之一就是为我们提供了一个统一的引用计数系统,为什么说它具有“统一”的能力?那是因为 kobject 是“基”对象,就像大厦的基地,其他对象(如 devic,bus,class,device_driver 等容器)都将其包含,以后,其他对象的引用技术继承或封装 kobject 的引用技术就可以了。 
初始化时, kobject 的引用计数设置为 1 。只要引用计数不为零,那么该对象就会继续保留在内存中,也可以说是被“钉住”了。任何包含对象引用的代码首先要增加该对象的引用计数,当代码结束后则减少它的引用计数。增加引用计数称为获得( getting )对象的引用,减少引用计数称为释放 (putting) 对象的引用。当引用计数跌到零时,对象便可以被销毁,同时相关内存也都被释放。 
增加一个引用计数可通过 koject_get ()函数完成: 
struct kobject * kobject_get(struct kobject *kobj);
 该函数正常情况下将返回一个指向 kobject 的指针,如果失败则返回 NULL 指针; 
减少引用计数通过 kobject_put() 完成: 
void kobject_put(struct kobject *kobj);
 如果对应的kobject 的引用计数减少到零,则与该kobject 关联的ktype 中的析构函数将被调用。  我们深入到引用计数系统的内部去看,会发现 kobject 的引用计数是通过 kref 结构体实现的,该结构体定义在头文件<linux/kref.h > 中: 
struct kref {        atomic_t refcount;};
 其中唯一的字段是用来存放引用计数的原子变量。那为什么采用结构体?这是为了便于进行类型检测。在使用 kref 前,必须先通过kref_init() 函数来初始化它: 
void kref_init(struct kref *kref){        atomic_set(&kref->refcount, 1);}
    
 正如你所看到的,这个函数简单的将原子变量置 1 ,所以 kref 一但被初始化,它表示的引用计数便固定为 1 。 
     开发者现在不必在内核代码中利用atmoic_t 类型来实现其自己的引用计数。对开发者而言,在内核代码中最好的方法是利用kref 类型和它相应的辅助函数,为自己提供一个通用的、正确的引用计数机制。 
上述的所有函数定义与声明分别在在文件lib/kref.c 和文件<linux/kref.h> 中。 
2.    
 共同特性的ktype

如上所述,kobject 是一个抽象而基本的对象。对于一族具有共同特性的 kobject ,就是用ktype 来描述:


struct kobj_type {       
         void (*release)(struct kobject *); 
        struct sysfs_ops        *sysfs_ops; 
        struct attribute        **default_attrs;}; 
   
 定义于头文件<linux/kobject.h> 中。
   
 release 指针指向在 kobject 引用计数减至零时要被调用的析构函数。该函数负责释放所有 kobject 使用的内存和其它相关清理工作。 
        sysfs_ops 变量指向sysfs_ops 结构体,其中包含两个函数,也就是对属性进行操作的读写函数show() 和store ()。 
    
最后,default_attrs 指向一个attribute 结构体数组。这些结构体定义了kobject 相关的默认属性 。属性描述了给定对象的特征,其实,属性就是对应/sys 树形结构中的叶子结点,也就是文件。

3.     对象集合体kset   
   Kset ,顾名思义就是 kobject 对象的集合体,可以把它看成是一个容器,可将所有相关的 kobject 对象聚集起来,比如“全部的块设备”就是一个 kset 。听起来 kset 与 ktypes 非常类似,好像没有多少实质内容。那么“为什么会需要这两个类似的东西呢”。 ksets 可把 kobject 集中到一个集合中,而 ktype 描述相关类型 kobject 所共有的特性,它们之间的重要区别在于:具有相同 ktype 的 kobject 可以被分组到不同的 ksets 。 
       kobject 的 kset 指针指向相应的 kset 集合。 kset 集合由 kset 结构体表示,定义于头文件<linux/kobject.h> 中: 

struct kset {        struct kobj_type         *ktype; 
               struct list_head         list; 
             struct kobject           kobj; 
          struct  kset_uevent_ops   * uevent_ops ;};   
       其中 ktype 指针指向集合( kset )中 kobject 对象的类型( ktype ), list 连接该集合( kset )中所有的 kobject 对象。 kobj 指向的 koject 对象代表了该集合的基类,uevent_ops 指向一个用于处理集合中 kobject 对象的热插拔操作的结构体。 

总结:    kobject 通常是嵌入到其它结构中的,其单独意义其实并不大。相反,那些更为重要的结构体,比如在struct cdev 中才真正需要用到kobject 结构。 
/* cdev structure 
– 该对象代表一个字符设备 */
struct cdev {        struct kobject          kobj;  
                    struct module           *owner;     
                    struct file_operations  *ops;  
                    struct list_head        list;   
                    dev_t                   dev;        
                   unsigned int            count;}; 
     
 当kobject 被嵌入到其它结构中时,该结构便拥有了kobject 提供的标准功能。更重要的一点是,嵌入kobject 的结构体可以成为对象层 次架构中的一部分。比如cdev 结构体就可通过其父指针cdev->kobj->parent 和链表 cdev->kobj->entry 来插入到对象层次结构中。   
参考  LDK , ULK 以及 lxr.linux.n

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值