Architecture
Darwin is a hybrid kernel with a BSD userland story layered on top. This page walks the stack from the kernel up through userland, naming the pieces and the concepts that tie them together.
Darwin is a hybrid kernel with a BSD userland story layered on top. This page walks the stack from the kernel up through userland, naming the pieces and the concepts that tie them together.
From the bottom up: the boot chain hands control to the XNU kernel, which brings up the scheduler, virtual memory, and IPC. XNU then loads the BSD personality — syscalls, process model, filesystems — on top of the Mach core. IOKit matches drivers to hardware as it's discovered. Once the kernel is live, launchd runs as PID 1 and starts every userspace service. Programs link against libSystem via the dyld dynamic linker. Everything above that — shells, daemons, applications — is userland.
XNU ("X is Not Unix") is a hybrid kernel. The low-level core is Mach, a microkernel that provides tasks, threads, virtual memory, and inter-process communication via message-passing over ports. The upper half is a BSD personality — process groups, signals, sockets, a VFS layer, and roughly a BSD-style syscall table — running in the same address space as Mach.
The value of the hybrid model is that you get Mach's flexibility (e.g. out-of-line IPC, exception handling, memory objects) with BSD's familiar Unix interface. The cost is conceptual: writing kernel code means switching between Mach semantics and BSD semantics depending on what you're touching.
Each hardware platform XNU runs on has a platform expert — a kernel module that knows how to probe the board at boot: enumerate CPUs, discover memory regions, read device trees or ACPI, set up interrupt controllers. The platform expert is the first driver IOKit instantiates. Porting darwinOS to new hardware usually starts here.
These three words get used interchangeably — they shouldn't. A target is an architecture + ABI the compiler emits for (e.g. `arm64-darwin`). A platform is a family of machines that share a platform expert (e.g. "Apple Silicon"). A board is a specific machine within a platform (e.g. "Mac mini M2"). A single target can cover many platforms; a platform can cover many boards.
IOKit is the driver framework. Drivers are written in a restricted dialect of C++ called Embedded C++ (eC++). Classes inherit from `IOService`; matching happens at runtime by walking a personality dictionary against a registry of nubs (hardware-providing parents).
IOKit groups related drivers into families: network, storage, USB, HID, audio, graphics. Each family defines a set of abstract superclasses and the shared contract between them. A new network driver, for example, inherits from `IOEthernetController` and implements a handful of methods.
Historically drivers were shipped as kexts — kernel-loadable bundles living in kernel space. Modern Apple platforms push drivers into userspace via DriverKit, a subset of IOKit that runs in a sandboxed userspace process with IPC back into the kernel. darwinOS supports both; new drivers should prefer DriverKit where the hardware permits.
launchd is the first userspace process the kernel starts and the parent of every other userspace process. It reads launchd job plists — XML property lists that describe a service: binary, arguments, environment, activation triggers (on boot, on demand, on socket, on calendar interval), throttle policy, and owning user.
The novelty of launchd is on-demand activation. A daemon can declare it owns a Mach port or a socket; the system keeps the port registered but doesn't run the daemon until a client sends a message. The daemon starts, services the request, and may exit — launchd restarts it only when another client arrives. That model collapses the distinction between init-started daemons and inetd-style on-demand services.
Every Darwin binary is a Mach-O executable that lists the dynamic libraries it needs. dyld is the program in kernel-selected address space that reads those lists, loads the libraries, and resolves symbols at first use (lazy binding).
The big architectural choice in modern Darwin is the shared cache: one giant Mach-O that contains every system library pre-linked and relocated. At boot, dyld maps the cache into every process's address space instead of re-resolving it per binary. The cost is a multi-minute cache rebuild when anything in `/usr/lib` changes; the win is vastly faster process startup.
Programs on Darwin don't link against libc directly. They link against libSystem, an umbrella that re-exports a set of lower-level libraries as a single ABI:
The umbrella lets Apple (and darwinOS) refactor the underlying libraries without breaking binaries, as long as libSystem's exported symbols stay stable.
Darwin's security model stacks several mechanisms that work together:
The canonical on-disk filesystem for Darwin is APFS — copy-on-write, snapshot-capable, space-sharing containers. APFS is not open source, but the on-disk format is documented; darwinOS carries a clean-room read/write implementation in progress.
Legacy HFS+ support ships as read-only for migration use. The VFS layer itself is straight BSD; any filesystem with a working vnode driver can mount.
darwinOS targets open boot paths. On UEFI systems that's a UEFI loader (think GRUB or a Darwin-specific stub) that reads the kernel image and jumps. On non-UEFI systems (embedded boards, virtual machines), the loader may be firmware-specific.
Apple's production boot chain (iBoot) is proprietary and out of scope for darwinOS content. Where we need to document the boot-time contract — the boot args, the device tree, the handoff structure the kernel expects — we document only what's in public sources.