Mac OS X Internals: A Systems Approach

10.6. Creating Kernel Extensions

In this section, we will discuss how kernel extensions are created and loaded. Note that Apple urges third-party programmers to avoid programming in the kernel unless absolutely necessary. A legitimate reason for a device driver to reside in the kernel is if it handles a primary interrupt or if its primary client is kernel resident.

10.6.1. A Generic Kernel Extension

Let us create a trivial kextone that implements only the start and stop entry points. Once we can compile and load the kext, we will extend it to implement a couple of sysctl entries. We will call our kext DummySysctl.

We begin with an Xcode project instantiated from the template for generic kernel extensions. Since sysctl implementation is a BSD-only endeavor, we need to specify our kext's dependency on the BSD KPI. We will use the kernel version of the printf() function to print messages in our kext. Therefore, we need libkern, which provides printf(). The key contents of the kext's Info.plist file are as follows:

... <plist version="1.0"> <dict> ... <key>CFBundleExecutable</key> <string>DummySysctl</string> <key>CFBundleIdentifier</key> <string>com.osxbook.kext.DummySysctl</string> ... <key>OSBundleLibraries</key> <dict> <key>com.apple.kpi.bsd</key> <string>8.0.0</string> <key>com.apple.kpi.libkern</key> <string>8.0.0</string> </dict> </dict> </plist>

At least certain values in the stock Info.plist file generated by Xcode must be modified or added before the compiled kext can be successfully loaded. The CFBundleIdentifier key should be set to a reverse-DNS-style name for the kext. The OSBundleLibraries key is used to enumerate a kext's dependencies. This key's value is a dictionaryspecifically, an OSDictionarythat has been serialized into the XML property list format. It may contain an empty dictionary. Beginning with Mac OS X 10.4, a kext can declare dependencies either on new-style kernel programming interfaces (KPIs) or on compatibility interfaces. KPI dependencies are specified through com.apple.kpi.* identifiers, whereas the others are specified through com.apple.kernel.* identifiers. The former start from version 8.0.0 (Mac OS X 10.4 and newer), whereas the latter end at version 7.9.9 (Mac OS X 10.3 and older). The kextstat command can be used to list the interfaces available in the current kernelthe interfaces correspond to the "fake" kexts that represent built-in kernel components such as Mach, BSD, libkern, and the I/O Kit.

A kext can also declare a dependency on a specific version of the entire kernel by using the com.apple.kernel identifier. Although this approach would provide the kext access to all available kernel interfaces, including internal ones, it is not recommended because Apple does not guarantee binary compatibility across kernel versions to such kexts.

$ kextstat | egrep -e 'com.apple.(kernel|kpi)' 1 1 0x0 0x0 0x0 com.apple.kernel (8.6.0) 2 11 0x0 0x0 0x0 com.apple.kpi.bsd (8.6.0) 3 12 0x0 0x0 0x0 com.apple.kpi.iokit (8.6.0) 4 12 0x0 0x0 0x0 com.apple.kpi.libkern (8.6.0) 5 12 0x0 0x0 0x0 com.apple.kpi.mach (8.6.0) 6 10 0x0 0x0 0x0 com.apple.kpi.unsupported (8.6.0) 11 60 0x0 0x0 0x0 com.apple.kernel.6.0 (7.9.9) 12 1 0x0 0x0 0x0 com.apple.kernel.bsd (7.9.9) 13 1 0x0 0x0 0x0 com.apple.kernel.iokit (7.9.9) 14 1 0x0 0x0 0x0 com.apple.kernel.libkern (7.9.9) 15 1 0x0 0x0 0x0 com.apple.kernel.mach (7.9.9)

You can view the list of symbols corresponding to a KPI identifier by running nm on the corresponding "pseudo-extension"the various pseudo-extensions reside as plug-ins within the System kext.

