设备管理之设备模型

2015-6-18 chenhui 设备驱动

Linux 早期版本里是没有设备模型的,所有的设备都被零散地放置,非常不容易管理,所以后来为了解决设备管理的问题,内核开发者们专门提出并开发了一种拓扑结构的设备模型来管理所有的设备。

 

设备模型和 sysfs 

    sysfs是用来显示设备模型结构的一个特殊文件系统,他被挂载在 /sys 目录。


kobjectkset、子系统。

    理解这部分内容会比较难,这是因为这三个东西之间的关系是互相缠绕的,而我们讲述时却只能顺序讲解,所以读者在看到本小节时如果遇到不懂的地方可以先略过,在后面我会贴出并讲解内核中的实例代码来帮助读者进行理解。


    kobject 是设备模型的核心结构,他本身并不能做一些有意义的事情,他必须被其他对象所包含,实际上模型内的所有成员都内嵌了一个 kobject 结构并通过 kobject 结构来把他加入设备模型,这有点像内核的 struct list_head 结构,我们可以通过内嵌一个 list_head 对象来让某个对象实现链表功能,这和 kobject 是一样的 。一个 kobject 对应 sysfs 里的一个目录,而这个目录里的文件则由该 kobject 的属性生成(我们不是说过sysfs 是用来显示设备模型结构的吗?其显示的基础就是 kobject 生成目录,而 kobject 的属性生成目录下的文件)。我们先来看一下他的定义。

 

 

k_name : kobject 一般是被内嵌的,所以他必然有一个容器,这个 k_name 存储的就是他的容器名,实际上在sysfs里生成目录时,容器名就是目录名。

name : 他也是容器名。当容器名短于 KOBJ_NAME_LEN 个字节(一般20) 时,使用本字段存储容器名,否则使用 k_name 

kref : 容器的引用计数。

entry : 用来插入某个链表。

parent : 指向他的父 kobject 。(kobject 的父 kobject 一般是某个 kset 内嵌的 kobject,具体的请看 kset 那一节)。

kset : 指向其所属的 kset(具体的请看 kset 那一节)。

ktype : 指向 kobject 的类型描述符,kobject的属性就存放在这里。一个集合(kset)的所有 kobject 的 ktype 都指向同一个类型描述符。

dentry : 我们说过,一个 kobject 对应 sysfs 里的一个目录,这里就是指向那个目录的目录项对象。


我们说过,kobject 在 sysfs 中对应一个目录,而其属性则在目录下生成对应的文件。但实际上我们去查看 /sys 目录时,发现目录下面不仅仅是有文件而已,他还会有下一层目录,那么这些目录又是怎么出现的?其实这些目录都是当前目录的 kobject 的子 kobject 对象所生成的目录,你看,这是不是又把设备模型的结构体现出来了?一个 kobject 对应一个目录,而他的子 kobject 则会在该目录下继续生成目录。这个时候问题又来了,在什么情况下一个 kobject 才会成为另一个 kobject 的父亲呢?实际上,除非我们自行定义父 kobject,那么一个 kobject 的父亲就是他所属的 kset 内嵌的 kobject


kset 是一堆 kobject 的集合,我们来看一下 kset 的定义。

 

 

ktype : kobject也有一个 ktype 字段,我们说过他指向 kobject 的类型描述符。kset 的 ktype 也是指向一个类型描述符,其下属的所有 kobject 的 ktype 字段都指向其本身的 ktype 指向的类型描述符。

list : 我们说过,kset 是 kobject 的集合,list 就是这些 kobject 的链表头。

list_lock : 保护 list 链表的自旋锁。

kobj : 我们之前说过,设备模型内的成员都内嵌了 kobject 对象,kset也不例外(不然怎么在sysfs里生成对应的目录呢?)。

uevent_ops : 处理 kobject 的过滤和热拔插操作的函数表。


我们在把一个 kobject 注册入设备模型前,可以先让他加入某个 kset(我们假设不实现为他设置父kobject),然后内核就会在 sysfs 里 kset 对应的目录(准确的说是 kset 内嵌的 kobject 对应的目录)下生成一个新目录。如果我们没有为一个 kobject 加入 kset 就直接把他加入设备模型,那么内核就会在 sysfs 的根目录下生成其对应的目录。


