![]()
Давно хотел рассмотреть процесс загрузки Mac OS X, давайте сделаем это вместе. Предупрежу – это моя попытка разобраться, и не рассматривайте на эту статью, как истину в последней инстанции. Это первая часть, далее я буду рассматривать более детально начальную и завершающую стадии – BootROM/EFI и launchd.
В процессе написания я использовал такую информацию:
- “System Startup Programming Topics“
- man kextcache
- главу “Booting Mac OS X” из несколько устаревшей, но чрезвычайно полезной книги “Mac OS X Internals” (@alexmak, спасибо за рекомендацию).
Процесс загрузки Intel Mac (Snow Leopard):
1. При включении питания запускается BootROM firmware.
1.1. Выполняется POST (Power-On Self Test), инициализирующий некоторые системные интерфейсы, и проверяющий, что в системе установлено достаточное количество памяти и она находится в нормальном состоянии.
1.2. Запускается EFI (Extensible Firmware Interface), инициализируя остальное базовое системное “железо”, и производящий выбор операционной системы.
2. Управление передаётся загрузчику boot.efi , находящемуся на разделе выбранной Mac OS X. Его основная задача – подготовить всё для загрузки ядра. boot.efi находится в /System/Library/CoreServices/ (и копия – в /usr/standalone/i386/).
2.1. Дисплей закрашивается светло-серым цветом.
2.2. Загрузчик пытается загрузить прелинкованную версию ядра (самой ядро – /mach_kernel), включающую все драйвера, необходимые для загрузки. Из-за этого время загрузки значительно сокращается. Каталог /System/Library/Caches/com.apple.kext.caches/Startup/, файлы вида kernelcache_i386.2B109974 (здесь указана архитектура и 8 символов контрольной суммы, вычисляемой по алгоритму Adler-32).
2.3. Появляется лого Apple и появляется вращающийся курсор.
2.4. Если прелинкованная версия ядра отсутствует, устарела (например, время модификации драйвера новее, чем это ядро) или повреждена, загрузчик пытается загрузить все драйвера из кеша mkext /System/Library/Caches/com.apple.kext.caches/Startup/Extensions.mkext. Описание кеша находится в /usr/standalone/bootcaches.plist.
2.5. Если кеш отсутствует, устарел или повреждён, загрузчик ищет в каталоге /System/Library/Extensions драйвера и расширения ядра, и загружает те, у которых OSBundleRequired установлено в значение, соответствующее типу загрузки (например, локальная, сетевая, etc). Детальнее можно посмотреть в “Loading Kernel Extensions at Boot Time“. Например,
$ grep -1 OSBundleRequired /System/Library/Extensions/AppleBacklight.kext/Contents/Info.plist | tail -2 </dict> <key>OSBundleRequired</key> <string>Safe Boot</string> $ grep -1 OSBundleRequired /System/Library/Extensions/AppleRAIDCard.kext/Contents/Info.plist </dict> <key>OSBundleRequired</key> <string>Local-Root</string>
- Варианты OSBundleRequired:
- Root. This KEXT is required to mount root, regardless of where root comes from – for example, platform drivers and families, PCI, or USB.
- Network-Root. This KEXT is required to mount root on a remote volume—for example, the network family, Ethernet drivers, or NFS.
- Local-Root. This KEXT is required to mount root on a local volume – for example, the storage family, disk drivers, or file systems.
- Console. This KEXT is required to provide character console support (single-user mode) – for example, keyboard drivers or the ADB family.
- Safe Boot. This KEXT is required even during safe-boot (unnecessary extensions disabled)—for example, mouse drivers or graphics drivers.
2.6. Когда ядро и все необходимые драйвера загружены (не запущены, а именно “loaded” в память), загрузчик запускает процедуру инициализации ядра. На этой стадии загружено достаточное количество драйверов для того, чтобы было найдено устройство, на котором расположена корневая файловая система (более понятно – root device).
2.7. Ядро инициализирует структуры данных Mach и BSD, а затем I/O Kit (коллекцию системных фреймворков и библиотек, поддерживающих остальные драйвера устройств). I/O Kit линкует драйвера в ядро, используя дерево устройств для определения, какие именно драйвера линковать. Это дерево строилось на стадии EFI. Посмотреть его уже из загруженной системы можно так:
$ ioreg -S -p IODeviceTree -l 0 -w
+-o Root <class IORegistryEntry>
| {
| "IOKitBuildVersion" = "Darwin Kernel Version 10.0.0: Fri Jul 31 22:47:34 PDT 2009; root:xnu-1456.1.25~1/RELEASE_I$
| "IOMaximumMappedIOByteCount" = 536870912
| "OSPrelinkPersonalityCount" = 571
| "OS Build Version" = "10B504"
| "OSKernelCPUSubtype" = 3
| "OSKernelCPUType" = 7
| "OSPrelinkKextCount" = 139
| "IORegistryPlanes" = {"IOACPIPlane"="IOACPIPlane","IOPower"="IOPower","IODeviceTree"="IODeviceTree","IOService"="$
| "IONDRVFramebufferGeneration" = <0400000004000000>
| "IOConsoleUsers" = ({"kCGSSessionConsoleSetKey"=0,"kCGSSessionOnConsoleKey"=Yes,"kSCSecuritySessionID"=3172743,"k$
| "IOKitDiagnostics" = {"Container allocation"=3295268,"Instance allocation"=6354490,"Pageable allocation"=20925644$
| }
...
+-o PCI0@0 <class IOACPIPlatformDevice>
| | {
| | "compatible" = <"PNP0A03">
| | "IODTPersist" = <a2319300a2409300>
| | "_STA" = 15
| | "IOPCIConfigured" = Yes
| | "acpi-address-spaces" = <0200000000000000000000000000000000000000000000000000000000000000ff000000000000000000$
| | "#size-cells" = <02000000>
| | "acpi-pci-routing-table" = <2800000000000000ffff030000000000000000005c5f53425f2e504349302e4c534d4200000000002$
| | "#address-cells" = <03000000>
| | "_ADR" = 0
| | "device-properties" = {"acpi-device"="IOACPIPlatformDevice is not serializable","acpi-path"="IOACPIPlane:/_SB$
| | "acpi-path" = "IOACPIPlane:/_SB/PCI0@0"
| | "name" = <"PNP0A08">
| | "acpi-device" = "IOACPIPlatformDevice is not serializable"
| | }
...
2.8. Как только найдено root device, ядро его монтирует в “/”.
3. Загружаются системные сервисы и ведётся подготовка системы для использования пользователями. До версии Mac OS X 10.4 этим занимались привычные для BSD Unix процессы mach_init и init (они запускали в том числе разнообразные системные скрипты /etc/rc), но с версии 10.4 они были заменены на launchd.
В дополнение к инициализации системы launchd обеспечивает вызов демонов в нужном порядке. Наподобие inetd, launchd вызывает демонов по необходимости (они могут останавливаться через определённое время неактивности и перезапускаться по необходимости).
$ cat /System/Library/LaunchDaemons/com.apple.loginwindow.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple.loginwindow</string>
<key>ProgramArguments</key>
<array>
<string>/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow</string>
<string>console</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
4. В завершение launchd запускает loginwindow (окно входа в систему).
Детальнее о launchd поговорим в следующей статье.





