golang/go
Runtime
The Go runtime, in src/runtime/, is the system that gives Go its characteristic feel: cheap goroutines, channels, garbage collection, growable stacks. Every Go binary embeds it. This page is a tour of its structure and the rules that govern its code.
For the unwritten rules — write barriers, nosplit, system stack vs. user stack — read src/runtime/HACKING.md first. This page is the index over what's where.
Purpose
The runtime is responsible for:
- Bootstrap: the entry path from
_rt0_*_<os>(per-arch assembly stubs) intoruntime.main. - Goroutine scheduling: the G-M-P scheduler in
proc.go. - Memory management: the multi-level allocator (
malloc.go,mcache.go,mcentral.go,mheap.go). - Garbage collection: concurrent mark-and-sweep with a hybrid write barrier (
mgc*.go,mwbbuf.go,mbarrier.go). - Stack management: small growable stacks, copying when they overflow (
stack.go). - Synchronization primitives: the runtime side of
sync.Mutex, channels, semaphores (chan.go,sema.go,lock_*.go). - OS abstraction: per-OS, per-arch source files (
os_linux.go,sys_darwin_amd64.s, etc.) wrap syscalls and threading primitives. - Panic/defer/recover:
panic.go. - The execution tracer and pprof support.
Directory layout
The runtime is mostly flat — files at src/runtime/*.go, with assembly siblings:
src/runtime/
├── HACKING.md # Required reading for runtime hackers
├── proc.go # G-M-P scheduler (8000+ lines)
├── runtime1.go # Misc init, GODEBUG parsing
├── runtime2.go # Type definitions: G, M, P, schedt, ...
├── chan.go # Channel implementation
├── select.go # select statement
├── map*.go # Map implementation (Swiss tables, group-of-8)
├── slice.go # Slice ops (growslice, append support)
├── string.go # String ops
├── iface.go # Interface representation + assertions
├── alg.go # Hash + equality dispatch tables
├── panic.go # panic, recover, defer
├── error.go # runtime error types
├── stack.go # Stack growth, copying
├── traceback.go # Traceback printing (60000+ lines!)
├── trace.go # Execution tracer (v2)
├── trace*.go # Tracer support
├── debug.go # //go:linkname-exposed knobs for runtime/debug
├── debugcall.go # delve-friendly call injection
├── debuglog*.go # Internal high-perf log buffer
│
├── malloc.go # Allocator front door, tiny allocator
├── mcache.go # Per-P allocation cache
├── mcentral.go # Per-size-class central pool
├── mheap.go # Heap state, page allocator, large objects
├── mfixalloc.go # Fixed-size allocator for runtime structs
├── mranges.go # AddrRange data structure
├── mgc.go # GC mark phase, controller
├── mgcmark.go # Concurrent marker
├── mgcsweep.go # Concurrent sweeper
├── mgcstack.go # Stack scanning
├── mgcpacer.go # GC pacing algorithm
├── mgcwork.go # Work queues for the marker
├── mbarrier.go # Write/read barriers
├── mwbbuf.go # Write-barrier buffer (per-P)
├── mfinal.go # Finalizers
├── mbitmap.go # Heap bitmap (pointer info)
├── sizeclasses.go # Generated size class tables
├── arena.go # Experimental arena allocator
├── pinner.go # runtime.Pinner (pin objects from cgo)
│
├── netpoll*.go # Network poller (epoll/kqueue/IOCP)
├── netpoll_<os>.go # Per-OS netpoll backend
│
├── lock_futex.go, lock_sema.go # Low-level locks per OS
├── sema.go # Semaphores backing sync.Mutex etc.
├── time.go # Timer wheel, time.Sleep support
├── tickspersecond.go # Time calibration
│
├── preempt.go, preempt_*.s # Async + cooperative preemption
├── stubs*.go, stubs*.s # Compiler-emitted helper signatures
├── asm_*.s # Per-arch assembly: entry, traps, atomics
├── sys_<os>_<arch>.s # Per-OS+arch syscall stubs
├── os_<os>.go # OS-level threading and signals
├── signal_*.go, signal_*.s # Signal handling
├── vdso_linux*.go # Linux VDSO calls (clock_gettime, etc.)
│
├── race*.go, race/ # Race detector integration (TSan)
├── asan*.go, asan/ # Address sanitizer integration
├── msan*.go, msan/ # Memory sanitizer integration
│
├── coverage/ # New whole-binary coverage support
├── debug/ # The runtime/debug user-facing API
├── pprof/ # The runtime/pprof user-facing API
├── trace/ # The runtime/trace user-facing API
├── metrics/ # The runtime/metrics user-facing API
└── _mkmalloc/ # Generator scripts for malloc tablesThe G-M-P scheduler
The scheduler lives almost entirely in src/runtime/proc.go (the file is 8000+ lines). The shapes of the three core types are in src/runtime/runtime2.go:
g— a goroutine: stack bounds, instruction pointer, scheduling state, current M, defer chain.m— an OS thread: thread-local storage, current G, current P, signal stack.p— a "processor," i.e., a scheduling slot: local runqueue, per-P allocation cache (mcache), GC bg worker info, defer pool.
graph LR
G1[G runnable] -.goes onto.-> RunQ[P local runqueue]
RunQ -.scheduled by.-> M1[M holding P]
M1 --> CPU[OS thread / CPU]
M1 -.steals from.-> RunQOther[Another P's runqueue]
M1 -.parks if no work.-> Idle[Idle M pool]
SysCall[G in syscall] -->|hands P off| M2[Another M acquires P]Key entry points in proc.go:
schedinit— early bootstrap, called once.runtime.main— the runtime's "main" goroutine; runsinitfunctions thenmain.main.newproc— implements thegokeyword.gopark/goready— the universal sleep/wake primitive used by channels, mutexes, timers.findRunnable— the heart of the scheduler: looks at local queue, global queue, network poller, GC, and finally steals.sysmon— single-threaded monitor goroutine; preempts long-running Gs, retakes Ps from blocked Ms, runs the network poller, advances scavenger.goexit— the function each goroutine "returns into." Cleans up and reschedules.
runtime/HACKING.md documents the rules (system stack vs. user stack, write-barrier discipline, nosplit chains, locking order).
Memory management
graph TD
User[malloc / new / make] --> Mallocgc[runtime.mallocgc]
Mallocgc -->|≤ 16 B| Tiny[Tiny allocator: combine small objects]
Mallocgc -->|≤ 32 KB| Small[Small: per-P mcache]
Mallocgc -->|> 32 KB| Large[Large: direct from mheap]
Tiny --> Mcache[mcache: per-P spans]
Small --> Mcache
Mcache -->|empty| Mcentral[mcentral: per-size-class pool]
Mcentral -->|empty| Mheap[mheap: page allocator]
Mheap --> OS[OS: mmap / VirtualAlloc]Levels:
- Tiny allocator (
malloc.go): combines several ≤ 16-byte allocations into a single 16-byte block. Per-P. - Per-P
mcache(mcache.go): holds onemspanper size class. Allocation is bumped pointer + free bit. mcentral(mcentral.go): one per size class; pools partial spans.mheap(mheap.go): the global heap. Manages the address-space arenas and large objects directly.- Page allocator (
mpagealloc*.go): large radix-tree-of-bitmaps for free pages. - Scavenger: returns pages to the OS when memory pressure drops.
Garbage collector
Concurrent, non-moving, tri-color mark-and-sweep with a hybrid write barrier. Phases:
- STW sweep termination. Finishes any leftover sweeping from the previous cycle.
- Concurrent mark. Stops the world briefly to enable the write barrier, then runs the marker concurrently with the mutator. Workers take work from the global gray queue and per-P caches. The compiler emits write barriers around every non-local pointer write.
- STW mark termination. Drains remaining work, disables the write barrier.
- Concurrent sweep. Reclaims unmarked spans lazily, in the background and on allocation.
Pacing (mgcpacer.go) decides when to start the next cycle so that allocation does not outrun the marker. Driven by GOGC and GOMEMLIMIT.
Channels and synchronization
chan.goimplements buffered/unbuffered channels with a single mutex and a wait queue.select.goimplementsselectvia a small selectcase array, randomized polling order, and a parking primitive.sema.gounderliessync.Mutex,sync.RWMutex,sync.WaitGroup,sync.Cond. Sleeping goroutines are queued on a per-address tree (treap).lock_futex.go(Linux) andlock_sema.go(others) provide the low-level mutex used inside the runtime itself.
Stack management
Stacks start small (~2 KiB) and grow. The compiler inserts a prologue that compares the SP to the G's stack bound; if low, it calls morestack, which copies the stack to a larger one. //go:nosplit skips the prologue (used by code that must not grow).
Code in stack.go handles the copying; stkframe.go handles per-frame layout.
Network poller
netpoll*.go integrates the OS event loop with the goroutine scheduler:
- One Goroutine sleeps in
netpoll(timeout). - File descriptors register with
netpollopen; the per-OS backend wires them to epoll/kqueue/IOCP. - When events arrive, ready Gs are unparked.
This is what makes net/http and friends able to handle 10k connections per goroutine without thread-per-connection.
Cgo
When a Go function calls into C, control reaches runtime.cgocall (cgocall.go), which switches to the M's g0 stack, calls asmcgocall, and on return resumes the user G. The reverse direction (cgo callbacks back into Go) is handled by runtime/cgo/.
Tracer
The execution tracer (trace.go, traceruntime.go, traceevent.go, …) writes a binary event log per P. cmd/trace and go tool trace parse it. Used to understand scheduling, GC, and goroutine behavior.
Per-OS, per-arch files
Naming conventions in src/runtime/:
os_linux.go,os_darwin.go,os_windows.go— OS bootstrap and threading.os_linux_amd64.go,os_linux_arm64.go— narrower per-OS+arch glue.defs_linux_amd64.go— per-OS+arch struct definitions matching the OS ABI.sys_linux_amd64.s,sys_darwin_amd64.s— syscall and trampoline assembly.signal_linux_amd64.go,signal_darwin_amd64.go— signal handler entry.asm_amd64.s,asm_arm64.s— generic per-arch entry, atomics, and trampolines.
Special calling conventions
The Go runtime uses Go's standard calling convention internally, with a few extras:
//go:nosplit— no stack-grow prologue. Required for any function that runs on a stack with limited room (e.g., signal handlers, syscall trampolines).//go:systemstack— the function must run on the M'sg0(system) stack. The runtime checks this at runtime.//go:nowritebarrier///go:nowritebarrierrec— the function must not contain pointer writes that would emit a write barrier (used in GC-internal code).//go:linkname— wires a Go declaration to an unexported symbol elsewhere. Used heavily forsync,time,os,reflect, andsyscallto access runtime internals without exporting them.
Key abstractions
| Abstraction | Where | Purpose |
|---|---|---|
g (goroutine) |
runtime/runtime2.go |
Lightweight thread |
m (machine) |
runtime/runtime2.go |
OS thread |
p (processor) |
runtime/runtime2.go |
Scheduling context |
schedt |
runtime/runtime2.go |
Global scheduler state |
mspan |
runtime/mheap.go |
Run of pages of one size class |
mcache |
runtime/mcache.go |
Per-P allocator cache |
mheap |
runtime/mheap.go |
Global heap |
gcWork |
runtime/mgcwork.go |
Per-P GC work queue |
wbBuf |
runtime/mwbbuf.go |
Per-P write-barrier buffer |
hchan |
runtime/chan.go |
Channel header |
_defer |
runtime/runtime2.go |
Deferred call record |
_panic |
runtime/runtime2.go |
In-progress panic |
Key source files
| File | Purpose |
|---|---|
src/runtime/HACKING.md |
The runtime author's bible |
src/runtime/runtime2.go |
All core type definitions |
src/runtime/proc.go |
The scheduler |
src/runtime/malloc.go |
Allocator front door |
src/runtime/mheap.go |
Heap and page allocator |
src/runtime/mgc.go |
GC controller |
src/runtime/mgcmark.go |
Concurrent marker |
src/runtime/mgcpacer.go |
Pacing algorithm |
src/runtime/chan.go |
Channels |
src/runtime/sema.go |
Semaphores (mutex backing) |
src/runtime/panic.go |
panic, recover, defer |
src/runtime/stack.go |
Stack growth |
src/runtime/traceback.go |
Traceback printer |
src/runtime/asm_<arch>.s |
Per-arch entry and atomics |
src/runtime/sys_<os>_<arch>.s |
Per-OS+arch syscall stubs |
Integration points
- The compiler (compiler) emits calls into the runtime for channel ops, map ops, type assertions, panic/recover, write barriers.
- The standard library uses
//go:linknameto call unexported runtime functions forsync,os,time,reflect,syscall,net. runtime/debug,runtime/pprof,runtime/trace,runtime/metricsare user-facing APIs that wrap runtime internals.- The race detector, MSan, and ASan are integrated through the
race*.go,msan*.go,asan*.gofiles.
Entry points for modification
- Scheduler change: start in
proc.go. ReadHACKING.mdfirst. - GC change:
mgc.go,mgcmark.go,mgcpacer.go. Be ready to argue about GC pause and throughput trade-offs in the design doc you'll need to write. - Allocator change:
malloc.go,mheap.go. Size-class table changes go in_mkmalloc/. - New OS or arch: copy an existing pair of
os_*.go+sys_*_*.sand adapt. Build matrix tests will tell you what's missing. - Adding a new
runtime/metricscounter: declare it inruntime/metrics.go, plumb throughruntime/metrics/description.go.
Where to read next
src/runtime/HACKING.md— required reading.- Compiler — how compiler-emitted code calls into the runtime.
- Standard library — which packages reach into the runtime via
//go:linkname. - Debugging —
GODEBUG,GOTRACEBACK,gctrace, the tracer.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.