Booting the Mac: the kernel and extensions

The whole purpose of the BootROM and EFI phases is to get to load and run the macOS kernel and its extensions, which is what boot.efi, the “OS X booter”, finally does. Although boot.efi doesn’t suddenly vanish, from here on it is very little needed.

Like so much of macOS, the kernel and its extensions started off simple, and just grew into the monstrously complex. It now includes systems and features such as the Platform Expert, Mach with its inter-process communication of messages and ports, IOKit, and a goodly chunk of BSD’s Unix features too. As I’m not a kernel engineer, I won’t attempt to go into this any further: chapter 2 of Jonathan Levin’s *OS Internals, volume 1, and the promised Volume 2 are and will be excellent references.

The two important topics with respect to the boot process are IOKit and extensions.

In the previous article, I mentioned that one of the important tasks of boot.efi was to build the IODeviceTree, and when starting the kernel, to hand that information over. This is only the start, as the kernel and loaded extensions expand that into a catalogue of all the installed drivers for hardware and other devices, the IORegistry.

It is the IORegistry which is actually displayed when you use the ioreg command in Terminal, rather than boot.efi’s rather simpler IODeviceTree. If you need to look at this registry, study its formatting and selection options carefully using man ioreg: they are complex.

IOKit is remarkable, possibly unique, as it uses object-oriented principles in classes and overloading. Instead of each driver being entire within itself, it declares itself to be a member of a particular family, so letting a driver re-use all the code which it has inherited from that family class, and it only has to implement the functions which differ.

For all this ingenuity, IOKit is a world unto itself, as browsing any of Apple’s (now archived) documentation will reveal: try what it optimistically calls IOKit Fundamentals for a starter.

The kernel has just grown and grown, but needs a great deal more to be able to run a modern Mac. Device drivers, security extensions, filesystem drivers, and all sorts of other fundamental sub-systems need to tack themselves onto the XNU kernel, and do so as kernel extensions (KEXTs, or just extensions). Depending on which version of macOS you’re running, there are around 300 in the main macOS /System/Library/Extensions folder, and perhaps a dozen or more in the admin-accessible /Library/Extensions, according to the third-party hardware and other products which you may have installed.

Until High Sierra, Apple controlled extension development with special developer signatures, but macOS 10.13 introduced another level of protection: User Approved Kernel Extension loading. This changed again in macOS 10.13.4, which primarily affects sysadmins and is detailed here, and the whole system is explained here. Further details are also given here, although that Technical Note is already deemed obsolete.

In practice, macOS High Sierra may install new extensions in /Library/StagedExtensions/Applications as a non-executable stub application, which is then protected by SIP. This may in turn be used as the source from which to install the extension if permission is given. This can result in strange problems.

With so many extensions to be loaded at boot time, macOS has a much better way of coping with this: it builds an optimised and pre-linked version of the kernel with its extensions, which is then stored in /System/Library/PrelinkedKernels/prelinkedkernel, which is pointed to from its more traditional location of /System/Library/Caches/

One disadvantage of using a kernelcache in this way is that the log no longer lists each extension as it is loaded, but bolts through the whole 28 MB or more of kernel plus extensions without that running commentary.

It is also worth bearing in mind that, when macOS updates replace the kernel and/or extensions, one of the time-consuming steps following installation will be the rebuilding of the kernelcache.

Having travelled the journey from the Mac first powering up, to loading a prelinked kernel, there remains one unanswered question which I will try to tackle in the last article in this series: how do we determine which should be the startup disk and boot volume?