My Notes on 'How macOS Works': Programs, Processes, and the Kernel

Sep 20, 202616 min readmacos

I watched How macOS works by Sriniously, the first video of a playlist on macOS internals. Honestly, a lot of it went over my head the first time. I use a Mac every day and could not have told you what happens between double-clicking an app and its window showing up.

So I watched it again, took notes, and tried to restate everything in the simplest words I could. This post is those notes cleaned up, with a few small diagrams you can click on.

Nothing here assumes you have written a Mac app or opened a terminal. Every number that looks oddly specific (page sizes, process counts, version strings) is from the video author's own MacBook, an Apple Silicon machine running macOS 26.6.2. Yours will differ a little; the shape will not.

The video's definition of a computer#

For this whole series, a computer is only four things:

PartIts one jobOn a MacBook
Processor (CPU)Run instructions, one after anotherPart of the M-series chip
Memory (RAM)Hold the data the processor is working on right nowOn the same chip as the processor
DiskHold data when the power is offThe SSD
ScreenShow pixelsA panel that can redraw 120 times a second

That's it. Everything below is about how software gets to use these four things without stepping on each other.

A program is a file. A process is that file, running.#

This sounded pedantic to me at first, but the video keeps coming back to it, and by the end I saw why.

A program is a file on your disk. It contains instructions the processor can run, plus whatever data those instructions need. When you "install an app", you are copying a program onto your disk. By itself, it does nothing. It is bytes at rest.

A process is what you get when the operating system takes that file, loads it into memory, and starts running it. A process gets three things a program does not have: its own slice of memory, a number called a process ID (PID), and the ability to actually do things.

One program can be running as many processes at once. Open two Terminal windows and you have one process with two windows. Open a browser with two tabs and you likely have three processes: one for the browser and one per tab, because browsers deliberately put each page in its own process so a misbehaving site can't reach into the others.

One program, many processes
On disk (program)
/Applications/Ghostty.app/Contents/MacOS/ghostty
42 MB of instructions, sitting still. Does nothing on its own.
In memory (processes): 0
Nothing running. The file is still on disk, unchanged.

The one line I kept repeating to myself: program = file on disk, process = running instance with its own memory and PID.

The app icon is actually a folder#

In Finder, Ghostty.app looks like a single file with a nice icon. It isn't. It's a folder. Apple calls a folder with this particular layout a bundle, and Finder hides the folder-ness because an icon is friendlier than a directory tree.

What Finder shows vs. what is on disk
Ghostty
Application · 42 MB
One icon. Double-click opens it. Finder is hiding the folder underneath because that is friendlier.

Inside every app bundle you'll find the same parts:

  • Info.plist: a description of the app (name, version, icon, what files it opens)
  • MacOS/: the actual program, the file the kernel runs
  • Resources/: images, icons, translations
  • Frameworks/: libraries the app brought along because they're not part of macOS
  • _CodeSignature/: a cryptographic signature saying who built this and a fingerprint of everything else in the bundle

The lesson the video keeps repeating: what the screen shows you and what is on the disk are two different views. When in doubt, check the disk.

The kernel: the referee every process has to ask#

Every process wants the same four things: processor time, memory, files on the disk, and pixels on the screen. And by default, no process is allowed to touch any of them directly. It has to ask.

Why the middleman? Imagine two processes both decide to write to the same byte of memory. Without a referee, whoever writes last wins and the other one's data silently disappears. With a referee that never lets two processes see the same memory, that collision cannot happen at all.

The referee is the kernel. It is the one piece of software on your Mac with full power: it can touch every byte of memory, every device, every process. Everything that is not the kernel runs with limited power, in what people call user land (or user mode, or user space). Your editor, your browser, your terminal, Finder itself: all user land. There is exactly one kernel, and it sits underneath all of them.

On macOS the kernel is called XNU. It has three parts, and knowing them helps later because the video's whole playlist is organised around them:

  • Mach: memory, threads, and messages between processes
  • BSD: files, sockets (networking), signals, and the parent/child process tree
  • IOKit: devices. When you plug in a keyboard, IOKit notices, loads a driver, and turns keypresses into Mach messages.