$ cd /System/Library/Extensions/System.kext/PlugIns $ ls AppleNMI.kext IOSystemManagement.kext ApplePlatformFamily.kext Libkern.kext BSDKernel.kext Libkern6.0.kext BSDKernel6.0.kext Mach.kext IOKit.kext Mach6.0.kext IOKit6.0.kext System6.0.kext IONVRAMFamily.kext Unsupported.kext $ nm BSDKernel.kext/BSDKernel ... U _vnode_iterate U _vnode_lookup U _vnode_mount ...

Besides core kernel components, kexts can depend on various I/O Kit families by using identifiers such as com.apple.iokit.IOGraphicsFamily, com.apple.iokit.IONetworkingFamily, com.apple.iokit.IOPCIFamily, and com.apple.iokit.IOStorageFamily.

Figure 1013 shows the source for the kext. The start function is called when the kext is loaded, and the stop function is called when it is unloaded. Xcode inserts skeletal implementations of these functions in the automatically generated C file for the Generic Kernel Extension project template. We have added a printf() statement to both functions.

Figure 1013. Source for the DummySysctl kernel extension

// DummySysctl.c #include <mach/mach_types.h> kern_return_t DummySysctl_start(kmod_info_t *ki, void *d) { printf("DummySysctl_start\n"); return KERN_SUCCESS; } kern_return_t DummySysctl_stop(kmod_info_t *ki, void *d) { printf("DummySysctl_stop\n"); return KERN_SUCCESS; }

Let us now compile the kext. The benefits of Xcode are most evident in the compilation stage, since a manual compilation would need to specify the appropriate combination of compiler arguments, environment variables, linker arguments, and so on. Note that it is possible to initiate an Xcode build from the command line using the xcodebuild program. On successful compilation, the target kext bundle is created in a subdirectory of the build/ directory within the Xcode project directory.

$ xcodebuild -list Information about project "DummySysctl": Targets: DummySysctl (Active) Build Configurations: Debug (Active) Release If no build configuration is specified "Release" is used. $ xcodebuild -configuration Debug -target DummySysctl === BUILDING NATIVE TARGET DummySysctl WITH CONFIGURATION Debug === ... ** BUILD SUCCEEDED ** $ ls build/Debug DummySysctl.kext

Since loading a kext requires the kext bundle's contents to have root and wheel as the owner and group, respectively, a typical compile-test-debug cycle would involve copying the kext bundle from the build directory to a temporary locationsay, to /tmp/and using the chown command on the copy. As we noted earlier, besides ownership, the modifiability of objects within the bundle also mattersthe bundle's contents must not be writable by any user except root.

$ sudo rm -rf /tmp/DummySysctl.kext # remove any old bundles $ cp -pr build/DummySysctl.kext /tmp/ # copy newly compiled bundle to /tmp $ sudo chown -R root:wheel /tmp/DummySysctl.kext

We can use kextload to load the kext manually.

$ sudo kextload -v /tmp/DummySysctl.kext kextload: extension /tmp/DummySysctl.kext appears to be valid kextload: loading extension /tmp/DummySysctl.kext kextload: sending 1 personality to the kernel kextload: /tmp/DummySysctl.kext loaded successfully

If the kext fails to load, the -t (test) option of kextload may provide information about possible problems. For example, suppose we specified an unavailable version of a dependencysay, version 7.9.9 for com.apple.kpi.libkernthen the -t option would be helpful in identifying the cause of the problem.

$ sudo kextload -v /tmp/DummySysctl.kext kextload: extension /tmp/DummySysctl.kext appears to be valid kextload: loading extension /tmp/DummySysctl.kext kextload: cannot resolve dependencies for kernel extension /tmp/DummySysctl.kext $ sudo sysctl -v -t /tmp/DummySysctl.kext ... kernel extension /tmp/DummySysctl.kext has problems: ... Missing dependencies { "com.apple.kpi.libkern" = "A valid compatible version of this dependency cannot be found" }