我们在上面讲解了 kobject 和 kset。前者能在 sysfs 里生成一个目录并根据属性在目录下生成一些文件;而后者则能做为 kobject 的集合,最直观的体现就是他能够在 sysfs 中已有的目录下再生成一个目录。在以前的版本里,kset 和 kobject 一样是不能直接使用的,他也是被内嵌使用的,那么谁会内嵌他?这就是我们下面要讲的子系统。在以前的版本中,子系统由 struct subsystem 结构来进行描述,但现在这个结构不再使用了,现在都是直接使用 kset 来代替 subsystem 来进行注册,也就是说一个 kset 就可以当成一个子系统。好了,多说不如看代码,我们直接来深入分析系统初始化时调用的 devices_init() 来了解设备模型是怎么回事。

 

 

subsystem_register() 就是用来往设备模型注册一个子系统的函数,在之前的版本里我们需要传入一个 subsystem 结构的指针,但我们刚才说过子系统已经不再使用 subsystem 结构而是使用 kset 结构来描述,所以 devices_subsys 实际上是一个 kset 对象。那么devices_subsys 到底是什么?我们来看一下他的定义。

 

 

很明显,devices_subsys 是通过一个宏来定义的,我们再来看一下他展开后是什么样的。

 

 

然后我们继续来看subsystem_register() 的实现。

 

 

我们在之前说过,子系统原来使用的 subsystem 结构已经不再使用,而是用 kset 来直接代替,所以 subsystem_register() 就直接调用 kset_register() 了。这个函数从名字上就能看出来,就是把一个 kset 注册到设备模型里。

 

 

602 行,既然是要注册 kset,如果没传入 kset 那肯定要报错的。

604 行,初始化 kset

605 行,调用 kset_add() 来把 kset 加入设备模型。

 kset_init() 这个函数非常简单,就是初始化一些字段,所以我们这里就不分析他了,下面直接开始分析

 

 

我们在之前说过,设备模型里所有成员都通过内嵌一个 kobject 来把自身加入设备模型。

kset自然也是通过他内嵌的 kobject 来把自身注册到设备模型的,所以这里又调用 kobject_add() 并传入自己内嵌的 kobject 对象的指针。这个函数从名字上就可以看出来,他就是把一个 kobject 注册到设备模型。

 

 

kobject_add() 又调用 kobject_shadow_add() 来执行下一步操作。

 

 

 

170 行,先递增 kobject 的引用计数。如果 kobject 为空(即没有传入),那么这里就会返回错误。

172 行,如果 kobject 的长容器名为空,就让他指向短容器名数组。

174 行,此时无论容器名是长是短,k_name 肯定就是指向那个容器名了,但是如果容器名的第一个字符就是空字符,那就说明没有容器名,那么报错退出。对于我们的devices_subsys 来说,容器名就是 “devices” 。

180 行,递增父 kobject 的引用计数并让 parent 变量指向他。如果没有父 kobject,那么 parent 变量就为空。(这句代码要和186~195 这段代码一起理解)

186~195 行,如果 kobject 有所属的 kset 了且他没有父 kobject(在180行,如果没有父 kobjectparent为空),那么就把 kobject 的父 kobject 设为其所属 kset 的 kobject。在这里我们也可以看得出来,kobject的 parent 和 kset 字段之间的关系,即在调用本函数之前如果用户自定义了父亲,那么父亲是不会变的,否则就强行设置他所在的 kset 内嵌的 kobject 为他的父亲。对于我们的 devices_subsys 来说,他的 kobject 在定义的时候就没有定义 parent 和 kset,所以他即使这里执行完了,parent 和 kset 字段还是空。另外这段代码还会把 kobject 加入其所属的 kset 的下属链表(192行)。

197 行,开始在 sysfs 里创建 kobject 对应的目录。在这个函数里,如果他检测到 kobject 有父目录,那么就会在那个父目录下创建目录,否则就会在根目录下创建目录。对于我们的 devices_subsys 来说,他没有父亲,所以他会在根目录(/sys)下创建一个 “devices” 目录(kobject 的容器名),也就是 /sys/devices 目录。

198 行,如果创建失败,则调用 unlink() 把 kobject 从 kset 中卸下,最后返回错误。

我们接下来再来看 create_dir() 是怎么创建一个目录的。

 

 

