老谭笔记

初探Mac OSX内核开发(三)——通过属性与应用层交互

前面两篇文章已经介绍了如何创建内核扩展和驱动程序,这都属于内核态编程,内核态与用户态编程是两个被操作系统所隔离的区间,两者运行的环境、权限都不相同,所以也不能直接进行通信(关于OSX系统的编程环境介绍可以点击我之前的一篇文章《OSX系统编程环境的介绍》),所以我们就来一起探讨一下用户态的应用层程序如何与驱动程序进行交互。

在前一篇文章的文章中我们已经看到,工程中是引用了Kernel.framework框架的,它是一个面向于内核编程的框架,而如果应用层需要与内核通信呢就需要引用另一个框架IOKit.framework,这个框架定义了一些与公共的接口,可以让用户层通过这些接口与一些标准的驱动程序进行交互,在之前的工作中,我也站在应用层的角度使用过这些接口来获取硬件和系统的状态信息,相关的示例可以点击这篇文章《通过IOKit读取系统信息》,并且本文中也要使用到这篇文章中的代码块。

好了,打开Xcode,创建一个普通的应用程序,由于不涉及到UI部分,我们就选择命令行程序的模板就行了,然后取名为AppTest,然后为工程添加IOKit.framework的引用,并修改main.m文件为以下的内容:

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
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
int main(int argc, const char * argv[])
{
io_iterator_t iterator;
kern_return_t kr;
io_object_t driver;
CFMutableDictionaryRef matchDictionary = IOServiceMatching("com_osxkernel_driver_IOKitTest");
kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &iterator);
if (kr != kIOReturnSuccess)
{
return 0;
}
while ((driver = IOIteratorNext(iterator)) != 0)
{
CFMutableDictionaryRef properties = NULL;
kr = IORegistryEntryCreateCFProperties(driver,
&properties,
kCFAllocatorDefault,
kNilOptions);
if (kr != kIOReturnSuccess || properties == NULL)
{
continue;
}
NSLog(@"%@",(__bridge NSDictionary*)properties);
}
return 0;
}

把上一篇文章中的驱动文件加载好,然后编译运行AppTest,如果一切顺利的话,你就能看到控制台会输出与IOKitTest驱动相关的属性信息,至此,从应用层单向的获取驱动的属性信息就成功,由于该属性列表由驱动程序去维护管理,所以驱动程序通过给属性中添加不同的信息都能传递到应用层了。

上面已经实现了从应用层到内核层的单向通信,下面我们继续修改代码来尝试从应用层向内核传递信息,从上面的代码我们已经看到可以获得驱动程序的属性列表,那我们如何在应用层向该属性表中写入数据而达到向内核层传输数据呢,修改上面的代码:

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
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
int main(int argc, const char * argv[])
{
io_iterator_t iterator;
kern_return_t kr;
io_object_t driver;
CFMutableDictionaryRef matchDictionary = IOServiceMatching("com_osxkernel_driver_IOKitTest");
kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &iterator);
if (kr != kIOReturnSuccess)
{
return 0;
}
while ((driver = IOIteratorNext(iterator)) != 0)
{
//读取数据
CFMutableDictionaryRef properties = NULL;
kr = IORegistryEntryCreateCFProperties(driver,
&properties,
kCFAllocatorDefault,
kNilOptions);
if (kr == kIOReturnSuccess)
{
NSLog(@"%@",(__bridge NSDictionary*)properties);
}
//写入数据
kr = IORegistryEntrySetCFProperty(driver,
CFSTR("message"),
CFSTR("Hello,Kernel!"));
if (kr == kIOReturnSuccess)
{
NSLog(@"已经向内核发送消息!");
}
}
return 0;
}

修改之后,我们的应用层代码便已经有向内核传递信息的功能,但如何让内核收到消息呢?当然我们是需要去修改驱动程序的代码的,应用层中我们使用了IORegistryEntrySetCFProperty方法去设置属性,驱动中对应需要去实现setProperties方法来接收数据并处理,打开驱动程序的工程,加入以下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//记得去头文件中声明方法
IOReturn com_osxkernel_driver_IOKitTest::setProperties(OSObject *properties)
{
IOLog("IOKitTest::setProperties\n");
OSDictionary *dict = OSDynamicCast(OSDictionary, properties);
if (dict)
{
OSObject *value = dict->getObject("message");
OSString *message = OSDynamicCast(OSString, value);
if (message)
{
setProperty("message", message);
IOLog("message:%s\n",message->getCStringNoCopy());
return kIOReturnSuccess;
}
}
return kIOReturnUnsupported;
}

然后重新编译并加载驱动程序,此时再运行应用程序,你便在控制台看到各种输出信息了,至此,驱动程序与应用程序之间的双向通信就已经都实现了,但基于属性的的交互只适合于数据量很小的情境,如果需要更灵活的交互,后面我们会继续探讨其它的方式!

本文中的驱动程序与应用程序的Demo下载:IOKitTest+AppTest.zip