老谭笔记

初探Mac OSX内核开发(二)——创建驱动程序

接上文,我们已经通过Xcode自带的模板完成了简单的内核创建到部署的过程,然后我们准备来尝试创建一个驱动程序然后完成布署,上文上已经提到了内核扩展与驱动的差别在于加载的时机不一样,另外他们还有一个很明显的差别,内核扩展是用C来实现的,而Mac的驱动却是用C++去完成的,Mac OSX系统的驱动开发有一套基于C++的IO Kit框架,这也是OSX内核中非常重要的一个部分,在内核开发中使用C++其实只是它的一个子集,嵌入式C++,它不可以使用C++的异常、多继承、模板、运行时等特性,但IO Kit框架为了开发的方便而去实现了类似Cocoa编程中的引用计数、运行时、容器等特性。

关于驱动程序的加载过程,我用下面的图来表示:
driver_01

当硬件插入电脑时,系统会根据硬件的类型创建一个Provider(提供者)的对象,并且这个Provider会在初使化的过程中去尝试匹配合适的驱动程序,如上图,我们开发一款适合于PCI声卡或USB音频设备的驱动程序,首先硬件载入后会去查找驱动程序Info.plist中的IOKitPersonalities的信息,如果IOProviderClass中定义了IOPCIDriver就表示可以为PCI做匹配,如果同时也有IOUSBDriver项就表示USB的设备也可以做匹配,同时如果有多个支持PCI或USB的驱动出现时,就会去调用驱动程序的probe方法,最终找到匹配度最高的驱动程序进行加载(实际的原理会更复杂),然后该驱动程序就可以通过不同的Provider与硬件通信,并且通过标准的IOAudioDriver接口为系统提供音频的服务,从而用户程序就可以通过系统的标准方法最终让音频设备工作。

好了,直接开始编码,打开Xcode,选择IOKit Driver模板,取名工程名IOKitTest,然后可以在工程中看到会自动创建两个文件,分别是IOKitTest.h和IOKitTest.cpp,但非常遗憾的是,模板并没有为我们在这两个文件中生成任何的内容。我们手动在IOKitTest.h中输入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <IOKit/IOService.h>
class com_osxkernel_driver_IOKitTest : public IOService {
//一个宏定义,会自动生成该类的构造方法、析构方法和运行时
OSDeclareDefaultStructors(com_osxkernel_driver_IOKitTest)
public:
//该方法与Cocoa中的init和C++中的构造方法类似
virtual bool init(OSDictionary* dictionary = NULL);
//该方法与Cocoa中的dealloc和C++中的析构方法类似
virtual void free(void);
//进行驱动匹配时调用
virtual IOService* probe(IOService* provider, SInt32* score);
//当匹配成功后加载驱动
virtual bool start(IOService *provider);
//当硬件移除时或卸载驱动
virtual void stop(IOService *provider);
};

在IOKitTest.cpp中输入以下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "IOKitTest.h"
#include <IOKit/IOLib.h>
//让你拥有Cocoa中的super关键字一样的体验
#define super IOService
//和头文件中的宏定义类似,自动生成一些特定代码
OSDefineMetaClassAndStructors(com_osxkernel_driver_IOKitTest, IOService)
//该方法打印出dict的内容
bool com_osxkernel_driver_IOKitTest::init(OSDictionary *dict)
{
bool res = super::init(dict);
IOLog("IOKitTest::init abcd\n");
OSCollectionIterator *iter = OSCollectionIterator::withCollection(dict);
if (iter)
{
OSObject *object = NULL;
while ((object = iter->getNextObject()))
{
OSSymbol *key = OSDynamicCast(OSSymbol, object);
IOLog("key:%s ",key->getCStringNoCopy());
OSString *value = OSDynamicCast(OSString, dict->getObject(key));
if (value != NULL)
{
IOLog("value:%s\n",value->getCStringNoCopy());
}
}
}
return res;
}
void com_osxkernel_driver_IOKitTest::free(void)
{
IOLog("IOKitTest::free\n");
super::free();
}
IOService* com_osxkernel_driver_IOKitTest::probe(IOService* provider, SInt32* score)
{
IOService *res = super::probe(provider, score);
IOLog("IOKitTest::probe\n");
return res;
}
bool com_osxkernel_driver_IOKitTest::start(IOService *provider)
{
bool res = super::start(provider);
IOLog("IOKitTest::start\n");
//只有调用了此方法,应用层才可以连接该驱动
registerService();
return res;
}
void com_osxkernel_driver_IOKitTest::stop(IOService *provider)
{
IOLog("IOKitTest::stop\n");
super::stop(provider);
}

至此,程序部分就完成了,但如果需要让驱动程序正常的被加载,我们需要在Info.plist文件中加入必要的项目,如下图:
QQ20131026-5

其中OSBundleLibraries中的两个值对应的是内核中iokit和libkern的版本,此处我们设置内核的版本即可,而IOKitPersonalities就是上文也有提到的,用于匹配驱动程序用,IOProviderClass是需要对应特定类型的,如IOPCIDriver、IOUSBDriver等,但我们这是一个测试程序,不希望去驱动某个特定的硬件,所以指定IOResources就是无需硬件设备。

好了,我们的驱动程序就已经开发完成了,编译后在Build目录中找到IOKitTest.kext文件,如第一篇文章一样,用以下的命令完成文件所有者修改及驱动的加载:

1
2
sudo chown -R root:wheel IOKitTest.kext
sudo kextload IOKitTest.kext

然后我们在控制台Console中就可以看到依次执行了init,probe,start这三个方法,并且发现在init中打印的dict字典正是Info.plist文件中IOKitPersonalities项所填写的项目。
当在命令行下执行以下操作:

1
sudo kextunload IOKitTest.kext

你会在控制台上收到stop,free的的日志信息。

本文也只是记录了如何创建一个驱动程序的示例,但具体如何通过驱动程序与特定的硬件通信,还未涉及到相关的业务,文章中的示例代码下载:IOKitTest.zip