前面已經(jīng)講解了基于KernControl API的通信實現(xiàn)了嚎,該實現(xiàn)相對簡單舌镶,但有一些缺點。內(nèi)核向應(yīng)用層傳輸消息需調(diào)用ctl_enqueuedata接口,該接口實際將數(shù)據(jù)緩存至緩沖區(qū)期犬,當(dāng)需要瞬時大量傳輸消息時,緩沖區(qū)容量有限,將丟棄后來的數(shù)據(jù)丹墨。如果傳輸?shù)臄?shù)據(jù)有優(yōu)先級,則需對ctl_enqueuedata接口進(jìn)行二次封裝嬉愧,避免高優(yōu)先級數(shù)據(jù)丟失贩挣。
IOKit Fundamentals 框架提供更加全面且方便的內(nèi)核驅(qū)動API。IOService是大多數(shù)內(nèi)核驅(qū)動的基類没酣,提供驅(qū)動實例化相關(guān)的各項服務(wù)王财。IOUserClient是與用戶態(tài)應(yīng)用程序間通信的基類,通過繼承并實現(xiàn)該類裕便,可與客戶端應(yīng)用程序建立通信機(jī)制绒净。IOSharedDataQueue是非常便于使用的內(nèi)核與用戶態(tài)進(jìn)程進(jìn)行數(shù)據(jù)交換的通用隊列,用戶可自行設(shè)置隊列大小偿衰」医基于IOKit中的IOService、IOUserClient下翎、IOSharedDataQueue可方便的實現(xiàn)內(nèi)核與用戶進(jìn)程的通信和數(shù)據(jù)傳輸缤言。下面結(jié)合代碼進(jìn)行簡單實現(xiàn),更多代碼細(xì)節(jié)和工程配置可參考NuwaStone項目漏设。
IOService內(nèi)核編程
由于本次編寫的為內(nèi)核拓展墨闲,僅需要IOService進(jìn)行驅(qū)動的加載與卸載管理,這里的代碼實現(xiàn)很簡單郑口,僅需重寫start鸳碧、stop方法盾鳞。如有加載卸載時的自定義操作可在函數(shù)中實現(xiàn)。
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內(nèi)核編程
編寫繼承于IOUserClient的類后需重寫如下方法瞻离,相關(guān)源文件實現(xiàn)請參考開源項目腾仅。registerNotificationPort和clientMemoryForType用于數(shù)據(jù)交換隊列的配置,externalMethod配置對外函數(shù)調(diào)用接口套利,對外接口的函數(shù)原型如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. 用于數(shù)據(jù)傳輸
IOReturn registerNotificationPort(mach_port_t port, UInt32 type, UInt32 refCon) override;
// Called in clients with IOConnectMapMemory. 用于數(shù)據(jù)傳輸
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;
// Called in clients with IOConnectCallScalarMethod. 設(shè)置對外通信調(diào)用接口
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments, IOExternalMethodDispatch *dispatch, OSObject *target, void *reference) override;
// 自定義對外調(diào)用方法
static IOReturn callYourMethod(OSObject *target, void *reference, IOExternalMethodArguments *arguments);
};
IOKit客戶端編程
連接內(nèi)核拓展前需先進(jìn)行加載,加載調(diào)用KextManagerLoadKextWithIdentifier或KextManagerLoadKextWithURL即可肉迫。內(nèi)核拓展需在plist中配置IOService及IOUserClient類名验辞,在拓展啟動后可通過類名進(jìn)行查找匹配。首先需查找注冊了指定類名的內(nèi)核驅(qū)動喊衫,代碼如下:
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存放匹配成功的驅(qū)動字典跌造,然后需要創(chuàng)建通信端口和處理隊列進(jìn)行處理連接請求。處理連接請求時所持有的IOService對象需注意釋放族购。調(diào)用IOServiceOpen接口即可建立連接壳贪,后面的IOConnectCallScalarMethod表示調(diào)用驅(qū)動對外接口進(jìn)行連接測試。函數(shù)返回前需將用于連接請求處理的端口釋放寝杖。通過IOConnectCallScalarMethod或IOConnectCallStructMethod可調(diào)用驅(qū)動對外接口违施,其中ScalarMethod僅可傳輸有限數(shù)量的常量,StructMethod則可傳輸自定義結(jié)構(gòu)體類型瑟幕,相關(guān)驅(qū)動配置可參照NuwaStone項目磕蒲。
func processConnectionRequest(iterator: io_iterator_t) {
repeat {
// 持有的對象需進(jìn)行釋放
let nextService = IOIteratorNext(iterator)
guard nextService != 0 else {
break
}
// 建立與內(nèi)核驅(qū)動的連接
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
}
// 調(diào)用驅(qū)動方法測試連接
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)
}
我們通過調(diào)用IOServiceOpen存放io_connect_t類型的對象建立了與內(nèi)核驅(qū)動的連接颤殴,相應(yīng)的笑跛,斷開連接時需調(diào)用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
}