又过了一个星期了,终于抽出时间继续来更新我的博客,接上一篇文章,我们已经讨论了如何通过属性来让应用层与内核产生交互,但试想一下,针对一个功能更加复杂的驱动程序,仍然使用属性的值来定义一套交互的过程,会显得既笨拙又低效,今天就来继续学习一下如何用更好的方式来完成这个交互,我用了两篇文章来讨论应用层与内核之间的交互过程,一方面因为我能力有限短时间内还不能深入到具体的驱动程序的开发,但一方面我觉得既然驱动程序最终也是为应用层提供服务,所以两者之间的交互是一个必需的也非常重要的过程。
在上一篇文章中使用IOKit相关接口的时,或许你不经意间已经留意到了类似IOServiceOpen的方法,没错,这篇文章正是讨论怎样打开一个枚举到的驱动程序对象而建立连接,但IOKit框架并不会让会应用程序直接与驱动程序连接,而是需要引用一个IOUserClient作为媒介,其原因是因为应用程序与驱动程序并非一对一的关系,驱动程序可能需要同时为多个应用提供服务,而应用程序也可以同时与多个驱动对象交互,所以每一个连接就需要一个对象来进行维护,而这个工作就是由IOUserClient来完成的(大多情况下你会根据需求编写子类),这与面向对象编程的低耦合思想是相符的,为了加深理解这个机制,我们通过下面的关系图可以更加直观的理清它们之间的关系:
![IOUserClient]()
理论就介绍到这儿了,下面就直接开始编码了,我们本篇文章的Demo让驱动程序实现一个四则运算,然后用户程序通过与驱动建立连接,调用接口并得到计算结果。
IOUserClient虽然是应用程序与驱动程序的枢纽,但由于它也属于驱动程序的一个部分,所以应用程序也不能直接通过函数调用的方法使用到IOUserClient,而是通过IOKit框架提供的接口IOConnectCallStructMethod方法,应用程序需要调用什么什么方法,通过指定一个uint64的值传递到内核的IOUserClient,然后选择对应的方法,方法需要的参数和返回值都是通过结构体保存和传递,所以我们需要定义表示不同方法的uint64的值和不同的结构体,而这些定义在应用程序和驱动程序中都需要使用到,所以我们定义在一个公共的头文件Common.h中:
//四则运算
enum CalculateSelectorCode
{
kCalculatePlus,
kCalculateMinus,
kCalculateMultiply,
kCalculateDivided
};
//运算的参数
typedef struct CalculateArguments
{
double va_a;
double va_b;
}CalculateArguments;
//运算的结果
typedef struct CalculateResult
{
double va;
}CalculateResult;
然后我们编写应用程序中的代码:
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#include "Common.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)
{
//service表示需要连接的驱动程序
io_service_t service = driver;
//task表示当前应用程序
task_port_t task = mach_task_self();
//type的含义由开发者选定方式定义(忽略)
uint32_t type = 0;
//connect用于保存这个连接
io_connect_t connect = 0;
//打开连接
IOServiceOpen(service, task, type, &connect);
//设置参数
size_t inputCount = sizeof(CalculateArguments);
CalculateArguments *inputStruct = (CalculateArguments *)calloc(inputCount, 1);
inputStruct->va_a = 4.0;
inputStruct->va_b = 2.0;
size_t outputCount = sizeof(CalculateResult);
CalculateResult *outputStruct = (CalculateResult *)calloc(outputCount, 1);
//加法
IOConnectCallStructMethod(connect, kCalculatePlus, inputStruct, inputCount, &outputStruct, &outputCount);
NSLog(@"%.2f",outputStruct->va);
//减法
IOConnectCallStructMethod(connect, kCalculateMinus, inputStruct, inputCount, &outputStruct, &outputCount);
NSLog(@"%.2f",outputStruct->va);
//乘法
IOConnectCallStructMethod(connect, kCalculateMultiply, inputStruct, inputCount, &outputStruct, &outputCount);
NSLog(@"%.2f",outputStruct->va);
//除法
IOConnectCallStructMethod(connect, kCalculateDivided, inputStruct, inputCount, &outputStruct, &outputCount);
NSLog(@"%.2f",outputStruct->va);
//关闭连接
IOServiceClose(service);
}
return 0;
}
应用程序的代码就已经完成了,下面就需要修改驱动程序的代码了,修改之前编写的驱动程序代码,添加四个方法:
double com_osxkernel_driver_IOKitTest::calculatePlus(double valueA,double valueB)
{
return valueA+valueB;
}
double com_osxkernel_driver_IOKitTest::calculateMinus(double valueA,double valueB)
{
return valueA-valueB;
}
double com_osxkernel_driver_IOKitTest::calculateMultiply(double valueA,double valueB)
{
return valueA*valueB;
}
double com_osxkernel_driver_IOKitTest::calculateDivided(double valueA,double valueB)
{
return valueA/valueB;
}
然后下面就要编写一个自定义的IOUserClient的子类com_osxkernel_driver_IOKitTestUserClient,实现应用程序与驱动程序之间的连接,为了让IOKit框架可以自动识别我们的用户连接类我们需要修改Info.plist文件,并在IOKitPersonalities下的IOKitTest字典中添加IOUserClientClass的值为com_osxkernel_driver_IOKitTestUserClient,另外将前面的公共头拖入到工程中,然后就开始编写代码,下面是com_osxkernel_driver_IOKitTestUserClient的头文件:
#include <IOKit/IOUserClient.h>
#include "IOKitTest.h"
class com_osxkernel_driver_IOKitTestUserClient : public IOUserClient
{
OSDeclareAbstractStructors(com_osxkernel_driver_IOKitTestUserClient)
private:
//保存对应用程序的引用
task_t m_task;
//保存对驱动程序的引用
com_osxkernel_driver_IOKitTest *m_driver;
public:
//由驱动程序调用
virtual bool start(IOService *provider);
//连接初使化
virtual bool initWithTask(task_t owningTask, void * securityToken, UInt32 type,
OSDictionary * properties);
//连接断开或应用程序退出
virtual IOReturn clientClose(void);
//当应用程序调用方法时
virtual IOReturn externalMethod( uint32_t selector, IOExternalMethodArguments * arguments,
IOExternalMethodDispatch * dispatch = 0, OSObject * target = 0, void * reference = 0 );
};
由代码可以看到用户连接类与驱动程序的类结构非常相似,其实IOUserClient也是IOService的一个子类,只不过初使化的方法和结束的方法不太一样,另外一个关键的方法就是externalMethod,具体的一些实现细节就直接看实现文件:
#include "MyUserClient.h"
#include <IOKit/IOLib.h>
#include "Common.h"
#define super IOUserClient
OSDefineMetaClassAndStructors(com_osxkernel_driver_IOKitTestUserClient, IOUserClient)
bool com_osxkernel_driver_IOKitTestUserClient::initWithTask(task_t owningTask,
void *securityToken,
UInt32 type,
OSDictionary *properties)
{
if (!super::initWithTask(owningTask, securityToken, type, properties))
{
return false;
}
IOReturn ret = clientHasPrivilege(securityToken, kIOClientPrivilegeAdministrator);
if (ret == kIOReturnSuccess)
{
//Admin权限
}
//获得应用程序的引用
m_task = owningTask;
return true;
}
bool com_osxkernel_driver_IOKitTestUserClient::start(IOService *provider)
{
if (!super::start(provider))
{
return false;
}
//获得驱动程序的引用
m_driver = OSDynamicCast(com_osxkernel_driver_IOKitTest, provider);
if (!m_driver)
{
return false;
}
return true;
}
IOReturn com_osxkernel_driver_IOKitTestUserClient::clientClose(void)
{
//销毁连接对象
terminate();
return kIOReturnSuccess;
}
IOReturn com_osxkernel_driver_IOKitTestUserClient::externalMethod(uint32_t selector,
IOExternalMethodArguments * arguments,
IOExternalMethodDispatch * dispatch,
OSObject * target,
void * reference)
{
//从参数的结构体中获得值
double valueA = ((CalculateArguments*)arguments->structureInput)->va_a;
double valueB = ((CalculateArguments*)arguments->structureInput)->va_b;
CalculateResult *result = (CalculateResult *)arguments->structureOutput;
//根据应用程序传递的整型值判断执行的方法
switch (selector)
{
case kCalculatePlus:
result->va = m_driver->calculatePlus(valueA, valueB);
break;
case kCalculateMinus:
result->va = m_driver->calculateMinus(valueA, valueB);
break;
case kCalculateMultiply:
result->va = m_driver->calculateMultiply(valueA, valueB);
break;
case kCalculateDivided:
result->va = m_driver->calculateDivided(valueA, valueB);
break;
default:
return kIOReturnUnsupported;
break;
}
return kIOReturnSuccess;
}
好了,代码到这儿就完全结束了,现在编译加载驱动,然后运行应用程序相信就能得到相应的结果了,本文章中的Demo源代码下载:IOKitTest+AppTest_2.zip