52~58 行,首先判断欲创建目录的 kobject 有没有容器名,没有容器名是创建不了目录的。然后调用 sysfs_create_dir() 来执行创建操作,如果创建成功了,那么就会进入54~56 行的代码段,55 行会在 kobject 对应的目录中创建一些属性文件(我们之前说过,目录下的文件由 kobject 的属性生成),如果创建属性文件失败,则会在 56 行删除掉整个目录。

 


 

230 行,如果shadow_parent 不为空,那么我们就在 shadow_parent 这个目录下创建目录。对于我们的通常注册过程而言,他是一定为空的。

232 行,如果我们已经有了父 kobject,那么就取出父 kobject 在 sysfs 里对应的目录,我们接下来会在这个目录下面创建我们自己的目录。

234 行,如果我们没有父 kobject,那么我们就要看看 sysfs 是否已经挂载了,如果挂载了那么我们就让 parent 指向 sysfs 的根目录,即在根目录下创建我们自己的目录。

236 行,以上条件都不成立,报错吧。

239 行,调用 create_dir() 开始创建目录。(这个 create_dir() 和我们之前调用的 create_dir() 不一样哦!)。

240 行,如果创建成功,那么 error 会为 0,且 dentry 指向 kobject 对应的目录的目录项。这里就要让 kobject 的 dentry 字段指向其对应的目录的目录项。

 


 

177 行,先为新创建的目录设置权限。

180 行,查找或创建新目录的目录项。

181 行,如果成功得到目录项,则进入 if 代码段。

182 行,查找新目录项的目录名是否已经存在于其父目录的子文件(目录)列表下,如果存在了,那么不能创建一个同名的目录,则准备返回一个 –EEXIST ;如果不存在,则调用 sysfs_make_dirent() 来开始创建真正的目录。

186~193 行,如果目录创建成功,那么调用 sysfs_create() 为新目录分配一个 inode 对象,如果分配成功,那么初始化目录项操作表并把新目录的目录项对象加入目录项高速缓存。

195~203 行,如果创建目录或者分配 inode 失败,那么把新目录从父目录的子文件链表中摘下(删除目录),然后释放目录项对象。

 


 

141 行,sysfs 除了使用 VFS 的目录项对象外,他还自定义了一个 sysfs 专用的目录项,他就是 sysfs_dirent 

 

 

s_count :引用计数。

s_sibling :兄弟队列,同一目录的所有文件(目录)都挂在该链表。

s_children :子文件(目录)队列。

s_element :如果需要为 kobject 创建目录,那么他就指向那个 kobject 对象;如果是为 kobj 的属性创建文件,那么他就指向一个 attribute 对象;如果创建的是一个符号连接,则他指向一个 sysfs_symlink 对象。

s_type :文件类型(如文件类型或目录类型)

s_mode :文件访问权限。

s_dentry :他在 VFS 中对应的目录项对象。

143 行,执行创建操作,其实他就是为目录创建了一个 sysfs_dirent 对象。

144 行,把新目录的sysfs_dirent 对象存放到父目录的子文件(目录)队列下,这样在查找文件时就能找到他了。

 

 

122~124 行,为新目录分配一个sysfs_dirent 对象。

126~131 行,初始化新目录的 sysfs_dirent 对象。

 


 

__sysfs_list_dirent() 非常简单,他就是把新目录插入到父目录的子文件(目录)队列里,这样就算是成功了。

 至此,我们已经完全了解了 kobject 的目录创建过程,devices_init() 一路走下来,”/sys/devices” 这个目录也就成功创建了,但实际上我们还有创建属性文件的部分还没有讲,其实创建属性文件和创建目录是非常相似的,大家有兴趣可以自己去分析,这里就不再去讲解了。


总线、设备和驱动

总线上是对设备的一种分类方法,比如说 USB 设备都会挂在 USB 总线上,PCI 设备则都会挂在 PCI 总线上。我们可以使用 struct bus_type 结构来描述一个总线。

 

 

name :总线名。

subsys :每条总线实际上也都是一个单独的子系统,所以他会使用到这个 kset。另外这里要注意的是,每条总线都是总线子系统的儿子,也就是说,每条总线被注册到设备模型时,他会在 /sys/bus/ 目录下再生成一个对应的目录(/sys/bus这个目录就是总线子系统)。

drivers :在以前的版本中,drivers和 klist_ drivers是一体的,即drivers生成保存总线下所有驱动对应目录的父目录,也把所有子驱动挂在 kset 的 list 链表上。现在他们被分开了,drivers只起到第一个作用。