When the -t option is specified, kextload neither loads the kext nor sends its personality to the kernel. It only performs a series of tests on the kext and determines whether it is loadable. The tests include validation, authentication, and dependency resolution.

Besides dependency resolution failures, other reasons for a kext's failure to load include incorrect file permissions, a flawed bundle structure, a missing CFBundleIdentifier property in the kext's Info.plist file, and a missing or syntactically invalid Info.plist file.

We can use the kextstat command to check whether our kext is currently loaded in the kernel.

$ kextstat Index Refs Address Size Wired Name (Version) <Linked Against> 1 1 0x0 0x0 0x0 com.apple.kernel (8.6.0) 2 11 0x0 0x0 0x0 com.apple.kpi.bsd (8.6.0) 3 12 0x0 0x0 0x0 com.apple.kpi.iokit (8.6.0) 4 12 0x0 0x0 0x0 com.apple.kpi.libkern (8.6.0) ... 133 0 0x5cbca000 0x2000 0x1000 com.osxbook.kext.DummySysctl (1.0.0d1) <4 2>

The value 133 in the kextstat output indicates the index at which the kext is loaded. The kernel uses these indices for tracking interkext dependencies. The second value, which is 0 in our case, shows the number of references to this kext. A nonzero reference indicates that one or more kexts are using this kext. The next value, 0x5cbca000, is the kext's load address in the kernel's virtual address space. The next two values, 0x2000 and 0x1000, represent the amounts (in bytes) of kernel memory and wired kernel memory, respectively, used by the kext. The final value in the column is a list of indices of all other kexts that this kext references. We see that DummySysctl references two kexts: the ones loaded at indices 4 (com.apple.kpi.libkern) and 2 (com.apple.kpi.bsd).

We can unload the kext manually by using the kextunload command.

$ sudo kextunload -v /tmp/DummySysctl.kext kextunload: unload kext /tmp/DummySysctl.kext succeeded

In a deployment scenario, one does not have to run kextload or kextunload manuallykexts are loaded automatically when they are needed and unloaded when they are not being used.

The output from the printf() statements we inserted in our kext should appear in /var/log/system.log.

$ grep DummySysctl_ /var/log/system.log Mar 14 17:32:48 g5x4 kernel[0]: DummySysctl_start Mar 14 17:34:48 g5x4 kernel[0]: DummySysctl_stop

10.6.2. Implementing Sysctl Variables Using a Generic Kext

Let us extend our generic kext from Section 10.6.1 to implement a sysctl node with two variables: an integer and a string. We will call the new node osxbook, and it will have the following properties.

  • It will be a subcategory of the existing top-level sysctl node called debug. In other words, the new node's MIB-style name would be debug.osxbook.

  • One of its two children will be called uint32, which will hold a 32-bit unsigned integer. The integer's value will be readable or writable by any user.

  • The second of its children will be called string, which will hold a string up to 16 characters in length (including the terminating NUL character). The string's value will be readable by anyone but writable only by the root user.

When our sysctl kext is loaded, the kernel's sysctl hierarchy would look like the one shown in Figure 1014, with possibly other top-level categories depending on the kernel version.

Figure 1014. The kernel's sysctl hierarchy

The most general way to create a sysctl variable is to use the SYSCTL_PROC() macro, which allows a handler function to be specified for the sysctl. The handler is called when the variable is accessed for reading or writing. There exist data-type-specific macros such as SYSCTL_UINT() for unsigned integers and SYSCTL_STRING() for strings. The sysctls defined using these macros are served by predefined type-specific functions such as sysctl_handle_int() and sysctl_handle_string(). We will use SYSCTL_PROC() to define our sysctl variables, with our own handler functions, although we will simply call the predefined handlers from our handlers. Figure 1015 shows the updated contents of DummySysctl.c. Note that we register the three sysctl entriesdebug.osxbook, debug.osxbook.uint32, and debug.osxbook.stringin the kext's start routine and unregister them in the stop routine.

Figure 1015. Implementing sysctl nodes

