10.4. Dynamically Extending the KernelThe Mac OS X kernel is extensible through dynamically loadable components called kernel extensions, or simply kexts. A kext is loaded into the kernel either by the kernel's built-in loader (during early stages of bootstrapping) or by the user-level daemon kextd, which loads kexts when requested by user processes or the kernel. On being loaded, a kext resides in the kernel's address space, executing in privileged mode as part of the kernel. Numerous I/O Kit device drivers and device families are implemented as kexts. Besides device drivers, kexts also exist for loadable file systems and networking components. In general, a kext can contain arbitrary codesay, common code that may be accessed from multiple other kexts. Such a kext would be akin to a loadable in-kernel library. 10.4.1. The Structure of a Kernel ExtensionA kext is a type of bundle, much like an application bundle. A kext bundle's folder has a .kext extension.[7] Note that the extension is not merely conventionalit is required by the Mac OS X tools that deal with kernel extensions. A kext bundle must contain an information property list file (Info.plist) in its Contents/ subdirectory. The property list specifies the kext's contents, configuration, and dependencies in an XML-formatted dictionary of key-value pairs. When a kext is loaded into the kernel, the contents of its Info.plist are converted to kernel data structures for in-memory storage.[8] A kext bundle normally also contains at least one kernel extension binary, which is a Mach-O executable. It can optionally contain resources such as helper programs and icons in its Resources/ subdirectory. Moreover, a kext bundle can contain other kext bundles as plug-ins.
It is possible to have a valid kext bundle without any executables. The Info.plist file of such a kext may reference another kext in order to alter the characteristics of the latter. For example, ICAClassicNotSeizeDriver.kext does not contain an executable, but it holds several driver personalities that refer to AppleUSBMergeNub.kext, which is a plug-in kext within IOUSBFamily.kext. Kernel-loadable binaries contained within kexts are statically linked, relocatable Mach-O binaries called kernel modules, or kmods. In other words, a kext is a structured folder containing one or more kmods along with mandatory metadata and optional resources. Figure 106 shows the structure of a simple kext bundle. Figure 106. The contents of a simple kernel extension bundle
It is important to note that even though a kmod is a statically linked Mach-O object file, nontrivial kmods usually have unresolved external references that are resolved when the kext is dynamically loaded into the kernel. 10.4.2. Creation of Kernel ExtensionsAlthough most driver kexts are created using only I/O Kit interfaces, a kext maydepending on its purpose and natureinteract with the BSD and Mach portions of the kernel. In any case, loading and linking of kexts is always handled by the I/O Kit. The preferred and most convenient way to create a kernel extension is by using one of the kernel extension project templates in Xcode. In fact, other than for a contrived reason, it would be rather pointless to compile a kmod and package it into a kernel extension manuallysay, using a hand-generated makefile. Xcode hides several details (such as variable definitions, compiler and linker flags, and other rules for compiling and linking kernel extensions) from the programmer. Two kernel extension templates are available in Xcode: one for generic kernel extensions and one for I/O Kit drivers. A primary difference between them is that an I/O Kit driver is implemented in C++, whereas a generic kernel extension is implemented in C. It is also possible to create a library kext containing reusable code that can be used by multiple other kexts.
Definitions and rules for various types of Xcode projects reside in the /Developer/Makefiles/ directory. Figure 107 shows an excerpt from the build output of a Universal kernel extensionan I/O Kit drivercontaining kmods for the PowerPC and x86 architectures. The path to the build directory, which is normally a subdirectory called build in the kernel extension's Xcode project directory, has been replaced by $BUILDDIR in the output shown. Figure 107. Excerpt from the build output of a Universal kernel extension
Note that the Kernel framework (Kernel.framework), which is referenced by the compiler in the output shown in Figure 107, provides only kernel headersit does not contain any libraries. We see in Figure 107 that the kmod being compiled is linked with several libraries and an object file called <kmod>_info.o, where <kmod> is the kmod's nameDummyDriver in our example. These entities serve the following purposes.
The order of arguments in the linker command line is instrumental in differentiating between the compilation of C++-based and C-based kmods. As shown in Figure 107, the order of object files and libraries in the linker command line is as follows: ...DummyDriver.LinkFileList ... -lkmodc++ ...DummyDriver_info.o -lkmod ... The file DummyDriver.LinkFileList contains the pathnames of the kmod's object files. If the kmod uses C++, the compiler will add references in object files to undefined symbols called .constructors_used and .destructors_used. In the case of a kmod that does not use C++, the references to these symbols will not be present. Let us see how these symbols affect linking by examining the implementations of libkmodc++.a and libkmod.a, which are shown in Figures 108 and 1010, respectively. Figure 108. Implementation of libkmodc++.a
Since libkmodc++.a exports the .constructors_used and .destructors_used symbols, it will be used to resolve references to these symbols in the case of a C++ kmod object file. As a side effect, the symbols _start and _stop will also come from libkmodc++.a. The <kmod>_info.c file uses these symbols to populate a kmod_info data structure (struct kmod [osfmk/mach/kmod.h]) to describe the kernel module. The kmod_info structure contains the starting address of the kernel module in its address field. Since the module is a Mach-O binary, the binary's Mach-O header is located at this address. Juxtaposing the information in Figures 108 and 109, we see that the start and stop routines of an I/O Kit driver kmod will come from libkmodc++.a. Moreover, these routines will run OSRuntimeInitializeCPP() and OSRuntimeFinalizeCPP(), respectively. Since the _realmain and _antimain function pointers are both set to NULL in <kmod>_info.c, the start and stop routines will not invoke the corresponding functions. Figure 109. Declaration of the kmod_info structure for an I/O Kit driver kernel module
In the case of a C++ kext, a particular virtual function may be overridden by another subclass at runtime. Since this cannot be determined at compile time, virtual function calls in a C++ kext are dispatched through the vtable. Depending on the kext ABI of the running system and the kext ABI of a given kext, the loading mechanism can patch vtables to maintain ABI compatibility. OSRuntimeInitializeCPP() calls the preModLoad() member function of OSMetaClass, passing it the module's name as the argument. preModLoad() prepares the runtime system for loading a new module, including taking a lock for the duration of the loading. OSRuntimeInitializeCPP() then scans the module's Mach-O header, seeking segments with sections named __constructor. If any such sections are found, the constructors within them are invoked. If this process fails, OSRuntimeInitializeCPP() calls the destructors (that is, sections named __destructor) for those segments that had their constructors successfully invoked. Eventually, OSRuntimeInitializeCPP() calls the postModLoad() member function of OSMetaClass. postModLoad() performs various bookkeeping functions and releases the lock that was taken by preModLoad(). OSRuntimeFinalizeCPP() is called when a module is unloaded. It ensures that no objects represented by OSMetaClass and associated with the module being unloaded have any instances. It does this by checking all metaclasses associated with the module's string name and examining their instance counts. If there are outstanding instances, the unload attempt fails. The actual unloading operation is performed by OSRuntimeUnloadCPP(), the call to which is surrounded by preModLoad() and postModLoad(). OSRuntimeUnloadCPP() iterates over the module's segments, examining them for sections named __destructor and, if any are found, calling the corresponding destructor functions. Next, let us see how libkmod.a (Figure 1010) is used in the implementation of a C-only kmod. Figure 1010. Implementation of libkmod.a
In the case of a C-only kmod object file, which will not contain unresolved references to .constructors_used and .destructors_used, libkmodc++.a will not be used. The _start and _stop symbols referenced in <kmod>_info.c will come from the next library that contains these symbolsthat is, libkmod.a. As Figure 1011 shows, the <kmod>_info.c file of such a kernel module will set _realmain and _antimain to point to the module's start and stop entry points, respectively. Figure 1011. Declaration of the kmod_info structure for a generic kernel module
In other words, although every kmod has start and stop entry points, they can be implemented by the programmer only in the case of a generic kernel module. In the case of an I/O Kit driver, these entry points are unavailable to the programmer because they correspond to the C++ runtime initialization and termination routines. It follows that these two types of loadable entities cannot be implemented within the same kext. 10.4.3. Management of Kernel ExtensionsThe functionality for working with kernel extensions is implemented across several Darwin packages, such as xnu, IOKitUser, kext_tools, cctools, and extenTools. The following are the primary command-line programs available for managing kernel extensions.
kexTD, the kernel extension daemon, is the focal point of much of the activity that occurs when kexts are loaded or unloaded in a normally running system. During the early stages of bootstrapping, kextd is not yet available. The libsa support library in the kernel handles kernel extensions during early boot. Normally, libsa's code is removed from the kernel when kextd starts running. If you boot Mac OS X in verbose mode, you will see a "Jettisoning kernel linker" message printed by the kernel. kextd sends a message to the I/O Kit to get rid of the in-kernel linker. In response, the I/O Kit invokes destructors for the kernel's __KLD and __LINKEDIT segments and deallocates their memory. The memory set up by BootX is also freed. kexTD can be instructed (via the j option) not to jettison the kernel linker. This allows the kernel to continue handling all load requests. In this case, kexTD exits with a zero status if there is no other error. Bootable optical discs can use this option in startup scripts, along with an mkext cache, to accelerate booting. For example, as we saw in Section 5.10.4, Apple's Mac OS X installer disc runs kextd with the j option in /etc/rc.cdrom. kextd registers com.apple.KernelExtensionServer as its service name with the Bootstrap Server. It processes signals, kernel requests, and client requests (in that order) in its run loop. kextd and command-line tools such as kextload and kextunload use the KXKextManager interface for manipulating kernel extensions. KXKextManager is implemented as part of the I/O Kit framework (IOKit.framework). Figure 1012 shows an overview of kextd's role in the system. Figure 1012. Kext managementWhen kexTD starts running, it calls KXKextManagerCreate() to create an instance of KXKextManager and initializes it by calling KXKextManagerInit(). The latter creates data structures such as a list of kext repositories, a dictionary of all potentially loadable kexts, and a list of kexts with missing dependencies. 10.4.4. Automatic Loading of Kernel ExtensionsIf a kext is to be loaded every time the system boots, it must be placed in the /System/Library/Extensions/ directory. All contents of the kext bundle must have the owner and group as root and wheel, respectively. Moreover, all directories and files in the kext bundle must have mode bit values of 0755 and 0644, respectively. The system maintains a cache of installed kexts, along with their information dictionaries, to speed up boot time. It updates this cache when it detects any change to the /System/Library/Extensions/ directory. If an installer installs an extension as a plug-in of another, however, only a subdirectory of /System/Library/Extensions/ is updated, and the automatic cache update is not triggered. In such a case, the installer must explicitly touch /System/Library/Extensions/ to ensure that the caches are recreated to include the newly installed kext. A kext can be declared as a boot-time kext by setting the OSBundleRequired property in its Info.plist file. The valid values this property can take include Root (required to mount root), Local-Root (required to mount root on locally attached storage), Network-Root (required to mount root on network-attached storage), Safe Boot (required even in safe-mode boot), and Console (required to provide character console supportthat is, for single-user mode).
Although a driver kext can be explicitly loaded with kextload, it is preferable to restart the system to ensure reliable matching so the driver can be considered for all potential devices it can drive. In the case of a running system, if a driver is already managing a given device, another driver will not be able to manage the device in question. |