My Notes on 'How macOS Works': Programs, Processes, and the Kernel
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:
| Part | Its one job | On a MacBook |
|---|---|---|
| Processor (CPU) | Run instructions, one after another | Part of the M-series chip |
| Memory (RAM) | Hold the data the processor is working on right now | On the same chip as the processor |
| Disk | Hold data when the power is off | The SSD |
| Screen | Show pixels | A 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.
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.
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 runsResources/: images, icons, translationsFrameworks/: 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:
Hands out CPU time, memory pages, files and devices. Everything above it has to ask.
- · Mach: memory, threads, messages
- · BSD: files, sockets, signals
- · IOKit: devices
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:
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:
| Framework | What you ask it for |
|---|---|
| AppKit | Windows, menus, buttons, keyboard and mouse events |
| SwiftUI | A 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 Animation | Layers, animation, and handing finished pixels to the screen |
| Metal | Talking 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?
You press K. The keyboard controller sends an interrupt to the chip.
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:
"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.
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,NSArrayare 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.
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:
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#
| Path | What lives there |
|---|---|
/System | macOS itself. Read-only, even for admins. |
/Library | Things installed for every user: fonts, third-party frameworks, daemon settings |
/Users | One home folder per user |
/Applications | App bundles |
/usr, /private | The Unix layer: command-line tools, config, temp files |
/etc, /tmp | Just 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:
$ 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: 15sysctl 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.
| Tool | What it answers |
|---|---|
sysctl | What does the kernel know about the hardware and its own settings? |
powermetrics | Which cores are busy, at what speed, drawing how much power? |
otool, dyld_info | What's inside this program file? Which libraries does it need? |
codesign | Who signed this app and what is it allowed to do? |
spctl | Would macOS let this app open? (the check that runs when you double-click) |
fs_usage, dtrace | What files and system calls is this process touching, right now? |
lipo | For universal binaries: which architectures are in here and how big is each? |
| Instruments | Xcode's profiler: where is the time going? |
lldb | The 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#
| Word | Plain meaning |
|---|---|
| Program | A file of instructions on disk |
| Process | A program that is running, with its own memory and a PID |
| Bundle | A folder that Finder shows as one app icon |
| Kernel | The one piece of software with full access to hardware; the referee |
| User land | Everything that isn't the kernel; runs with limited power |
| System call | A request from a process to the kernel |
| Library | Code many programs share; loaded into each process that needs it |
| Framework | A library plus its headers and resources, in a bundle |
| API | The list of things you're allowed to ask a framework to do |
| Daemon | A background process with no window |
| launchd | PID 1; the ancestor of every other process |
| Thread | One sequence of instructions inside a process; the main thread owns the UI |
| Page | The fixed-size chunk memory is handed out in; 16 KB on Apple Silicon |
| Virtual memory | Each process's private, pretend view of memory |
| Universal binary | One file holding instructions for two kinds of processor |
| Rosetta | Translates instructions for the old processor into the new one, live |
| Signature | Proof of who built a program; checked before it runs |
| Notarization | Apple'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.