// DummySysctl.c #include <sys/systm.h> #include <sys/types.h> #include <sys/sysctl.h> static u_int32_t k_uint32 = 0; // the contents of debug.osxbook.uint32 static u_int8_t k_string[16] = { 0 }; // the contents of debug.osxbook.string // Construct a node (debug.osxbook) from which other sysctl objects can hang. SYSCTL_NODE(_debug, // our parent OID_AUTO, // automatically assign us an object ID osxbook, // our name CTLFLAG_RW, // we will be creating children, therefore, read/write 0, // handler function (none needed) "demo sysctl hierarchy"); // Prototypes for read/write handling functions for our sysctl nodes. static int sysctl_osxbook_uint32 SYSCTL_HANDLER_ARGS; static int sysctl_osxbook_string SYSCTL_HANDLER_ARGS; // We can directly use SYSCTL_INT(), in which case sysctl_handle_int() // will be assigned as the handling function. We use SYSCTL_PROC() and // specify our own handler sysctl_osxbook_uint32(). // SYSCTL_PROC( _debug_osxbook, // our parent OID_AUTO, // automatically assign us an object ID uint32, // our name (CTLTYPE_INT | // type flag CTLFLAG_RW | CTLFLAG_ANYBODY), // access flags (read/write by anybody) &k_uint32, // location of our data 0, // argument passed to our handler sysctl_osxbook_uint32, // our handler function "IU", // our data type (unsigned integer) "32-bit unsigned integer" // our description ); // We can directly use SYSCTL_STRING(), in which case sysctl_handle_string() // will be assigned as the handling function. We use SYSCTL_PROC() and // specify our own handler sysctl_osxbook_string(). // SYSCTL_PROC( _debug_osxbook, // our parent OID_AUTO, // automatically assign us an object ID string, // our name (CTLTYPE_STRING | CTLFLAG_RW), // type and access flags (write only by root) &k_string, // location of our data 16, // maximum allowable length of the string sysctl_osxbook_string, // our handler function "A", // our data type (string) "16-byte string" // our description ); static int sysctl_osxbook_uint32 SYSCTL_HANDLER_ARGS { // Do some processing of our own, if necessary. return sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); } static int sysctl_osxbook_string SYSCTL_HANDLER_ARGS { // Do some processing of our own, if necessary. return sysctl_handle_string(oidp, oidp->oid_arg1, oidp->oid_arg2, req); } kern_return_t DummySysctl_start(kmod_info_t *ki, void *d) { // Register our sysctl entries. sysctl_register_oid(&sysctl__debug_osxbook); sysctl_register_oid(&sysctl__debug_osxbook_uint32); sysctl_register_oid(&sysctl__debug_osxbook_string); return KERN_SUCCESS; } kern_return_t DummySysctl_stop(kmod_info_t *ki, void *d) { // Unregister our sysctl entries. sysctl_unregister_oid(&sysctl__debug_osxbook_string); sysctl_unregister_oid(&sysctl__debug_osxbook_uint32); sysctl_unregister_oid(&sysctl__debug_osxbook); return KERN_SUCCESS; }

Let us compile and load the kext to test it. Once it is loaded, the sysctl command can be used to get and set the values of our sysctl variables.

$ sysctl debug ... debug.osxbook.uint32: 0 debug.osxbook.string: $ sysctl -w debug.osxbook.uint32=64 debug.osxbook.uint32: 0 -> 64 $ sysctl debug.osxbook.uint32 debug.osxbook.uint32: 64 $ sysctl -w debug.osxbook.string=kernel debug.osxbook.string: sysctl: debug.osxbook.string: Operation not permitted $ sudo sysctl -w debug.osxbook.string=kernel debug.osxbook.string: -> kernel $ sysctl debug.osxbook.string debug.osxbook.string: kernel

10.6.3. I/O Kit Device Driver Kext

