macOS 驱动开发(一) -----初探IOKit Drive
关于macOS的驱动程序
Mac OSX系统的驱动开发有一套基于C++的IO Kit框架,这也是OSX内核中非常重要的一个部分,在内核开发中使用C++其实只是它的一个子集,嵌入式C++,它不可以使用C++的异常、多继承、模板、运行时等特性,但IO Kit框架为了开发的方便而去实现了类似Cocoa编程中的引用计数、运行时、容器等特性。
下面用一张图片解释驱动的加载流程
![](https://img.haomeiwen.com/i2011753/81b18cfaad276b56.png)
当硬件插入电脑时,系统会根据硬件的类型创建一个Provider(提供者)的对象,并且这个Provider会在初使化的过程中去尝试匹配合适的驱动程序,如上图,我们开发一款适合于PCI声卡或USB音频设备的驱动程序,首先硬件载入后会去查找驱动程序Info.plist中的IOKitPersonalities的信息,如果IOProviderClass中定义了IOPCIDriver就表示可以为PCI做匹配,如果同时也有IOUSBDriver项就表示USB的设备也可以做匹配,同时如果有多个支持PCI或USB的驱动出现时,就会去调用驱动程序的probe方法,最终找到匹配度最高的驱动程序进行加载(实际的原理会更复杂),然后该驱动程序就可以通过不同的Provider与硬件通信,并且通过标准的IOAudioDriver接口为系统提供音频的服务,从而用户程序就可以通过系统的标准方法最终让音频设备工作。
废话说了也不好, 直接上代码更实在
新建工程选择IOKit Driver模板
![](https://img.haomeiwen.com/i2011753/2d90b8d669c4a810.png)
IOKitTest.hpp
<code>
#include <IOKit/IOService.h>
#ifndef IOKitTest_hpp
#define IOKitTest_hpp
class com_apple_driver_IOKitTest : public IOService {
//一个宏定义,会自动生成该类的构造方法、析构方法和运行时
OSDeclareDefaultStructors(com_apple_driver_IOKitTest);
public:
// 该方法与cocoa中init方法和C++中构造函数类似
virtual bool init(OSDictionary *dict = 0) APPLE_KEXT_OVERRIDE;
// 该方法与cocoa中dealloc方法和c++中析构函数类似
virtual void free(void) APPLE_KEXT_OVERRIDE;
// 进行驱动匹配时调用
virtual IOService *probe(IOService *provider, SInt32 *score) APPLE_KEXT_OVERRIDE;
// 进行加载时调用
virtual bool start(IOService *provider) APPLE_KEXT_OVERRIDE;
// 进行卸载时调用
virtual void stop(IOService *provider) APPLE_KEXT_OVERRIDE;
};
#endif /* IOKitTest_hpp */
</code>
IOKitTest.cpp
#include "IOKitTest.hpp"
#include <IOKit/IOService.h>
#include <IOKit/IOLib.h>
// 类似cocoa中 super关键字
#define super IOService
// 和头文件中的宏定义类似, 自动生成一些特定的代码
OSDefineMetaClassAndStructors(com_apple_driver_IOKitTest, IOService);
bool com_apple_driver_IOKitTest::init(OSDictionary *dict)
{
bool result = super::init(dict);
IOLog("IOKitTest : did init !! \n"); // IOlog() 生成log日志, 存在在system.log里
/** 遍历OSDictionary */
OSCollectionIterator *iter = OSCollectionIterator::withCollection(dict);
if (iter)
{
OSObject *object = NULL;
while ((object = iter->getNextObject()))
{
OSSymbol *key = OSDynamicCast(OSSymbol, object);
IOLog("iRedTest : key:%s ",key->getCStringNoCopy());
OSString *value = OSDynamicCast(OSString, dict->getObject(key));
if (value != NULL)
{
IOLog("iRedTest : value:%s\n",value->getCStringNoCopy());
}
}
}
return result;
}
void com_apple_driver_IOKitTest::free(void)
{
IOLog("IOKitTest : free \n");
super::free();
}
IOService *com_apple_driver_IOKitTest::probe(IOService *provider, SInt32 *score)
{
IOService *probe = super::probe(provider, score);
return probe;
}
bool com_apple_driver_IOKitTest::start(IOService *provider)
{
bool result = super::start(provider);
IOLog("IOKitTest : start \n");
return result;
}
void com_apple_driver_IOKitTest::stop(IOService *provider)
{
IOLog("IOKitTest : stop \n");
super::stop(provider);
}
代码走完一遍, 光有代码还是不够的, 驱动的加载需要声明依赖文件配置在Info.plist里
![](https://img.haomeiwen.com/i2011753/a14e812b25a04723.png)
这些是基本库必要的依赖文件, 其中OSBundleLibraries中的两个值对应的是内核中iokit和libkern的版本,此处我们设置内核的版本即可,而IOKitPersonalities就是上文也有提到的,用于匹配驱动程序用,IOProviderClass是需要对应特定类型的,如IOPCIDriver、IOUSBDriver等,但我们这是一个测试程序,不希望去驱动某个特定的硬件,所以指定IOResources就是无需硬件设备。
为了方便大家 把source code放到下面
<key>IOKitPersonalities</key>
<dict>
<key>IOKitTest</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.driver.IOKitTest</string>
<key>IOClass</key>
<string>com_apple_driver_IOKitTest</string>
<key>IOMatchCategory</key>
<string>com_apple_driver_IOKitTest</string>
<key>IOResourceMatch</key>
<string>IOKit</string>
<key>IOProviderClass</key>
<string>IOResource</string>
<key>IOProbeScore</key>
<integer>10000</integer>
</dict>
</dict>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.iokit</key>
<string>16.1.0</string>
<key>com.apple.kpi.libkern</key>
<string>16.1.0</string>
</dict>
<!-- 内核版本号在终端中 输入 uname -a 查看-->
最后 command + B 编译
现在我们的驱动就开卡完成了 在build下找到IOKitTest.kext文件 复制到 /System/Library/Extensions/ 目录下 不出太多的意外的话 系统会提示你系统扩展不正确
如下图所示:
![](https://img.haomeiwen.com/i2011753/bc3289c439fe7148.png)
这不必要担心 是因为驱动为系统文件 但此时并没有给他系统所需要的权限
打开终端, 输入
$ sudo chown -R root:wheel /System/Library/Extensions/IOKitTest.kext
输入密码
$ sudo touch /System/Library/Extensions // 此步骤不是必须 但不执行次命令需重启Mac
这时在控制台 就会看到 init probe start 三个函数内的log信息
其他的调试命令:
$ sudo kextutil -t .../IOKitTest.kext // 检测kext文件错误信息
$ sudo kextload .../IOKitTest.kext // 加载kext文件
$ sudo kextunload ../IOKitTest.kext // 写在kext文件 // 可在控制台中看到free stop 函数中的log信息
$ sudo kextstat // 查看已经加载的内核程序 (可配合其他Linux命令使用 例如: | grep)
注:
在macOS 10.11 之后 默认不允许加载未签名的内核程序 这也是OS X 10.11 的新特性 名为 :rootless
关闭rootless方式如下:
- 重启电脑
- 开机按住 command + R 进入安全模式
- 打开终端
- 输入
$ csrutil disable // 关闭rootle
$ csrutil able // 打开rootless
- 重启电脑