linux 统一设备模型,Linux——统一设备模型

内容提要:

1、核心概念

将重复的部分抽象出来构成固定的框架、将变化的部分隔离出来并将其看成是传给固定框架的参数!

2、对核心概念的阐述

抽象重复、构建层次结构

将驱动的编写由问答题变为填空题

把Kernel看成一个大的黑盒函数,DTB作为该函数的参数,以适应外部设备的千变万化

3、核心细节说明

kobject、ktype、kset的关系

device、bus、class对kobject、ktype、kset的使用

4、更进一步

RTFSC

本文的核心思想是:

Linux的统一设备模型,从某个角度来看,其实就是在设备管理功能中,将重复的、具有共性的部分抽象出来构成固定的框架,同时将变化的、具有具体设备特性的部分分离出来,进而将后者看做是参数传递给先前抽象出来的固定框架,最终完成整体的设备管理功能。

接下来,我将分三个角度来阐述上述观点:

1、借鉴面向对象思想,站在某个抽象层次将重复的概念抽象出来,接着提高抽象层次,同时基于上一层的抽象添加某一类变化构成上一层,依次类推就能构建出面向对象中的继承关系,大大减少重复的逻辑和代码,而减少重复可以说是软件设计中的第一原则。进一步,既然不同抽象层次的共性概念已经被抽象出来了,那么要完全描述该抽象层次的某个事物,只需要填充好那个具体事物的特性就好了,而这些特性就可以看作是抽象出来的共性概念的参数。

何出此言?

我们还是从kobject谈起。

kobject可以看成是所有设备的基类,每个设备都含有一个kobject。其实除了device,设备管理模型中的其他概念,如driver、bus、class等等,都与kobject关系密切。那么kobject到底抽象出来一些什么共性呢?

就我的理解来看,主要包括以下三个方面:

引入引用计数,以便于kobject在引用计数为0时能够动态的释放内存资源,那么有了这个功能后,包含kobject的上层概念如device也应该能够动态的释放内存资源;

引入了parent指针,利用该指针就能够将所有包含kobject的东东构造成一棵大树,利用该树形结构就可以方便的遍历系统中所有这些东东,以便对其进行统一管理,如电源管理等;

kobject跟sysfs相结合,给应用空间提供一个基于文件的用户接口,方便应用层跟驱动的沟通与交流,尤其是方便对驱动程序的调试工作;

谈完kobject,我们来谈谈device。device是比kobject更高一层的概念,除了包含kobject所包含的共性,必然还包含一些其他的共性,那么这些其他的共性又是什么呢?device的核心概念就是对应的各种各样的设备,设备千变万化,但必然也存在一些共性,否则就不能都叫做设备了。譬如:设备大都需要有个初始化的操作(如初始化pin脚,申请各种资源等),大都需要一个驱动程序,大都涉及到电源管理功能,大都基于某一种特定的bus等等。

更进一步,我们基于device来谈谈platform_device。

platform_device是在device基础上,又将一类有共性的设备抽象出来,以方便对这一类设备驱动的开发和使用。那么,这一类设备又有什么共性呢?这个共性就是:可以由CPU

bus直接寻址此类设备,这类设备一般都是各个Soc厂商集成在Soc平台上的各种控制器,作为Linux驱动工程师,平常的大部分工作都是驱动platform设备。

以上,从object到device再到platform_device,就是从最底层一层层提高抽象层次构建出来的,每一层的概念都是抽象出一部分共性,只不过下一层的共性总是上一层共性的子集。下一层的共性加上不同的上一层共性,就抽象出不同的上层概念,如:kobject+设备共性就抽象出device,而如果kobjcet+驱动程序的共性就抽象出device_driver;同理,device+platform设备的共性就抽象出platform_device,而如果device+spi设备的共性就抽象出spi_device。

进一步,既然抽象出了device的共性概念,那么要实现某个具体device的驱动,就只需要将该特定设备的特性填充好就可以了,如对device结构中某些回调函数的实现,在此处该回调函数的实现就可以看作是传给device共性概念的一个参数,每个特定设备的该回调函数的实现都不一样,但是该回调函数代表的共性概念是一样的,共性和特性加在一起,就完成了整个逻辑,从这个方面来说,特定设备的特性就可以看作是传个device共性概念的参数,两者加在一起就完整描述了该特定设备。

2、设备的驱动细节千变万化,但其中的工作逻辑又存在很多的共性,有了Linux的设备管理模型,驱动的编写工作就由先前的问答题变为了简单易行的填空题,何乐而不为呢?

bus、device和device_driver的统一设备模型抽象出来的三组核心概念,构成核心的铁三角关系,目的是描述清楚设备模型的核心逻辑,这其中围绕着一个核心的概念模型,即:

在Linux的系统概念中,每个设备都要挂到一个Bus上才能工作,这个Bus可以是真实存在的Bus如i2c、usb等,也可以是Linux为了方便管理而假象出来的虚拟Bus如platform_bus。Bus一肩挑两端,一端是所有的device列表,一端是所有的device_driver列表,每当有新的device新添加到Bus上时,则触发新device和所有已存在device_driver的match动作;同理,若有新的device_driver添加到Bus上时,也会触发新device_driver和所有已存的device的match动作。match的逻辑也很简单,如果device和device_driver同名,则认为match成功,将会触发device_driver的probe函数进行device的初始化。

怎么样,是不是很简单,其实Linux整个驱动模型就是围绕着这个简单的核心概念逻辑来运转的,不同的特定device只需要给定不同的名称和不同的device_driver的probe函数,就能完成该特定device的初始化工作,在这个过程中,其实就是填空——针对该特定device填写正确的名称和能正常初始化该device的probe函数即可。其他功能如释放设备,也一样由设备模型搭好了整个框架,针对特定device要做的只是填写好其中留下来的空,所以说,有了Linux的设备驱动模型,Linux系统下驱动的编写工作就由问答题变为了简单易行的填空题,大大减少了工程师的工作量和难度,稳定性上也有了进一步的保证。

从这个角度来看,也可以说,这些留下来的该填的空正是传个整个驱动模型框架的参数,不是吗?

3、设备树其实可以看成是传给Kernel的参数,以适应外部成千上万的设备,以不变应万变的。

新版本的Linux版本中,引入了DeviceTree的概念,目的是让各个厂商针对各自的Soc能够写出更优雅的代码,减少各个Soc间的重复部分。可以将Device

Tree看成是各个具体硬件平台整体硬件的一个描述表,如该平台有几颗cpu,有多少内存,有多少usb控制器,mmc控制器,各个mmc控制器用了哪些中断资源,哪些寻址空间等等。其实各个平台涉及到的硬件资源都是固定的,无非都是些cpu,memory,中断,DMA等等,不同的是这些资源的细节实现如cpu的个数,memory的空间大小。如果把这些细节以代码的形式写入到Kernel,那么每个细小的改动都需要修改kernel的源码并重新编译出新的镜像,费时费力不说,而且随着硬件平台的增加,kernel的代码不可避免的会变得臃肿不堪,而且其中大部分都是重复部分,这与kernel设计人员追求的减少重复、优雅简洁的目标无疑是相差甚远的。如果有了Device

Tree,用Device

Tree来描述各个硬件平台的硬件资源,在系统启动时由bootloader将Device

Tree作为一个参数传递给Kernel,Kernel根据该Device

Tree来初始化自己的硬件资源,多么优雅的设计啊。

毕竟是技术文章,我们接下来对一些重要细节做一些梳理。

首先是kobject、ktype、kset三者的细节,在此先列出三者数据结构的定义如下(基于kernel版本:3.14.29):

struct kobject {

const char *name;

struct list_head entry;

struct kobject *parent;

struct kset *kset;

struct kobj_type *ktype;

struct kernfs_node *sd;

struct kref kref;

#ifdef

CONFIG_DEBUG_KOBJECT_RELEASE

struct delayed_work release;

#endif

unsigned int state_initialized:1;

unsigned int state_in_sysfs:1;

unsigned int state_add_uevent_sent:1;

unsigned int state_remove_uevent_sent:1;

unsigned int uevent_suppress:1;

};

name:该Kobject的名称,同时也是sysfs中的目录名称。

entry:用于将Kobject加入到Kset中的list_head。

parent:指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。

kset:该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(别忘了Kset是一个特殊的Kobject)。

ktype:该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。

sd,该Kobject在sysfs中的表示。

kref,"struct

kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。

state_initialized,指示该Kobject是否已经初始化,以在Kobject的Init,Put,Add等操作时进行异常校验。

state_in_sysfs,指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。

state_add_uevent_sent/state_remove_uevent_sent,记录是否已经向用户空间发送ADD

uevent,如果有,且没有发送remove

uevent,则在自动注销时,补发REMOVE

uevent,以便让用户空间正确处理。

uevent_suppress,如果该字段为1,则表示忽略所有上报的uevent事件。

list/list_lock,用于保存该kset下所有的kobject的链表。

kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。

uevent_ops,该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。

release,通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。

sysfs_ops,该种类型的Kobject的sysfs文件系统接口。

default_attrs,该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中。

child_ns_type/namespace,和文件系统(sysfs)的命名空间有关。

总结,Ktype以及整个Kobject机制的理解。