As we saw in Section 10.4.2, an I/O Kit driver is a kext that uses C++ in its implementationit runs in the kernel's C++ runtime environment provided by the I/O Kit. The Info.plist file of an I/O Kit driver kext contains one or more driver personality dictionaries. Moreover, unlike a generic kext, the driver implementor does not provide start/stop routines for the kext because, as we saw earlier, these routines are used as hooks to initialize/terminate the C++ runtime. However, an I/O Kit driver has several other entry points. Depending on the type and nature of the driver, many of these entry points can be optional or mandatory. The following are examples of I/O Kit driver entry points.

  • init() During active matching, the I/O Kit loads a candidate driver's code and creates an instance of the driver's principal class, which is specified in the driver's personality. The first method called on each instance of the driver's class is init(), which may be semantically seen as a constructor and may allocate resources needed by the instance. It is passed a dictionary object containing the matching properties from the selected driver personality. A driver may or may not override the init() method. If it does, it must call the superclass's init() as the first action.

  • free() The free() method is called when the driver is unloaded. It should free any resources allocated by init(). If init() calls the superclass's init(), free() must make a corresponding call to the superclass's free(). Note that unlike the start and stop entry points of a kmod, init() and free() are called once for each instance of the driver's class.

  • probe() This method is called to probe the hardware to determine whether the driver is suited for that hardware. It must leave the hardware in a sane state after it has finished probing. It takes as arguments the driver's provider and a pointer to a probe scorea signed 32-bit integer initialized to the value of the IOProbeScore key in the driver's personality, or to zero if the personality does not contain this key. The I/O Kit gives the driver with the highest probe score the first opportunity to drive the hardware.

  • start() This is the actual starting point of the driver's lifecycle. Here the driver advertises its services and publishes any nubs. If start() returns successfully, it means that the device hardware has been initialized and is ready for operation. Thereafter, the I/O Kit will not consider any remaining candidate driver instances.

  • stop() This method represents the end point of the driver's lifecycle. Here the driver unpublishes any nubs and stops providing its services.

  • attach() This method attaches the driver (an IOService client) to a provider (nub) through registration in the I/O Registry. In other words, when called in a driver, it enters the driver in the I/O Registry as a child of the provider in the Service plane.

  • detach() This method detaches the driver from a nub.

Let us implement an I/O Kit driver kext by starting with the IOKit Driver Xcode template. We will call our driver DummyDriver. Xcode will generate a header file (DummyDriver.h), a C++ source file (DummyDriver.cpp), and an Info.plist file, besides other project-related files. As we will see, it takes somewhat more work to create a trivial I/O Kit driver than a trivial generic kext.

Figure 1016 shows the key contents of the driver's property list file.

Figure 1016. An I/O Kit driver's personality and dependencies

... <dict> ... <key>IOKitPersonalities</key> <dict> <key>DummyPersonality_0</key> <dict> <key>CFBundleIdentifier</key> <string>com.osxbook.driver.DummyDriver</string> <key>IOClass</key> <string>com_osxbook_driver_DummyDriver</string> <key>IOKitDebug</key> <integer>65535</integer> <key>IOMatchCategory</key> <string>DummyDriver</string> <key>IOProviderClass</key> <string>IOResources</string> <key>IOResourceMatch</key> <string>IOKit</string> </dict> </dict> <key>OSBundleLibraries</key> <dict> <key>com.apple.kpi.iokit</key> <string>8.0.0</string> <key>com.apple.kpi.libkern</key> <string>8.0.0</string> </dict> </dict> </plist>

As in the case of DummySysctl, we identify our driver kext using a reverse-DNS-style name. The IOKitPersonalities property, which is an array of personality dictionaries, contains properties for matching and loading the driver. Our driver contains only one personality called DummyPersonality_0.

The IOClass key in the personality specifies the name of the driver's primary class, based on the driver kext's bundle identifier for uniformity. Note, however, that the name of the driver class cannot contain dots. Therefore, we translate dots to underscores while naming our driver's class.

