DriverKit: how Apple is moving every driver out of the kernel
Same IOKit object model, userland process. Why kexts are dying, what DriverKit gives you, and how a USB driver actually crosses the boundary.
For thirty years, third-party drivers on macOS were kexts — Mach-O bundles loaded into the kernel address space. Since macOS 10.15 (Catalina) Apple has been steadily, deliberately killing them. The replacement is DriverKit: the same IOKit object model, but each driver runs as a user-mode process. This is the article on what changed, why, and how it actually works.
What was wrong with kexts
A kext is code in the kernel. Three problems followed:
- Crashes panic the system. A null pointer dereference in a Wi-Fi driver doesn't just disconnect Wi-Fi; it takes the machine down. For decades this was the dominant cause of kernel panics on consumer Macs.
- The kernel C++ ABI is fragile. Adding a virtual method to
IOServicein a major macOS release would silently break every kext that subclassed it. Apple maintains an elaborate metaclass system (see the IOKit article) to soften this, but every release still breaks something somewhere. - Kexts are a security boundary that can be subverted. A signed kext runs at ring 0 with full memory access. Code-signing requirements help but don't eliminate the attack surface.
DriverKit fixes all three: a crash kills only the driver process; the ABI is a stable C interface across a Mach boundary; the driver runs sandboxed without kernel privileges.
The bridge: IOUserServer
Here's the part that's clever. DriverKit doesn't rewrite IOKit. The driver still subclasses IOService, still has an Info.plist with a personality dictionary, still matches against hardware the same way. The only difference is where the instance lives — in a userspace process instead of in kernel memory.
The kernel half of this is a class called IOUserServer. When the kernel decides a DriverKit driver matches some hardware, instead of instantiating the C++ class in kernel space, it:
- Forks the DriverKit driver as a userspace process via
launchd. - The driver process loads
DriverKit.frameworkand creates the driver class instance there. - The kernel creates an
IOUserServerinstance that proxies for the userspace driver. - From the rest of IOKit's perspective, the driver is just another
IOService— it doesn't know (or care) that the methods are getting forwarded over Mach IPC.
apple-oss-distributions/xnuiokit/Kernel/IOUserServer.cppThe kernel side of DriverKit — how an IOService in userland looks like an IOService in the kernel.View on GitHub(line —) apple-oss-distributions/xnuiokit/Kernel/IOUserClient.cppIOUserClient — the mechanism every userland↔driver call goes through, kext or DriverKit.View on GitHub(line —)
Every call from "kernel IOKit" into the driver — start, read, interrupt, anything — becomes a Mach message. The DriverKit framework on the userspace side dispatches the message to the matching method on the driver instance.
What a DriverKit driver can and can't do
DriverKit narrows the API surface to a curated subset. Things you can do:
- Subclass
IOUserService,IOUserClient,IOInterruptDispatchSource,IOTimerDispatchSource. - Read from and write to your assigned hardware through abstractions (
IOMemoryDescriptor,IOAddressSegment). - Service interrupts, but through a queued dispatch — your handler runs in your userspace process, not in the kernel interrupt context.
- Expose a user client to apps, just like a kext-era driver did.
Things you can't do:
- Allocate kernel memory directly.
- Walk the IORegistry freely (you only see your provider chain and descendants).
- Make arbitrary
mach_msgcalls. - Run code with kernel privileges, period.
The categories of devices DriverKit supports: USB, PCI, network, audio, HID, SCSI, serial. That covers most third-party device classes. Drivers for the SoC itself (interrupt controllers, the system management unit) remain in-kernel because they need privileged hardware access.
Process lifecycle
A DriverKit driver doesn't ship as an executable bundle the way a kext does. It ships as a driver extension (.dext) embedded inside a regular app bundle. The app contains a regular GUI for setup/preferences; the dext contains the driver itself.
When the user opts in via System Settings → Privacy & Security → "Allow system extensions", the dext is registered with the kernel. From then on:
- Hardware matching it appears (e.g., a specific USB device is plugged in).
- The kernel asks
launchdto start the dext process. launchdexecs the dext under a tight sandbox, with only the entitlements declared in its plist.- The dext registers with the kernel's
IOUserServerand becomes the matching driver. - When the hardware goes away (unplugged, suspended), the kernel terminates the dext process.
The "process per device" model has real costs: there's IPC overhead on every call, the memory footprint is a few MB per dext, and developer ergonomics are worse than a kext (more boilerplate, no debugger attach-to-kernel). The trade is worth it for the crash isolation alone.
What does this mean for users
Practically:
- Third-party kexts now require booting into Reduced Security mode and manually approving each kext. The user-facing flow is hostile by design.
- Most familiar third-party drivers (USB serial adapters, audio interfaces, USB→Ethernet dongles, NVMe over Thunderbolt, fan controllers, etc.) ship as dexts now. If you've used "System Extension" approval dialogs in System Settings, you've enabled DriverKit drivers.
- The crash class "kernel panic from a third-party driver" is becoming rare on modern macOS. A misbehaving dext shows up as "DriverKit extension crashed" in Console, the app restarts the dext, and the rest of the system carries on.
Reading the DriverKit headers
There's no public open-source mirror of DriverKit itself — it's part of the macOS SDK, not Darwin. You can read the headers in /Applications/Xcode.app/Contents/Developer/Platforms/DriverKit.platform/Developer/SDKs/DriverKit.sdk/System/DriverKit/usr/include/DriverKit/ once you've installed Xcode.
For the kernel side that is open source, the entry points to read:
apple-oss-distributions/xnuiokit/Kernel/IOUserServer.cppIOUserServer — how kernel IOKit talks to userland DriverKit.View on GitHub(line —) apple-oss-distributions/xnuiokit/Kernel/IOServicePM.cppPower management lifecycle — drives dext spawn/terminate.View on GitHub(line —)
What surprises newcomers
- Existing apps don't need changes to use DriverKit-backed hardware. The user client API is identical.
- Notification interrupts are slower than in-kernel interrupts. The latency is fine for USB peripherals but unacceptable for, say, a high-end audio interface — which is why Core Audio's most timing-critical paths still run in-kernel.
- A dext can have multiple matching personalities in the same bundle. One dext can drive a whole device family.
- DriverKit was first shipped on iOS (where third-party kernel code has been forbidden since day one). Bringing it to macOS in 10.15 was Apple making the iOS model the default everywhere.
What to read next
IOKit and the driver model — the foundation DriverKit is built on. Read it first if you haven't.
For the long view, Apple's WWDC sessions on DriverKit (2019 onward) are the most accessible deep dive — they walk through writing a USB dext end-to-end. Search the developer site for "DriverKit".