devices :在以前的版本中,devices 和 klist_devices 是一体的,即devices 生成保存总线下所有设备对应目录的父目录,也把所有子设备挂在 kset 的 list 链表上。现在他们被分开了,devices 只起到第一个作用。

klist_devices :总线的设备队列。属于总线的所有设备都挂在这条队列上。

klist_drivers :总线的驱动队列。属于总线的所有驱动都挂在这条队列上。

bus_attrs、dev_attrs、drv_attrs :总线属性、设备属性、驱动属性。

match :我们之前看到了,总线有两条队列分别用于存放设备和驱动。我们也知道设备和驱动并非完全一对一的关系,他们都是被单独添加到总线的,但是这就有一个问题,那就是:设备和驱动怎么知道对方是我需要的那个对象呢?所以这就出现了 match 函数,每当有设备或驱动添加到总线时,他就会调用本函数扫描对方队列是否有对象可以和自己匹配(确认方法多种多样,不过一般就是比对设备名和驱动名是否相等),如果可以匹配就会执行下一步东西(下一步动作是什么?我们这里先不讲,后面会说到)。

uevent :这个函数是在发生“热拔插”事件时会被调用的一个函数,他用来添加一些环境变量。具体的可以看后面的热拔插一节。

probe :当 match() 函数匹配成功时,这个函数就会被调用执行一些后续操作(什么后续操作?后面再讲)。

remove :当驱动或设备被移除时,他就会被调用执行一些后续操作。

drivers_autoprobe :如果他为 1,那么当设备或驱动插入总线时,总是查找是否有合适的驱动或设备能和自己匹配。

我们可以调用 bus_register() 把一个总线注册到内核。他先把总线的父亲设为bus_subsys(这样他才能在 /sys/bus/ 目录下生成目录)并把自己注册到设备模型,然后把总线的驱动队列和设备队列注册到设备模型并在总线对应目录下生成 devices目录和 drivers 目录,最后生成几个属性文件就结束。这个函数的代码其实非常简单,这里就不贴了。

刚才说完了总线,现在我们再来说一下设备。设备在内核中使用 struct device 结构来进行描述。

 

 

knode_bus :总线有一条子设备队列,设备就是通过这个字段挂到那个队列上的。

parent :父设备。他一般是某条总线或者是某种设备控制器。如果他为空,就说明他是顶层设备。

kobj :设备对应的 kobject,设备使用他来加入设备模型。

bus_id :设备名。

bus :设备所属的总线。

driver :设备对应的驱动程序。

node :每个类都有其下属设备形成的一个队列,设备通过这个字段挂入那个队列。

class :设备所属的类(请看下一节:类和类设备)。

devt :设备的设备号。

 

我们可以调用 device_create() 来创建并注册一个设备。

 

 

105 行,设备必须要有其所属的类。

108~112 行,申请一个设备描述符。

114~117 行,初始化设备描述符。

120 行,初始化设备名。

122~124 行,把设备注册到设备模型。

 

 

824 行,初始化设备描述符。

825 行,把设备描述符加入设备模型。

 


 

553 行,还记得这个 devices_subsys 吗?没错,他就是我们之前在 devices_init() 里讲解的那个子系统,他在 sysfs 里的根目录里生成了一个 devices 目录。我们这里把设备描述符加入 devices_subsys 这个 kset,这样他就会在这个目录下生成自己的目录了。

554 行,初始化 kobject

555 行,初始化子设备队列。

558 行,初始化兄弟设备队列。

 


 

664 行,增加设备的引用计数(其实就是他的kobject)

665 行,如果设备描述符不存在或者他没有设备名,则报错。

670 行,取出他的父设备并递增引用计数。

671~673 行,如果设备有所属的类,那么就把设备的 kobject 父亲设为类的 kobject;否则就把设备的 kobject 父亲设为父设备的 kobject 。(要注意,这里设置的是设备的 kobject 的父亲而不是他的父设备)。

675 行,把设备名复制到 kobject 的容器名里,这样创建出来的目录才会和设备名相同。

