Mac OS X Internals: A Systems Approach

5.6. I/O Kit Initialization

PE_init_iokit() [pexpert/ppc/pe_init.c] initializes the I/O Kit. Figure 514 shows its sequence of operations.

Figure 514. I/O Kit initialization

PE_init_iokit() first calls PE_init_kprintf() [pexpert/ppc/pe_kprintf.c], which was also called by ppc_vm_init() earlier in the startup sequence. We saw that PE_init_kprintf() initializes the serial port if it is present. It then calls PE_init_printf() [pexpert/gen/pe_gen.c], which was also called earlier during the startup, but PE_init_iokit() calls it with the vm_initialized Boolean argument set to trUE. PE_init_printf() calls vcattach() [osfmk/console/video_console.c] to arrange for acquiring the screen if the bootstrap is proceeding in nongraphical mode, in which case it also uses vcputc() [osfmk/console/video_console.c] to print messages in the kernel's log buffer for kernel printf() calls.

PE_init_iokit() uses DTLookupEntry() [pexpert/gen/device_tree.c] to look up the /chosen/memory-map entry in the device tree. If the lookup succeeds, the BootCLUT and Pict-FailedBoot properties are retrieved from the entry. BootCLUT is the 8-bit boot-time color lookup table that was passed to the kernel by BootX. Pict-FailedBootalso passed to the kernel by BootXis the picture shown if booting fails. You can examine this portion of the device tree by using one of the I/O Registry tools:

$ ioreg -S -p IODeviceTree -n memory-map | less +-o Root <class IORegistryEntry> +-o device-tree <class IOPlatformExpertDevice> +-o chosen <class IOService> | +-o memory-map <class IOService> | "Kernel-__VECTORS" = <0000000000007000> | "Kernel-__PRELINK" = <0043700000874000> | "AAPL,phandle" = <ffa26f00> | "Pict-FailedBoot" = <00d7a00000004020> | "BootArgs" = <00d78000000001fc> | "Kernel-__DATA" = <0035d000000a0000> | "BootCLUT" = <00d7900000000300> | "Kernel-__HIB" = <0000700000007000> | "name" = <"memory-map"> | "Kernel-__TEXT" = <0000e0000034f000> | } | ...

If BootCLUT is found, PE_init_iokit() copies its contents to appleClut8, the default Mac OS X color lookup table. It then calls panic_ui_initialize() [osfmk/console/panic_dialog.c], which sets the active CLUT pointer.

It is possible to replace the default panic picture either by recompiling the kernel with another picture or by dynamically loading a new picture through a sysctl interface. Moreover, the kernel also allows the panic user interface to be tested without inducing an actual panic. An image to be used as the panic picture must be converted either to a C structure that can be compiled into the kernel or to a kernel-loadable file. The genimage.c and qtif2kraw.c files in osfmk/console/panic_ui/ contain sources for utilities that convert an uncompressed QuickTime RAW image file into a C structure and a loadable RAW file, respectively. Arbitrary image formats can be converted to QuickTime RAWa .qtif fileusing QuickTime facilities, among other tools. Figure 515 shows an example of replacing the kernel's default panic image by loading a new one from user space.

Figure 515. Loading a replacement panic user interface image into the kernel

$ sips -g all image.qtif ... typeIdentifier: com.apple.quicktime-image format: qtif ... bitsPerSample: 8 hasAlpha: no space: RGB profile: Generic RGB Profile $ qtif2kraw -i image.qtif -o image.kraw Verifying image file... Image info: width: 640 height: 480 depth: 8... Converting image file to 8 bit raw... Converted 307200 pixels... Found 307200 color matches in CLUT... Encoding image file... Writing to binary panic dialog file image.kraw, which is suitable for loading into kernel... $ cat load_panic_image.c // load_panic_image.c #define PROGNAME "load_panic_image" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/sysctl.h> int main(int argc, char **argv) { int ret, fd; char *buf; size_t oldlen = 0, newlen; struct stat sb; int mib[3] = { CTL_KERN, KERN_PANICINFO, KERN_PANICINFO_IMAGE }; if (argc != 2) { fprintf(stderr, "usage: %s <kraw image file path>\n", PROGNAME); exit(1); } if (stat(argv[1], &sb) < 0) { perror("stat"); exit(1); } newlen = sb.st_size; buf = (char *)malloc(newlen); // assume success fd = open(argv[1], O_RDONLY); // assume success ret = read(fd, buf, sb.st_size); // assume success close(fd); if (sysctl(mib, 3, NULL, (void *)&oldlen, buf, newlen)) perror("sysctl"); exit(ret); } $ gcc -Wall -o load_panic_image load_panic_image.c $ sudo ./load_panic_image ./image.kraw