Zooming out, the playlist splits a Mac into five layers. Click through them:

The five layers of a Mac, bottom to top
Kernel (XNU)

Hands out CPU time, memory pages, files and devices. Everything above it has to ask.

Lives here
  • · Mach: memory, threads, messages
  • · BSD: files, sockets, signals
  • · IOKit: devices
Asks the layer below (Silicon) for what it needs.

System calls: how a process asks#

A process talks to the kernel through exactly one kind of door: the system call. "Open this file." "Give me more memory." "Start another process." "Put these bytes on the screen." Each is a request that crosses from user land into the kernel and comes back with an answer.

The video runs fs_usage, a tool that prints every file-related system call the kernel receives, while saving a one-line file in TextEdit. Step through what it saw:

Every row is one trip from user land into the kernel and back
TextEdit saves a one-line file
User land · TextEdit
Press next to make the first call.
Kernel · XNU
0/5 calls · 0 µs in the kernel

Two things surprised me here.

First, a single Cmd+S is five trips into the kernel, not one. And each trip has a cost: a few millionths of a second each, which sounds like nothing until an app does thousands of them. According to the video, a lot of "make this Mac app faster" work comes down to "make fewer system calls." Video 10 is apparently all about measuring that cost.

Second, TextEdit doesn't overwrite your file. It writes a brand-new file with a scrambled name next to it, then renames the new one over the old one. Renaming is a single, indivisible operation to the kernel, so if the power dies halfway through a save you get either the complete old file or the complete new file, never a half-written mess. That property is called atomic. The video says this temp-file-then-rename trick shows up all over the place; I'll be watching for it now.

Switch the demo to "Write into /System" to see the other half of the kernel's job. When a program asks for something it isn't allowed, the kernel says no with a number and a name: EPERM, "Operation not permitted." /System holds macOS itself and is read-only even for an administrator. The program never touched the disk; it just got refused.

Frameworks: code your app borrows#

When an app "draws a window", the app is not drawing a window. It calls code Apple wrote, which draws the window for it. That code is a library: a file of instructions that many programs share, kept once on disk and loaded into every process that needs it. Every app that shows the standard Open File dialog is running the exact same dialog code.

Apple packages a library together with its headers, resources and documentation into a folder, and calls the folder a framework. So a framework is just a library in a bundle. The ones that will keep coming up:

FrameworkWhat you ask it for
AppKitWindows, menus, buttons, keyboard and mouse events
SwiftUIA nicer way to describe UI; built on top of AppKit, not a replacement for it
Core Text"How wide is this string in this font?" and turning text into shapes
Core AnimationLayers, animation, and handing finished pixels to the screen
MetalTalking to the GPU

Each framework offers an API: the list of things you're allowed to ask it to do. Apple documents every one of those. The folder holding all public frameworks on the author's Mac has about 305 entries.

One keystroke, ten hand-offs#

This was the part that made the layers click for me. You press a key. What happens before the letter appears?

From key press to pixel: ten hand-offs
Keyboardhardware

You press K. The keyboard controller sends an interrupt to the chip.

hardwarekerneldaemonframeworkapp

Every layer from the diagram above shows up in that trip: hardware, kernel, a daemon (WindowServer), frameworks (AppKit, Core Text, Metal), your app, and hardware again.

Now the constraint. The author's screen refreshes 120 times per second. One second divided by 120 is 8.33 milliseconds. That whole trip has to finish inside that window, or the panel redraws the old frame and your letter waits for the next refresh at 16.7 ms. Drag the slider:

The 8.33 ms budget on a 120 Hz screen
8.3
16.7
25.0
33.3
Done before the first refresh. The letter appears at 8.3 ms. Smooth.

"This app feels laggy" has a precise meaning now: the app is missing its 8.33 ms budget several frames in a row. The video's framing is that building a fast Mac app is basically the job of staying inside that budget.