676~678 行,把设备加入设备模型,这个函数执行完后设备就在 sysfs 里成功创建了对应的目录。至于这个目录到底创建在哪,那就要看他是否有所属的类了,如果有那就会在类对应的目录下创建;如果他没有所属的类,那么只要他存在父设备,他就会在父设备对应的目录下创建;如果他连父设备也没有,那么他就会在 devices_subsys 对应的目录(/sys/devices)下创建(没有类和父设备的话,设备的 kobject 的 parent 字段就会被设为空(见 setup_parent() ),所以 kobject_add() 会把 kobject 所属的 kset 设为他的父亲。而设备所属的 kset 则已经在 device_initialize() 里被初始化为 devices_subsys)。

680~681 行,如果设置了 platform 回调函数,则调用他。

683~685 行,如果设备有其所属的总线,那么使用通知链机制通知总线有新设备加入了。

687~695 行,初始化设备的事件属性并为他在设备对应的目录下创建文件。这样可以通过在用户空间写入数据的方式来触发内核的一些动作。

 


 

698~716 行,若存在主设备号,则在设备对应目录下创建一个属性文件”dev”,读取这个属性文件可以得到设备的设备号。

719~717行,如果设备有其所属的类,那么在设备对应目录下创建一个名为 “subsystem” 的符号链接,这个链接指向其所属的类。如果设备所属的类不是他的 kobject 的父亲,那么就说明他有独立的父亲,既然如此,那就在类对应的目录下创建一个指向设备的符号链接。如果存在父设备,那么就在设备对应目录下创建一个指向父设备的目录。

 


 

740 行,为设备的属性创建属性文件。

742 行,电源管理相关。

744 行,把设备插入总线。其实这个函数也就是创建几个属性文件和符号链接而已。

746 行,触发 udev 机制。这里通知用户空间有一个 kobject 加入了设备模型,最终用户空间的 udev 程序会从设备的 “dev” 属性文件(698~716 行)中读出设备的设备号并创建相应的设备节点。

747 行,把设备插入总线的设备队列,并在总线的驱动队列找到一个适用于当前设备的驱动。

748~749 行,如果父设备存在,就把设备插入父设备的子设备队列。

751~759 行,如果设备有所属的类,那么就把设备插入类的子设备队列。

755 行,遍历类的所有接口并调用他们的 add_dev 函数。

然后我们再来看一下驱动描述符 stucr device_driver

 

 

name :驱动名,他一般用来和设备名相比较。

bus :驱动所属的总线。

obj :驱动内嵌的 kobj

klist_devices :驱动支持(匹配上)的设备队列。

knode_bus :用来挂到总线的子驱动队列。

owner :驱动程序所在模块(如果有的话)。

probe :当设备和驱动匹配上时,内核会调用驱动的 probe 函数来执行下一步操作。

remove :当驱动从总线中取出时,内核调用驱动的 remove() 函数。

shutdown :设备断电时调用的方法。

suspend :设备置于低功率状态时所调用的方法。

resume :设备恢复正常状态时所调用的方法。

我们可以调用driver_register() 来注册一个驱动。

 

 

163 行,初始化 klist_devices 队列。

164 行,把驱动添加到总线

 

 

605~609 行,取出并递增驱动所属的总线的总线描述符。

612~614 行,初始化驱动名。

615~617 行,把驱动所属的 kset 设为其所属的总线,并注册他到设备模型,这样他就会在总线对应目录下生成一个新目录了。

619~623 行,如果总线的 drivers_autoprobe 字段为 1,,就说明我们需要探测其所属总线上是否有驱动支持的设备,那么调用 driver_attach() 去探测。

624 行,把驱动挂入总线的子驱动队列。

625 行,在驱动的目录下出创建一个驱动所属模块的符号链接

627~628 行,在驱动对应目录下生成属性文件。

633~638 行,在驱动对应目录下生成属性为 driver_attr_unbind 和 driver_attr_bind 的属性文件。


类和类设备

        类和总线有一点很相似,那就是他们本身都是子系统属性,但他们都不是顶层子系统(在 sysfs 根目录生成目录的子系统)。所有总线都属于总线子系统(bus_subsys),而所有的类则都属于类子系统(class_subsys)。

类用于描述一组相同特性的设备,比如说所有基于 framebuffer 的 LCD 驱动,他们同属于一个类。

我们可以调用 class_create() 来创建一个类。

 


 

210~214 行,申请一个类描述符。

216~219 行,初始化类描述符。

221~223 行,注册类。如果注册失败,则跳到 228 行释放类描述符并返回错误

 


 

145~147 行,初始化类的三个链表,这三个链表的作用可看上面我们他们的解释。

