前面已经讲解了基于KernControl API的通信实现,该实现相对简单,但有一些缺点。内核向应用层传输消息需调用ctl_enqueuedata接口,该接口实际将数据缓存至缓冲区,当需要瞬时大量传输消息时,缓冲区容量有限,将丢弃后来的数据。如果传输的数据有优先级,则需对ctl_enqueuedata接口进行二次封装,避免高优先级数据丢失。
IOKit Fundamentals 框架提供更加全面且方便的内核驱动API。IOService是大多数内核驱动的基类,提供驱动实例化相关的各项服务。IOUserClient是与用户态应用程序间通信的基类,通过继承并实现该类,可与客户端应用程序建立通信机制。IOSharedDataQueue是非常便于使用的内核与用户态进程进行数据交换的通用队列,用户可自行设置队列大小。基于IOKit中的IOService、IOUserClient、IOSharedDataQueue可方便的实现内核与用户进程的通信和数据传输。下面结合代码进行简单实现,更多代码细节和工程配置可参考NuwaStone项目。
IOService内核编程
由于本次编写的为内核拓展,仅需要IOService进行驱动的加载与卸载管理,这里的代码实现很简单,仅需重写start、stop方法。如有加载卸载时的自定义操作可在函数中实现。
DriverService.hpp
class DriverService : public IOService {
OSDeclareDefaultStructors(DriverService);
public:
// Called by the kernel when the kext is loaded
bool start(IOService *provider) override;
// Called by the kernel when the kext is unloaded
void stop(IOService *provider) override;
private:
void clearInstances();
};
IOUserClient内核编程
编写继承于IOUserClient的类后需重写如下方法,相关源文件实现请参考开源项目。registerNotificationPort和clientMemoryForType用于数据交换队列的配置,externalMethod配置对外函数调用接口,对外接口的函数原型如callYourMethod定义。
DriverClient.hpp
class DriverClient : public IOUserClient {
OSDeclareDefaultStructors(DriverClient);
public:
// Called as part of IOServiceOpen in clients.
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
// Called after initWithTask as part of IOServiceOpen.
bool start(IOService *provider) override;
// Called when this class is stopping.
void stop(IOService *provider) override;
// Called when a client manually disconnects (via IOServiceClose).
IOReturn clientClose(void) override;
// Called when a client dies.
IOReturn clientDied(void) override;
// Called during termination.
bool didTerminate(IOService *provider, IOOptionBits options, bool *defer) override;
// Called in clients with IOConnectSetNotificationPort. 用于数据传输
IOReturn registerNotificationPort(mach_port_t port, UInt32 type, UInt32 refCon) override;
// Called in clients with IOConnectMapMemory. 用于数据传输
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;
// Called in clients with IOConnectCallScalarMethod. 设置对外通信调用接口
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments, IOExternalMethodDispatch *dispatch, OSObject *target, void *reference) override;
// 自定义对外调用方法
static IOReturn callYourMethod(OSObject *target, void *reference, IOExternalMethodArguments *arguments);
};
IOKit客户端编程
连接内核拓展前需先进行加载,加载调用KextManagerLoadKextWithIdentifier或KextManagerLoadKextWithURL即可。内核拓展需在plist中配置IOService及IOUserClient类名,在拓展启动后可通过类名进行查找匹配。首先需查找注册了指定类名的内核驱动,代码如下:
func startProvider() -> Bool {
guard let service = IOServiceMatching("your service name") else {
return false
}
Logger(.Info, "Wait for kext to be connected.")
waitForDriver(matchingDict: service)
return true
}
service存放匹配成功的驱动字典,然后需要创建通信端口和处理队列进行处理连接请求。处理连接请求时所持有的IOService对象需注意释放。调用IOServiceOpen接口即可建立连接,后面的IOConnectCallScalarMethod表示调用驱动对外接口进行连接测试。函数返回前需将用于连接请求处理的端口释放。通过IOConnectCallScalarMethod或IOConnectCallStructMethod可调用驱动对外接口,其中ScalarMethod仅可传输有限数量的常量,StructMethod则可传输自定义结构体类型,相关驱动配置可参照NuwaStone项目。
func processConnectionRequest(iterator: io_iterator_t) {
repeat {
// 持有的对象需进行释放
let nextService = IOIteratorNext(iterator)
guard nextService != 0 else {
break
}
// 建立与内核驱动的连接
var result = IOServiceOpen(nextService, mach_task_self_, 0, &connection)
if result != kIOReturnSuccess {
Logger(.Error, "Failed to open kext service [\(String.init(format: "0x%x", result))].")
IOObjectRelease(nextService)
break
}
// 调用驱动方法测试连接
result = IOConnectCallScalarMethod(connection, kNuwaUserClientOpen.rawValue, nil, 0, nil, nil)
if result != kIOReturnSuccess {
Logger(.Error, "An error occurred while opening the connection [\(result)].")
IOObjectRelease(nextService)
break
}
IOObjectRelease(nextService)
IONotificationPortDestroy(notificationPort)
isConnected = true
Logger(.Info, "Connected with kext successfully.")
} while true
}
func waitForDriver(matchingDict: CFDictionary) {
var iterator: io_iterator_t = 0
let selfPointer = Unmanaged.passUnretained(self).toOpaque()
let notificationQueue = DispatchQueue(label: "your queue name")
let appearedCallback: IOServiceMatchingCallback = { refcon, iterator in
let selfPtr = Unmanaged<YourClassName>.fromOpaque(refcon!).takeUnretainedValue()
selfPtr.processConnectionRequest(iterator: iterator)
}
notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
IONotificationPortSetDispatchQueue(notificationPort, notificationQueue)
IOServiceAddMatchingNotification(notificationPort, kIOMatchedNotification, matchingDict, appearedCallback, selfPointer, &iterator)
processConnectionRequest(iterator: iterator)
}
我们通过调用IOServiceOpen存放io_connect_t类型的对象建立了与内核驱动的连接,相应的,断开连接时需调用IOServiceClose接口。
func stopProvider() -> Bool {
let result = IOServiceClose(connection)
if result != KERN_SUCCESS {
Logger(.Error, "Failed to close IOService [\(String.init(format: "0x%x", result))].")
return false
}
connection = IO_OBJECT_NULL
isConnected = false
return true
}