The history behind the names: NS, XNU, Darwin, Rosetta#

Every AppKit class starts with NS. The kernel is called XNU. There's something called Darwin. There were "Intel Macs" and now there aren't. None of this makes sense without the history, and the video spends a good chunk on it, so here it is compressed. Filter by thread if it helps.

How the names came about
1985CMU starts Mach, a small kernel that only does memory, threads and messages. Everything else runs as normal programs.
1985Steve Jobs leaves Apple and founds NeXT.
1980sBerkeley ships BSD: a full Unix with files, sockets, signals and a process tree. Big and practical.
1989NeXTSTEP: Mach underneath, BSD on top, Objective-C frameworks whose classes are all prefixed NS.
1996Apple buys NeXT. Classic Mac OS had no memory protection; NeXTSTEP becomes its replacement.
2001Mac OS X 10.0 ships. Mach + BSD + NS frameworks. Kernel named XNU: 'X is Not Unix'.
2006PowerPC → Intel. Universal binaries hold both instruction sets; Rosetta translates old programs live.
2012Carbon (the old Mac API) is deprecated. Cocoa/AppKit is the only path. 'OS X' becomes the name.
2016Renamed again to just 'macOS'.
2019Catalina drops 32-bit programs. Kernel extensions deprecated; system extensions run in user land instead.
2020Intel → Apple Silicon. Same trick again: universal binaries plus Rosetta 2.
2026macOS 27 'Golden Gate' announced: Apple Silicon only, and the last release with Rosetta 2.

The bits I actually needed:

  • NS = NeXTSTEP. Steve Jobs's company NeXT wrote the frameworks in the late 1980s, prefixed every class with NS, and Apple bought NeXT in 1996. NSWindow, NSString, NSArray are all fossils from that. Apple's own frameworks of the time were called Carbon; NeXT's were Cocoa; Carbon lost.
  • Mach + BSD = XNU. Mach was a research kernel from CMU that deliberately did very little (a "microkernel"). BSD was Berkeley's Unix with all the practical stuff: files, networking, processes. NeXT stacked BSD on top of Mach. Apple later moved the BSD code inside the kernel for speed, because crossing the user/kernel boundary for every file operation was too slow. So XNU is a hybrid, not a microkernel, and the name is a joke: "X is Not Unix."
  • Darwin is the open-source part: the kernel plus the Unix layer, published on GitHub under apple-oss-distributions. Darwin has no concept of a window. Everything that draws pixels on a Mac is proprietary and unpublished, which is why the later videos measure the window system instead of reading it.
  • Universal binary and Rosetta are the two answers to the same problem, used twice. A program is instructions for one kind of processor. When Apple switched PowerPC → Intel (2006) and again Intel → Apple Silicon (2020), programs either shipped both sets of instructions in one file (universal binary) or got translated on the fly (Rosetta, then Rosetta 2). The Ghostty binary from the bundle demo is 42 MB because it is really two programs: 21.7 MB of Intel code and 20.3 MB of Apple Silicon code. The author's Mac loads only the second half.
  • macOS 27 "Golden Gate", announced June 2026, is the first release that runs only on Apple Silicon and the last one to include Rosetta 2. This particular era of two-instruction-set programs is ending.

launchd, daemons, and the process tree#

The author's Mac had 738 processes running with maybe ten apps open. Most of the other 700 are daemons: processes with no window that sit in the background doing a job and answering other processes. The thing that indexes your files so Finder search is fast is a daemon. WindowServer, the process that owns your screen, is a daemon too.

The daemon that starts all the other daemons is launchd. It's the first process the kernel starts, it has PID 1, and every other process on the machine is its child, grandchild, or further descendant.

Everything descends from launchd
pidname
launchd PID 1

The first real process the kernel starts. Every other process on the Mac descends from it. On a clean install it directly parents ~480 of ~490 processes.

