0.8 The Go labs: the OS from inside a program
The commands in this guide show the operating system from the outside:
ps, strace, free, docker stats. Part 20 shows it from the
inside, with Go programs that make the kernel's behaviour visible: how many
OS threads a goroutine really costs, what happens when a container's memory
limit meets Go's garbage collector, why PID 1 must reap zombies, how to write
a file that survives a power cut, and a working container runtime in about
150 lines.
Every lab was run on Linux (in Docker on an Apple-silicon Mac) while writing
this guide, and the outputs shown are real. Most labs are Linux-only
(//go:build linux). On a Mac, cross-compile and run them in a container:
mkdir -p ~/oslabs && cd ~/oslabs && go mod init oslabs
mkdir threads # save the lab as threads/main.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/threads ./threads # GOARCH=amd64 on Intel
docker run --rm -v "$PWD/bin":/b debian:bookworm /b/threads
| Lab | Chapter | What it shows |
|---|---|---|
threads |
73 | Goroutines vs OS threads: sleeping, network-blocked, syscall-blocked |
pid1 |
74 | Zombies piling up under a PID 1 that doesn't reap, and three fixes |
gomaxprocs |
75 | Go's container-aware GOMAXPROCS, and CPU throttling when it's wrong |
memlimit |
76 | An OOM kill at 300 MB, prevented by GOMEMLIMIT, and what that costs |
atomicwrite |
77 | Durable, atomic file replacement, and what fsync costs |
fdlimit |
78 | RLIMIT_NOFILE, EMFILE, and Go raising its own limit |
gops |
79 | ps/top rebuilt from /proc |
minicontainer |
80 | Namespaces + chroot + cgroup v2 = a container |
profiled |
81 | A CPU hot spot and a goroutine leak, found with pprof |
| Dockerfile, unit, manifest | 82 | Shipping a Go service: 15 MB distroless image, systemd, Kubernetes |