DummyDriver neither controls any hardware nor implements any real functionality. In order for it to load and match successfully, we specify its IOProviderClass property as IOResources, which is a special nub that can be matched against any driver. Note that it is possible for real drivers that do not attach to any hardware to match against IOResources. The BootCache kext (BootCache.kext)[9] is an exampleit operates within the Mach and BSD portions of the kernel but implements minimal glue code for the I/O Kit. It also specifies IOResources as its provider class. Figure 1017 shows the personality specification from the BootCache kext's Info.plist file.

[9] We discussed BootCache in Section 4.14.

Figure 1017. The driver personality of the BootCache kernel extension

... <key>IOKitPersonalities</key> <dict> <key>BootCache</key> <dict> <key>CFBundleIdentifier</key> <string>com.apple.BootCache</string> <key>IOClass</key> <string>com_apple_BootCache</string> <key>IOMatchCategory</key> <string>BootCache</string> <key>IOProviderClass</key> <string>IOResources</string> <key>IOResourceMatch</key> <string>IOKit</string> </dict> </dict> ...

IOMatchCategory is a special property that allows multiple drivers to match a single nub. Examples of kexts whose driver personalities specify this property include AppleRAID, IOFireWireFamily, IOGraphicsFamily, IOSerialFamily, IOStorageFamily, and IOUSBFamily. Note that a kext that specifies an IOResourceMatch property is eligible for loading after the subsystem named by the value of IOResourceMatch has been published as available.

The iPod Driver

The iPod driver kext (iPodDriver.kext) is an example of a driver containing multiple personalities. Figure 1018 shows an excerpt from the iPod driver's Info.plist file.

Figure 1018. An excerpt from the iPod driver's property list file

... <key>IOKitPersonalities</key> <dict> <key>iPodDriver</key> <dict> <key>CFBundleIdentifier</key> <string>com.apple.driver.iPodDriver</string> <key>IOClass</key> <string>com_apple_driver_iPod</string> <key>IOProviderClass</key> <string>IOSCSIPeripheralDeviceNub</string> <key>Peripheral Device Type</key> <integer>14</integer> <key>Product Identification</key> <string>iPod</string> <key>Vendor Identification</key> <string>Apple</string> </dict> <key>iPodDriverIniter</key> <dict> <key>CFBundleIdentifier</key> <string>com.apple.iokit.SCSITaskUserClient</string> <key>IOClass</key> <string>SCSITaskUserClientIniter</string> ... <key>IOProviderClass</key> <string>com_apple_driver_iPodNub</string> ... </dict> </dict> ...

The base class for most I/O Kit families and drivers is IOService. We will also subclass IOService to implement com_osxbook_driver_DummyDriver. Our driver's source references two macros that are defined by the I/O Kit.

  • OSDeclareDefaultStructors() declares C++ constructors and is conventionally inserted as the first element of the class declaration in a driver header file.

  • OSDefineMetaClassAndStructors(), which is used in the driver's class implementation, defines the constructors and destructors, implements the OSMetaClass allocation member function for the class, and supplies the metaclass RTTI information for the RTTI system.

In general, all subclasses of OSObject use these macros or variants of them.

We implement several class methods in our dummy driver to examine when and in which order they are called. However, we need not implement any logic in these methodswe can simply log a message and forward the invocation to the corresponding superclass method.

Figure 1019 shows the contents of DummyDriver.h.

Figure 1019. Header file for the DummyDriver I/O Kit driver

// DummyDriver.h #include <IOKit/IOService.h> class com_osxbook_driver_DummyDriver : public IOService { OSDeclareDefaultStructors(com_osxbook_driver_DummyDriver) public: virtual bool init(OSDictionary *dictionary = 0); virtual void free(void); virtual bool attach(IOService *provider); virtual IOService *probe(IOService *provider, SInt32 *score); virtual void detach(IOService *provider); virtual bool start(IOService *provider); virtual void stop(IOService *provider); };