Kobject是基本数据类型,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。

Ktype代表Kobject(严格地讲,是包含了Kobject的数据结构)的属性操作集合,同时指定该类Kobject释放资源的方式。

Kset是一个特殊的Kobject(因此它也会在"/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以不同属性的),同时用来负责该集合中kobject的uevent功能。

Kobject的核心功能是:保持一个引用计数,当该计数减为0时,自动释放(由本文所讲的kobject模块负责)Kobject所占用的meomry空间。这就决定了Kobject必须是动态分配的(只有这样才能动态释放)。

而Kobject大多数的使用场景,是内嵌在大型的数据结构中(如Kset、device_driver等),因此这些大型的数据结构,也必须是动态分配、动态释放的。那么释放的时机是什么呢?是内嵌的Kobject释放时。但是Kobject的释放是由Kobject模块自动完成的(在引用计数为0时),那么怎么一并释放包含自己的大型数据结构呢?

这时Ktype就派上用场了。我们知道,Ktype中的release回调函数负责释放Kobject(甚至是包含Kobject的数据结构)的内存空间,那么Ktype及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject嵌在哪个数据结构中,并通过Kobject指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。

讲到这里,就清晰多了。所以,每一个内嵌Kobject的数据结构,例如kset、device、device_driver等等,都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构!

接下来,我们以device为例,来说明对kobject、kset以及ktype的使用,从这个角度来看,device可以看做是对kobject的应用。

先列出device的结构定义如下:

struct device {

struct device

struct kobject kobj;

const char *init_name;

const struct

#ifdef

CONFIG_PINCTRL

#ifdef

CONFIG_NUMA

int numa_node; #endif

u64 *dma_mask; u64 coherent_dma_mask;

struct list_head dma_pools;

struct dma_coherent_mem *dma_mem; #ifdef

CONFIG_DMA_CMA

struct cma *cma_area; #endif

struct

dev_tdevt;u32

id;

spinlock_tdevres_lock;structlist_headdevres_head;

void (*release)(struct

bool offline_disabled:1;

bool

parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。

p,一个用于struct

device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/parent等设备中的链表头等等,具体可查看源代码。

kobj,该数据结构对应的struct

kobject。

init_name,该设备的名称。

type,struct

device_type结构是新版本内核新引入的一个结构,它和struct

device关系,非常类似stuct

kobj_type和struct

kobject之间的关系。

bus,该device属于哪个总线。

driver,该device对应的device

driver。

platform_data,一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。

power、pm_domain,电源管理相关的逻辑。

pins,"PINCTRL”功能。

numa_node,"NUMA”功能。

dma_mask~archdata,DMA相关的功能。

devt,dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev

struct

该kset会在系统初始化时分配空间并完成初始化(devices_init(void)),在创建每个具体的设备时(device_initialize(structdevice*dev)),会将该设备所含kobject的kset置为devices_kset。从面向对象角度来看,device是基于kobject构建的一个类,而每个具体设备的实现就是device类的实例化。

根据需求定义出device数据结构,该结构包含kobject,详见:

根据需求定义一个ktype,对于device这个ktype就是device_ktype,定义如下:

该ktype指明了device内存的释放方式,以及device对应attribute的存取方式。

在需要使用device时,用device_create分配device结构所需空间(包含kobject),最终会通过调用device_add--->kobject_add将该device所包含的kobject加入到devices_kset中,即添加到内核中。device_create代码详见:

在需要引用device时,调用get_device来增加device所含kobject的引用计数,调用put_device来减少所含kobject的引用计数。代码详见:

当引用计数减少为0时,会调用device_ktype的release函数,即device_release,用来正确释放device所分配的内存资源。

说到这里,我们明白了device是对kobject的应用,对应的数据结构分别是:

kobject

kset

ktype

device

devices_kset

device_ktype

用此种方式使用kobject得到的device,其对应的核心概念就是设备,实际上在这个实现过程中,我们就是利用了kobject层已经实现的一些共性,然后将其中一些该填的空填上,最终就得到了对device的实现。

同理,bus、class也可以从这个角度来说,将其看做是对kobject的一个使用,其对应关系如下:

kobject

kset

ktype

bus_type

bus_kset

bus_ktype

class

class_kset

class_ktype

最后,要想更进一步了解,就是那句老话,Readthe fucking source

code.

如果懒得下载全套代码,其实可以在线浏览,很多网站都可以做这件事情,而且做好了交叉引用的索引,非常方便,还可以选择不同的版本来浏览,如:http://elixir.free-electrons.com/

Linux浩瀚的设计思想中,到处都隐藏着我们编程的珠玑,差别只在于你付出了多少,又想捡到多少。

参考:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值