148 行,我们说过,在以前的版本中,因每个类都看成是一个子系统所以包含了一个 subsystem 结构。现在取消 subsystem 结构了,自然就换成包含 kset 结构了,所以这里初始化他内嵌的 kset 

149 行,初始化类的信号量。

150~152 行,初始化类名。

154 行,这里的 class_subsys和我们之前讲的 devices_subsys 是一样的,都是静态定义的子系统,且也是在 sysfs 的根目录中生成一个目录(/sys/class目录)。这里把当前类的父亲设为 class_subsys(通过他内嵌的 kset 再内嵌的 kobject),这样在我们为类生成目录时,就会在 /sys/class/ 目录下再生成一个目录。

156 行,把类注册到设备模型(准确的说是把类的 kset 注册到设备模型,然后最后注册的就是一个 kobject)。

157~160 行,如果注册成功,则在类的目录下创建属性文件。

我们在上面创建类的过程中可以发现,所谓的类实际上就是一个父亲为 class_subsys 的子系统。实际上类只是用来为设备分类而已,其本身什么都做不了,所以说一般来说我们创建一个类就是为了给他添加类设备(比如说网络设备类,那么我们可以给他添加A网卡设备,也可以再给他添加B网卡设备),那么类设备又是怎么回事?我们下面继续讲解。

类设备使用 struct class_device 结构来进行描述。

 

 

node :类设备都是属于某个类的,所以类会有一个链表来管理他下属的所有类设备。类设备通过 node 字段挂到类的类设备链表。

kobj :类设备作为设备模型的一员,自然也有其内嵌的 kobject

class :指向类设备所属的类。

dev_t :类设备对应设备的设备号。

dev struct device 描述符用来描述一个设备。这里的 devt 字段就是指向类设备对应的设备的设备描述符。

class_data :类设备的私有数据。

parent :父类设备。

class_id :类设备名。

我们可以调用class_device_create() 来创建一个设备类。

 


 

cls :类设备所属的类。

parent :类设备的父类设备,可以为空。

dev_t :类设备的设备号,他非常重要,类设备最后连接到的就是这个设备号对应的设备。

device :类设备的设备,可以为空。

fmt :类设备名。

740 行,类设备必须要有一个有效的类才行。

743~747 行,申请一个类设备描述符。

749~754 行,初始化类设备。

756~758 行,初始化类设备名。

759~761 行,注册类设备。

 

 

703 行,初始化类设备。

704 行,执行真正的类设备注册操作。

 

 

571 行,把类设备的父 kobject 设为 class_obj_subsysClass_obj_subsys 又是一个顶层子系统。

572 行,初始化类设备的 kobject

 


585~587 行,递增类设备的引用计数。

589~590 行,类设备必须要有名字。

592~594 行,取出类设备所属的类并递增他的引用计数。

594 行,取出并递增类设备的父类设备。

601~602 行,初始化类设备的 kobject 名为类设备名。

605~608 行,如果存在父类设备,就让类设备的 kobject 父亲设为父类设备的 kobject;否则就让他的父亲设为他的类。更为通俗的说,这里就是:如果存在父类设备,就让当前类设备的目录在父类设备的目录下生成;否则就在他所属的类对应的目录下生成。

610~612 行,把类设备添加到设备模型。

614~617 行,在类设备的目录下创建一个名为 “subsystem” 的符号链接,这个符号链接指向他所属的类对应的目录。

618~624 行,在类设备对应的目录下生成一个属性文件。

 

627~644 行,如果类设备的主设备号不为 ,则在类设备的目录下创建一个属性文件,读取这个属性文件可以得到类设备的设备号。

647~661 行,为类设备创建属性文件。

666 行,触发 udev 机制,最终用户空间的 udev 程序会从类设备的 “dev” 属性文件(627~644 行)中读出设备的设备号并创建相应的设备节点。

669 行,把类设备插入到类的子设备队列。

670 行,遍历类的所有接口并调用他们的 add 函数。

至此,类设备的创建也就完成了。这个时候大家可能还比较疑惑,那就是到底什么是接口?所谓的接口,其实就是为类添加一些额外作用,比如说我们要创建一个类设备时,如果我们需要添加一些自定义的操作,那么我们就可以为类增加一个接口并定义他的 add 函数即可。



发表评论:

Copyright ©2015-2016 freehui All rights reserved