Open-Source Wikis

/

Go

/

Components

/

Runtime

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:

  1. Bootstrap: the entry path from _rt0_*_<os> (per-arch assembly stubs) into runtime.main.
  2. Goroutine scheduling: the G-M-P scheduler in proc.go.
  3. Memory management: the multi-level allocator (malloc.go, mcache.go, mcentral.go, mheap.go).
  4. Garbage collection: concurrent mark-and-sweep with a hybrid write barrier (mgc*.go, mwbbuf.go, mbarrier.go).
  5. Stack management: small growable stacks, copying when they overflow (stack.go).
  6. Synchronization primitives: the runtime side of sync.Mutex, channels, semaphores (chan.go, sema.go, lock_*.go).
  7. OS abstraction: per-OS, per-arch source files (os_linux.go, sys_darwin_amd64.s, etc.) wrap syscalls and threading primitives.
  8. Panic/defer/recover: panic.go.
  9. 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 tables

The 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; runs init functions then main.main.
  • newproc — implements the go keyword.
  • 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 one mspan per 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:

  1. STW sweep termination. Finishes any leftover sweeping from the previous cycle.
  2. 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.
  3. STW mark termination. Drains remaining work, disables the write barrier.
  4. 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.go implements buffered/unbuffered channels with a single mutex and a wait queue.
  • select.go implements select via a small selectcase array, randomized polling order, and a parking primitive.
  • sema.go underlies sync.Mutex, sync.RWMutex, sync.WaitGroup, sync.Cond. Sleeping goroutines are queued on a per-address tree (treap).
  • lock_futex.go (Linux) and lock_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's g0 (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 for sync, time, os, reflect, and syscall to 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:linkname to call unexported runtime functions for sync, os, time, reflect, syscall, net.
  • runtime/debug, runtime/pprof, runtime/trace, runtime/metrics are user-facing APIs that wrap runtime internals.
  • The race detector, MSan, and ASan are integrated through the race*.go, msan*.go, asan*.go files.

Entry points for modification

  • Scheduler change: start in proc.go. Read HACKING.md first.
  • 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_*_*.s and adapt. Build matrix tests will tell you what's missing.
  • Adding a new runtime/metrics counter: declare it in runtime/metrics.go, plumb through runtime/metrics/description.go.
  • 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.
  • DebuggingGODEBUG, GOTRACEBACK, gctrace, the tracer.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Runtime – Go wiki | Factory