Figure 1020 shows the contents of DummyDriver.cpp. Note how the OSDefineMetaClassAndStructors() macro is used: The first argument is the literal name of the driver's class (the same as the value of the IOClass property in the personality), and the second argument is the literal name of the driver's superclass.

Figure 1020. Implementation of the DummyDriver I/O Kit driver's class

// DummyDriver.cpp #include <IOKit/IOLib.h> #include "DummyDriver.h" #define super IOService OSDefineMetaClassAndStructors(com_osxbook_driver_DummyDriver, IOService) bool com_osxbook_driver_DummyDriver::init(OSDictionary *dict) { bool result = super::init(dict); IOLog("init\n"); return result; } void com_osxbook_driver_DummyDriver::free(void) { IOLog("free\n"); super::free(); } IOService * com_osxbook_driver_DummyDriver::probe(IOService *provider, SInt32 *score) { IOService *result = super::probe(provider, score); IOLog("probe\n"); return result; } bool com_osxbook_driver_DummyDriver::start(IOService *provider) { bool result = super::start(provider); IOLog("start\n"); return result; } void com_osxbook_driver_DummyDriver::stop(IOService *provider) { IOLog("stop\n"); super::stop(provider); } bool com_osxbook_driver_DummyDriver::attach(IOService *provider) { bool result = super::attach(provider); IOLog("attach\n"); return result; } void com_osxbook_driver_DummyDriver::detach(IOService *provider) { IOLog("detach\n"); super::detach(provider); }

Let us load the driver manually using kextload and unload it using kextunload.

$ sudo kextload -v DummyDriver.kext kextload: extension DummyDriver.kext appears to be valid kextload: notice: extension DummyDriver.kext has debug properties set kextload: loading extension DummyDriver.kext kextload: DummyDriver.kext loaded successfully kextload: loading personalities named: kextload: DummyPersonality_0 kextload: sending 1 personality to the kernel kextload: matching started for DummyDriver.kext $ sudo kextunload -v /tmp/DummyDriver.kext kextunload: unload kext /tmp/DummyDriver.kext succeeded

We can now look in /var/log/system.log for messages logged by DummyDriver. The following excerpt from the log shows the sequence in which the I/O Kit calls the driver's methods.

init # kextload attach probe detach attach start ... stop # kextunload detach free

We see active matching in action as the I/O Kit calls attach(), probe(), and detach(), in that order. Since our probe() implementation returns success, the I/O Kit proceeds to start the driver. Had we returned a failure from probe(), the next method to be called would have been free().

Figure 1021 shows a more general view of how the I/O Kit calls driver methods in a driver's lifecycle.

Figure 1021. The sequence of I/O Kit driver methods called in a driver's lifecycle

While debugging an I/O Kit driver, it is possible to only load the kext and defer the matching phase. The -l option instructs kextload not to start the matching process. Moreover, the -s option can be used to instruct kextload to create symbol files for the kext and its dependencies.[10] This allows the programmer to set up the debugger before initiating the matching process, which can be performed later using the -m option of kextload. If the matching succeeds, the driver will be started eventually. Section 10.8.4 provides an example of this debugging approach.

[10] When a kext is loaded, its symbols are relocated.

Providing User-Space Information to an I/O Kit Driver

A driver kext can retrieve information from the kext's property list file, which provides a load-time mechanism for providing information to a driver from user space. However, there is an important caveat: Because kext property lists are normally cached, modifying a property list will take effect only when the caches are updated.

Drivers can access user-space memory by creating an IOMemoryDescriptor instance based on a virtual address in a given task and then preparing and mapping the descriptor.

The user client interface is a convenient mechanismprovided the driver in question supports itfor exchanging arbitrary information between a driver and a user program.

Some drivers implement the setProperty() method, which allows user programs to set properties of I/O Registry entry objects through I/O Kit library functions, namely, IORegistryEntrySetCFProperty() and IORegistryEntrySetCFProperties().

Категории