On a completely clean macOS install the author counted 488 processes, and 481 of them were direct children of launchd. The rest are chains like the one at the bottom of that tree, which also clears up two words I had been using interchangeably:

  • A terminal is a program with a window. It draws text and takes keystrokes. That's all.
  • A shell (zsh on a modern Mac) is a different program with no window. It reads each line you type, figures out which program you mean, and asks the kernel to start it.

So when you type ls and press return: the terminal draws the letters and hands them to the shell; the shell starts a process running the ls program; ls prints the folder's contents back through the shell to the terminal; the terminal draws that. Two programs, talking through a pipe (a "pseudo-terminal", covered in a later video).

Memory: virtual memory and pages#

Every process thinks it has the entire memory to itself, starting at address zero. That belief is false, and it is also the whole point.

Because each process sees its own private address space (virtual memory), the kernel is free to map those pretend addresses to whatever real memory it likes, or even to the disk. It does that mapping in fixed-size chunks called pages. On Apple Silicon a page is 16,384 bytes (16 KB). On Intel Macs and most Linux machines it's 4 KB.

Why does the number matter? Because memory is handed out in whole pages. Try a 100,000-byte file:

Memory is handed out in pages, never in bytes
Page size
pages
7
allocated
114,688 B
wasted at the end
14,688 B
Bigger pages waste more at the tail of every file but need 4× fewer lookups per byte than the other choice. Apple chose fewer lookups.

Bigger pages waste more at the tail of every file, but you need far fewer lookups per byte, and lookups are what cost time. That's the trade-off; the video says the second episode covers why Apple landed on 16 KB. (Remember the Ghostty binary from earlier? Both halves of it were aligned to exactly 16,384 bytes. That number is the page size.)

And this is how two processes are kept out of each other's memory: their pages point at different physical bytes, so there is no address one process could use to reach the other's data.

Threads and the main thread#

A process can have several threads: separate sequences of instructions running the same program's code, each with its own place in the code and its own stack, all sharing the process's memory.

Every Mac app has one special thread, the main thread. It draws the windows and routes events. The video's advice is to keep it light and push heavy work (reading a big file, say) onto another thread. The mistake it warns about is finishing that work on the other thread and then updating the window from there, at which point the app crashes. Only the main thread is allowed to touch the UI. I haven't hit this one yet, but I'm writing it down before I do.

The disk, in one glance#

PathWhat lives there
/SystemmacOS itself. Read-only, even for admins.
/LibraryThings installed for every user: fonts, third-party frameworks, daemon settings
/UsersOne home folder per user
/ApplicationsApp bundles
/usr, /privateThe Unix layer: command-line tools, config, temp files
/etc, /tmpJust pointers into /private, kept for compatibility

The file system that turns disk bytes into these names is APFS (Apple File System). One nice trick it has: copy a 5 GB video and the copy is instant and takes no extra space, because APFS just records that two names point at the same data. Only when you edit one copy does it start storing the difference.

