I/OKit驱动程序框架
和其他操作系统不同,XNU的独特之处在于为设备驱动程序提供了一个完整的运行时环境。XNU的设备驱动程序运行时环境称为I/O Kit,是一个苹果开发的一个私有组件。I/O Kit为开发者提供了面向对象的强大能力,主要包括子类化和方法重写,通过这些特性使得设备驱动程序的开发过程非常高效。
I/O Kit 还提供了一组用户态的API:I/O Kit Framework,这个框架提供了一些更高级的特性,例如内核态通知以及内核态和用户态之间的双向通信。
I/O Kit 简介
I/OKit 的设计非常独特。尽管所有的操作系统肯定都有设备驱动程序,但是大部分都是完全用C语言编写的,没有自己的运行时环境。但是没有一个能够像I/OKit 这样提供面向对象的开发环境。
I/OKit 是什么
一套几乎自包含的编程环境:I/OKit 提供了一些对于Mach API的封装的API,这些API用于线程创建、内存分配和一些通用任务
一个面向对象的开发环境:I/OKit 驱动程序是从继承于OSObject类实例化来的。开发者可以找到和自己要写的驱动程序最接近的那个类,然后从这个类开始实现自己的类,有效地重用这个类的通用部分的代码
一个特别为驱动程序而设计的开发环境:I/O Kit 为很多操作设备特别要考虑的问题提供了支持,主要包括即插即用和电源管理;另一个重要的架构思想是驱动程序堆叠(driver layering),允许为一个设备驱动构建在另一个设备驱动的基础之上
** 一个工作循环驱动的开发环境**:I/O Kit 提供了一个工作循环(work loop)模型,工作循环是一个不断处理事件的消息循环。通常使用工作循环可以极大简化了并发问题,而且通常可以避免对锁的使用,锁是会影响性能的
一个基于注册表的开发环境:I/O Kit 中的所有东西都是要记账的:对象需要引用计数,类需要注册等,所有这些信息都通过I/O Registry(I/O 注册表)管理。I/O Kit Registry 是一个多层的层次化数据库,跟踪对象以及对象之间的相互关系
一个用户态友好的开发环境:I/O Kit 提供了用户态可以访问的API,而且完全由用户态实现一些驱动程序,例如USB 设备的驱动程序就是完全在用户态实现的。I/O Kit 注册表也可以在用户态访问,因此用户态可以硬件配置和参数信息
一个通过C++子集实现的开发环境:I/O Kit 是基于C++的,所以I/OKit 利用了一些C++语言有用的编译时特性,例如:
名称空间:I/O Kit 驱动程序可以通过C++名称空间将自己的函数和符号包装起来,避免内核中全局的符号突出
名称重整(name mangling ):I/O Kit 的符号名称是经过重新整过的,其中在函数名称中包含了C++层面的原型信息(名称空间、返回值和参数)
I/OKit 不是什么
一个完整的C++开发环境:I/O Kit 没有使用以下特性:
模版
异常
标准的构造函数
一套全功能的API:由于没有完整的C++运行时库,所以所有的运行时库功能都是通过libkern库提供的。为了完整地兼容I/O Kit,开发者应该仅仅使用libkern中的API
最灵活的编程模型:I/O Kit 驱动程序必须实现一个非常具体的生命周期,因此和其它操作系统中众所周知的驱动程序回调函数有严重的不同。驱动程序的生命周期非常复杂,开发者要清楚在什么条件下必须实现什么回调函数
代码可以搞定一切:I/OKit 驱动程序不仅仅是二进制程序。作为内核扩展,还必须包含一个Info.plist文件。由于是I/O Kit 驱动程序,所以Info.plist文件必须包含I/O Kit相关的指令,如果没有这些指令的话,驱动程序就无法正常工作
libkern:I/O Kit的基类
libkern C++运行时是I/OKit的基础,定义了所有I/O Kit驱动程序都可以使用的基础类。libkern中的基础类和CoreFundation中的一些功能上有交集,这些基础类定义在XNU的libkern/libkern/c++目录中,实现在libkern/c++中,一个类对应一个文件。这个目录下还包含了其它支持文件(OSRuntime.c和OSRuntineSupport.cpp),里面包含了libkern初始化的代码,以及序列化函数(OSSerialize/OSUnserialize)的代码。序列化/反序列化操作作用于向/从XML属性列表中写入/读取对象。
OSObject
I/OKit 中所有类都会追溯到一个祖宗,那就是OSObject。
OSMetaClass
I/O Kit 并不支持标准C++的Run Time Type Identifation(RTTI)。但是I/O Kit 提供了一个同样强大的机制:OSMetaClass类。这是一个抽象类,不能直接使用。需要通过一些特殊的宏来实现RTTI的特性。这些宏包括:
OSDeclareDefaultStructors:用于生产I/O Kit 对象默默认构造和析构函数的原型。
OSDefinwMetaClassAndStructors:类似地,这个宏用在类的实现中。抽象类使用OsDefineMetaClassAndStructors:这两个类都可以添加WithInit后缀,得到的还是包含初始化函数的宏。
I/O Kit Registry
I/O Kit 维护了一个保存了所有对象及对象间关系最新信息的数据库。这个数据库在内存中,出纳岗位I/O Registry 。 I/O Registry 是一个多平面的数据库。简单地说,I/O Registry 表示的是三维的关系。
IORegistryEntry
I/O RegistryEntry 类是I/O Registry 中所有对象的父类。I/O RegistryEntry 是一个包含了对象属性的简单容器,对象的属性保存了一个OSDictionary 对象中。这个类不应该直接继承。I/O Kit 对象的父类是IOService,即IORegistryEntry的子类。通过继承关系,所有的驱动程序也自动注册了。
IOService
IOService是IORegistryEntry的直接后代也是唯一后代。IOService也是所有驱动程序()包括苹果提供的驱动程序以及第三方的驱动程序)的祖先,尽管大部分驱动程序不是直接继承于IOService,但是IOService仍然是这些驱动程序的最终祖先。这些驱动程序从IOSevice继承了一组可以直接使用的方法(例如电源管理和中断处理等),在有些情况下还需要实现一些方法(例如驱动程序标准的回调函数)。
用户态的I/O Kit
I/O Kit 可以通过IOKit.Framework提供的API以及IOKitLib API 和 用户态通信。这个框架就是给用户态使用的。内核态的I/O Kit组件使用的是Kernel.Framework的IOKit/子目录。用户态应用程序可以通过这些API和内核中的I/O Kit 驱动程序以及I/O Kit组件(最重要的是I/O Registry)本身做接口。
所有的I/O Kit 都依赖一个特殊的主机端口,I/O Kit 通过IOMasterPort( )获得并引用这个端口。用户态和I/O Kit内核组件以及驱动程序之间的通信是通过Mach 消息进行的。
访问I/O Registry
有了Master Port,应用程序就可以无限量地发送I/O Kit 请求了。通常情况下有很多请求都涉及查询I/O Reigstry。
获得/设置驱动程序属性
由于在I/O Kit 模型设备驱动程序中都是对象,因此有自己的属性。这些属性在用户态都是可见的,而且可以通过用户态的客户程序访问甚至修改。可以通过以下几个函数进行属性操作:
- IORegistryEntryCreateCFProperties( ):获取驱动程序完整属性表的拷贝
- IORegistryEntryCreatepProperty( ):通过名字访问某一个属性
即插即用(通知端口)
用户态的应用程序有时会要求I/O Kit发出任何I/O Registry 变化的通知,例如设备到达(添加)和分离(删除),以及某些特定设备的状态变化。这个通知对于即插即用设备的支持非常有用,例如插入i-设备时启动iTunes。
要请求这些通知,客户程序必须首先创建一个通知端口。通过调用IONotificationPortCreate 返回的结果得到IONotificationPort 的引用(即IONotificationPortRef)。这个引用在用户态是不透明的,实际上隐藏了一个Mach端口。通知端口可以通过Mach消息的原语直接监听,不过推荐的使用方法是将通知端口连接到一个运行循环(run loop)结构中。运行循环属于Core Foundation 编程模型,实现了消息循环。当消息到达通知端口时,用户提供的回调函数会被调用。
I/O Kit 电源管理
驱动程序可以注册电源相关的通知,既可以响应系统电源状态的变化,也可以影响系统电源状态的变化。在IOPower 平面上可以找到要求这项功能的驱动程序,而且规模随着对电源依赖性的增加而成本增大。应用程序也可以请求参与到电源管理当中。
I/O Kit 诊断
除了ioreg(8)命令和Xcode 捆绑的IORegistry Explorer外,苹果仅仅提供了另外两个诊断工具:
- ioalloccount(8):显示I/O Kit 分配的内存消耗
- ioclasscount(8):计算所有注册的I/O Kit 类及子类的实例数,提供的是积累的计数
I/O Kit 内核驱动程序
I/O Kit 驱动程序是从一个公共祖先IOService 继承而来的对象。IOService 下面的继承树非常庞大复杂,顺着这棵树下来,驱动程序越来越具体化,并且只适合驱动程序专用的设备或总线。I/O Kit的驱动可以分为“驱动程序(driver)”和“节点(nub)”。节点简单地说就是两个驱动程序之间的适配器,表示被控制的设备。
驱动程序匹配
I/O Kit 维护了一个Catalogue对象,这个对象是一个保存了所以已知设备驱动程序和注册的驱动程序personality的数据库。I/O Kit 利用驱动程序的personality来匹配驱动程序和新加入的设备。
IOWorkLoop
I/O Kit 采用了NeXT的runloop模型,用户态开发者应该会想到CFRunLoop。I/O Kit 版本的runloop 成为IOWorkLoop,基本思想是一样的:提供一个单线程且现场安全的机制处理所有类型的时间,如果不采用这种机制则是异步的。工作循环的访问被一个互斥体保护,因此不需要开率可重入的问题以及线程安全的问题。
中断处理
尽管有部分设备驱动程序是虚拟设备的驱动程序,但是大部分驱动程序都要面对真实的硬件,因此需要和中断打交道,I/O Kit 想驱动程序开发者隐藏了Mach 的中断处理逻辑,I/O Kit 的这项隐藏做得非常棒,开发者可以很开心地忽略底层细节。开发者不需要陷入中断处理相关的具体细节,I/O Kit 提供了面向对象的中断视图,即高效又直观。