You can cause the kernel to display the panic user interface through another sysctl, as shown in Figure 516.

Figure 516. Testing the panic user interface

// panic_test.c #include <stdlib.h> #include <sys/types.h> #include <sys/sysctl.h> #define KERN_PANICINFO_TEST (KERN_PANICINFO_IMAGE + 2) int main(void) { int ret; size_t oldnewlen = 0; int mib[3] = { CTL_KERN, KERN_PANICINFO, KERN_PANICINFO_TEST }; ret = sysctl(mib, 3, NULL, (void *)&oldnewlen, NULL, oldnewlen); exit(ret); }

Next, PE_init_iokit() calls vc_progress_initialize() [osfmk/console/video_console.c] to initialize the rotating gearwheel boot-progress indicator. The image for the wheel is 32x32 pixels in size. It animates at 24 frames per second. Image data for the animation frames resides in pexpert/pexpert/GearImage.h. The kernel calls vc_progress_set() [osfmk/console/video_console.c] to toggle the animation on or off. When enabled, it arranges for vc_progress_task() [osfmk/console/video_console.c] to be scheduled to run via a callout.

PE_init_ioikit() finally calls StartIOKit() [iokit/Kernel/IOStartIOKit.cpp], passing it pointers to the device tree's root and the boot arguments.

StartIOKit() calls IOLibInit() [iokit/Kernel/IOLib.c] to initialize the I/O Kit's basic runtime environment. IOLibInit() creates a submap of the kernel map for use as the I/O Kit pageable space map. The size of this allocation is kIOPageableMapSize (96MB). A structure of type gIOKitPageableSpace and a queue of contiguous malloc entries are also initialized. The IOMallocContiguous() [iokit/Kernel/IOLib.c] function uses the latter.

StartIOKit() calls OSlibkernInit() [libkern/c++/OSRuntime.cpp] to initialize the I/O Kit C++ runtime environment. OSlibkernInit() calls getmachheaders() [osfmk/mach-o/mach_header.c] to fetch the address of the link-editor-defined _mh_execute_header symbol as the first element of an array of mach_header structures. The address so retrieved is set as the starting address of the libkern library's kmod_info structure [osfmk/mach/kmod.h].

While linking a Mach-O file, the link editor defines a symbol called _MH_EXECUTE_SYM, which is defined to be the string "__mh_execute_header". This symbol, which appears only in a Mach-O executable, is the address of the Mach header in the executable. Moreover, the symbol is absolute and is not part of any section.

OSlibkernInit() then provides a pointer to the kmod_info structure as an argument to OSRuntimeInitializeCPP() [libkern/c++/OSRuntime.cpp], which scans all segments listed in the kernel's Mach header, looking for sections named __constructor. Upon finding such sections, it invokes the constructors. If it fails, it calls OSRuntimeUnloadCPPForSegment() [libkern/c++/OSRuntime.cpp], which looks for sections named __destructor in the segment and invokes the corresponding destructors.

$ otool -l /mach_kernel ... Section sectname __constructor segname __TEXT addr 0x0035c858 size 0x000000f4 offset 3467352 align 2^2 (4) reloff 0 nreloc 0 flags 0x00000000 reserved1 0 reserved2 0 Section sectname __destructor segname __TEXT addr 0x0035c94c size 0x000000f0 offset 3467596 align 2^2 (4) reloff 0 nreloc 0 flags 0x00000000 reserved1 0 reserved2 0 ...

Kernel extensions explicitly declare their dependencies on other kernel components,[24] which may be other kernel extensions, or abstract "extensions" such as the Mach component, the BSD component, the I/O Kit, and so on. StartIOKit() fabricates kmod_info structures for such fictitious extensions, examples of which include the following ones defined by the gIOKernelMods string in iokit/KernelConfigTables.cpp:

[24] We will look at details of kernel extensions in Chapter 10.

const char *gIOKernelKmods = "{" "'com.apple.kernel' = '';" "'com.apple.kpi.bsd' = '';" "'com.apple.kpi.iokit' = '';" "'com.apple.kpi.libkern' = '';" "'com.apple.kpi.mach' = '';" "'com.apple.kpi.unsupported' = '';" "'com.apple.iokit.IONVRAMFamily' = '';" "'com.apple.driver.AppleNMI' = '';" "'com.apple.iokit.IOSystemManagementFamily' = '';" "'com.apple.iokit.ApplePlatformFamily' = '';" "'com.apple.kernel.6.0' = '7.9.9';" "'com.apple.kernel.bsd' = '7.9.9';" "'com.apple.kernel.iokit' = '7.9.9';" "'com.apple.kernel.libkern' = '7.9.9';" "'com.apple.kernel.mach' = '7.9.9';" "}";

The gIOKernelMods string represents a serialized data structure consisting of key-value pairs (i.e., an OSDictionary). StartIOKit() unserializes it to iterate over the list of fictitious extensions.

The fictitious extensions (also called pseudo-extensions) are implemented as plug-ins within the System kernel extension (System.kext), which contains no executable code for any of the extensionseach plug-in extension contains an information property list file (Info.plist), a version property list file (version.plist), and for some extensions, a Mach-O object file containing only a table of exported symbols.

StartIOKit() initializes the IORegistry class by calling IORegistryEntry::initialize() [iokit/Kernel/IORegistryEntry.cpp], which returns a pointer to the root of the I/O Registry. It also initializes the IOService, IOCatalogue, IOUserClient, and IOMemoryDescriptor classes by calling their initialize() methods, which allocate and set up locks, queues, and other class-specific data structures.

StartIOKit() calls IOKitDiagnostics::diagnostics() [iokit/Kernel/IOKitDebug.cpp] to instantiate the IOKitDiagnostics class, which provides I/O Kit debugging functionality such as the ability to print dumps of I/O Kit planes[25] and memory. A serialized version of this class resides in the I/O Registry as the IOKitDiagnostics property.

[25] We will discuss I/O Kit planes and several other aspects of the I/O Kit in Chapter 10.

Finally, StartIOKit() instantiates the IOPlatformExpertDevice class [iokit/Kernel/IOPlatformExpert.cpp]. The resultant instance is the I/O Kit's root nub, which is then initialized by a call to the initWithArgs() method, followed by a call to the attach() method. initWithArgs() creates and initializes a new IOWorkLoop object for the Platform Expert. It also saves the arguments it received as the root nub's IOPlatformArgs property.

// iokit/Kernel/IOPlatformExpert.cpp bool IOPlatformExpertDevice::initWithArgs(void *dtTop, void *p2, void *p3, void *p4) { IORegistryEntry *dt = 0; void *argsData[4]; bool ok; if (dtTop && (dt = IODeviceTreeAlloc(dtTop))) ok = super::init(dt, gIODTplane); else ok = super::init(); if (!ok) return false; workLoop = IOWorkLoop::workLoop(); if (!workLoop) return false; argsData[ 0 ] = dtTop; argsData[ 1 ] = p2; argsData[ 2 ] = p3; argsData[ 3 ] = p4; setProperty("IOPlatformArgs", (void *)argsData, sizeof(argsData)); return true; } ...

Note that the IOPlatformExpertDevice class inherits from IOService, which inherits from IORegistryEntry. The latter implements the setProperty() method.

StartIOKit() calls the recordStartupExtensions() [iokit/Kernel/IOCatalogue.cpp] method of the IOCatalogue class instance to build dictionaries for the startup extensions put into memory by BootX. The dictionaries are recorded in a startup extensions dictionary. The recording is performed by calling the function pointed to by the record_startup_extensions_function pointer, which points to the recordStartupExtensions() function implemented in libsa/catalogue.cpp. The resultant dictionary has the following format:

{ "plist" = /* extension's Info.plist file as an OSDictionary */ "code" = /* extension's executable file as an OSData */ }

StartIOKit() finally calls the root nub's registerService() method, which is implemented in the IOService class. Consequently, the I/O Kit matching process starts as the root nub is published for matching.

Категории