There's also something called a cryptex: a separately-signed disk image holding one slice of the system, mounted at boot under /System/Cryptexes/. The shared cache of all Apple's frameworks (13 files, about 5.8 GB on the author's machine) lives in one. The point is that Apple can update that slice on its own without reinstalling the whole OS. That shared cache is also why starting 100 apps doesn't load AppKit 100 times: it's loaded once and every process is pointed at the same place in memory.

Which version am I even on?#

A Mac has four version numbers, and the video's first hands-on lesson is reading them:

bash
$ sw_vers
ProductName:    macOS
ProductVersion: 26.6.2        # the marketing version, shown in System Settings
BuildVersion:   25G83         # the build, what Apple's engineers use
 
$ uname -a
Darwin ... 25.6.0 ... root:xnu-12377.161.14~...   # kernel version and exact kernel tag
 
$ sysctl hw.pagesize hw.ncpu
hw.pagesize: 16384
hw.ncpu: 15

sysctl reads settings the kernel exposes as key-value pairs: model, memory, page size, core count. On the author's chip there are two tiers of cores, five "super" cores and ten "performance" cores, and the default answer to some questions (like L1 cache size) quietly describes only one tier. Ask per tier and you get two answers: 128 KB for the super cores, 64 KB for the performance cores. Note to self: check which thing a number is measuring before quoting it.

Tools the series will use#

I'm listing these because I did not recognise most of them, and knowing the shape of a tool before you need it helps.

ToolWhat it answers
sysctlWhat does the kernel know about the hardware and its own settings?
powermetricsWhich cores are busy, at what speed, drawing how much power?
otool, dyld_infoWhat's inside this program file? Which libraries does it need?
codesignWho signed this app and what is it allowed to do?
spctlWould macOS let this app open? (the check that runs when you double-click)
fs_usage, dtraceWhat files and system calls is this process touching, right now?
lipoFor universal binaries: which architectures are in here and how big is each?
InstrumentsXcode's profiler: where is the time going?
lldbThe debugger: freeze a process and look inside

Signing, notarization, and the sandbox#

Three security words the video introduces and promises to cover properly later:

  • Signature. Every program on a modern Mac carries a cryptographic signature saying who built it, and the system checks it before running the program. That's what _CodeSignature/ in the bundle was.
  • Notarization. If you distribute an app outside the App Store, you still have to upload it to Apple first. Apple scans it for malware and records "this one passed." When a user opens your app for the first time, macOS looks for that record and refuses to open the app without it. The author's demo app, a Rust program that opens one empty window, went through this so it opens on any Mac without a warning.
  • Sandbox. An app can be confined to a limited environment: specific files, specific devices, and one private folder under your home directory called its container.

How does anyone know this, if Apple doesn't publish it?#

This was my favourite part of the video, because it answers the question I'd been silently asking the whole time. Three ways:

Measuring. Apple doesn't document how the cores inside its chips are built. Someone wrote tiny programs that run one instruction millions of times and read the chip's own performance counters afterward, and from those numbers worked out how wide the pipeline is and how many instructions finish per cycle. Around 35,000 tables of counter values, published.

Reading the source. Apple doesn't publish its GPU driver either. The Asahi Linux project wrote an open one by watching exactly what the closed driver sends to the hardware and reverse-engineering it. That's how anyone knows the GPU draws the screen in small tiles held in a few kilobytes of fast memory.

Building. Across the playlist the author builds a terminal from scratch in Rust (not Swift, on purpose, so none of Apple's abstractions hide what's happening). A terminal touches every layer: it opens a window (AppKit), draws text (Core Text, Metal), starts a shell and talks to it (processes, the kernel), and finally has to be signed and notarized to ship. Rust can call C, Apple's runtime is C underneath, and a set of packages called objc2 bridges the rest. The first commit is an empty coloured window with a title. It is already a real, signed, notarized macOS app.

My cheat sheet#

WordPlain meaning
ProgramA file of instructions on disk
ProcessA program that is running, with its own memory and a PID
BundleA folder that Finder shows as one app icon
KernelThe one piece of software with full access to hardware; the referee
User landEverything that isn't the kernel; runs with limited power
System callA request from a process to the kernel
LibraryCode many programs share; loaded into each process that needs it
FrameworkA library plus its headers and resources, in a bundle
APIThe list of things you're allowed to ask a framework to do
DaemonA background process with no window
launchdPID 1; the ancestor of every other process
ThreadOne sequence of instructions inside a process; the main thread owns the UI
PageThe fixed-size chunk memory is handed out in; 16 KB on Apple Silicon
Virtual memoryEach process's private, pretend view of memory
Universal binaryOne file holding instructions for two kinds of processor
RosettaTranslates instructions for the old processor into the new one, live
SignatureProof of who built a program; checked before it runs
NotarizationApple's scan-and-record step for apps shipped outside the App Store

What's next#

The rest of the playlist goes layer by layer: the chip, then the kernel, then the loader and runtimes, then the frameworks that draw windows and text, then the terminal itself, and finally measuring, signing and shipping the whole thing. I plan to keep writing these up as I go, mostly because writing this one is the first time the pieces stayed put in my head.

If you want the source, watch the video. It's better than my notes; this post exists so the second viewing goes faster.