首页投稿(暂停使用,暂停投稿)MacMacOS开发 技术集锦

macOS 驱动开发(一) -----初探IOKit Drive

2016-12-27  本文已影响4177人  陌_Carl

关于macOS的驱动程序
Mac OSX系统的驱动开发有一套基于C++的IO Kit框架,这也是OSX内核中非常重要的一个部分,在内核开发中使用C++其实只是它的一个子集,嵌入式C++,它不可以使用C++的异常、多继承、模板、运行时等特性,但IO Kit框架为了开发的方便而去实现了类似Cocoa编程中的引用计数、运行时、容器等特性。
下面用一张图片解释驱动的加载流程

driver_01.png

当硬件插入电脑时,系统会根据硬件的类型创建一个Provider(提供者)的对象,并且这个Provider会在初使化的过程中去尝试匹配合适的驱动程序,如上图,我们开发一款适合于PCI声卡或USB音频设备的驱动程序,首先硬件载入后会去查找驱动程序Info.plist中的IOKitPersonalities的信息,如果IOProviderClass中定义了IOPCIDriver就表示可以为PCI做匹配,如果同时也有IOUSBDriver项就表示USB的设备也可以做匹配,同时如果有多个支持PCI或USB的驱动出现时,就会去调用驱动程序的probe方法,最终找到匹配度最高的驱动程序进行加载(实际的原理会更复杂),然后该驱动程序就可以通过不同的Provider与硬件通信,并且通过标准的IOAudioDriver接口为系统提供音频的服务,从而用户程序就可以通过系统的标准方法最终让音频设备工作。

废话说了也不好, 直接上代码更实在
新建工程选择IOKit Driver模板

Snip20161227_3.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里

图片.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/ 目录下 不出太多的意外的话 系统会提示你系统扩展不正确
如下图所示:

Snip20161227_7.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方式如下:

  1. 重启电脑
  2. 开机按住 command + R 进入安全模式
  3. 打开终端
  4. 输入
$ csrutil disable // 关闭rootle
$ csrutil able // 打开rootless
  1. 重启电脑

最后附上代码的飞机票: IOKitTest.kext

上一篇 下一篇

猜你喜欢

热点阅读