---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: C-bibliotheken isoleren van Go via gedeeld geheugen op cachelijnniveau'
description: Zero-cgo IPC-runtime, IDL-compiler (hqq-bindgen) en geverifieerde procesbewaker die native C11-bibliotheken uitvoeren in geïsoleerde workerprocessen via gedeelde-geheugenringbuffers.
language: nl
date: 2026-10-18T13:56:46.094643498Z
path: /hqq
go_package: gosuda.org/hqq
go_repourl: https://github.com/gosuda/HQQ.git
category: lowlevel
---

`gosuda.org/hqq` is een zero-cgo runtime voor interprocescommunicatie en een toolchain voor codegeneratie voor Go en C11. Het isoleert native C-bibliotheken in afzonderlijke OS-workerprocessen en verbindt deze met Go-aanroepers via geheugengemapte, op cache-lijnen uitgelijnde shared-memory ringbuffers. Het project combineert twee transportlagen (`StandardLink` voor toegewijde single-producer single-consumer sessies en `Advanced` voor multi-worker MPMC-dispatch), de `hqq-bindgen` IDL-compiler, en een door middel van integriteitscontrole geverifieerde processupervisor (`secureexec` en `launch`).

```bash
go get gosuda.org/hqq
```

## De kosten van het delen van een adresruimte met C

Elke Go-systeemingenieur komt vroeg of laat voor dezelfde splitsing te staan. U bouwt een Go-service die in enkele seconden compileert met `CGO_ENABLED=0`, als één statisch binair bestand wordt uitgeleverd, vanaf een laptop zonder sysroot kruiscompileert naar Linux- of Windows-doelen, en binnen een geheugenveilige, door een garbage collector beheerde heap draait. Vervolgens hebt u een volwassen C-bibliotheek nodig: SQLite voor ingebedde relationele opslag, Zstandard (`libzstd`) voor compressie met woordenboeken, of een hardwareversneld media- of cryptografiebibliotheek.

Het inschakelen van `cgo` om die C-bibliotheek in het Go-proces te linken lost het directe bindingsprobleem op, maar introduceert drie structurele nadelen:

1. **Koppeling van toolchain en kruiscompilatie**: `CGO_ENABLED=1` vereist een C-compiler, headers van de doel-libc en een platformlinker voor elk doelbesturingssysteem en elke doelarchitectuur. Het bouwen van `linux/arm64`- of `windows/arm64`-artefacten vanaf een `darwin/arm64`-werkstation maakt van één `go build`-commando een matrix van kruiscompilers en sysroots.
2. **Overhead van runtime-scheduling**: Elke `cgo`-aanroep passeert de grens van de Go-runtime, schakelt van een groeibare goroutine-stack naar een systeemstack en koppelt een OS-thread (`M`) vast. Bij fijnmazige aanroepen stapelen de overgangskosten (ongeveer 50 tot 150 ns per aanroep) en de schedulercontentie zich snel op.
3. **Instorting van de fout-domein**: C-code draait in dezelfde virtuele adresruimte als de Go-runtime. Een enkele out-of-bounds-schrijfactie, use-after-free, stack-overflow of `abort()` in een C-functie beschadigt de metadata van de Go-heap of beëindigt het volledige serverproces onmiddellijk, waarbij `recover()` wordt omzeild en alle lopende HTTP- of RPC-verzoeken worden meegesleept.

Wanneer teams `cgo` proberen te vermijden, dwingen de gangbare alternatieven tot een andere afweging. Pure-Go mechanische vertalingen van C-codebases behouden `CGO_ENABLED=0`, maar lopen vaak achter op upstream C-releases, bevatten een geëmuleerde virtuele geheugenlaag en alloceren zwaar onder belasting. Het verplaatsen van de C-bibliotheek naar een helperproces via loopback-TCP of Unix-domeinsockets herstelt de foutisolatie, maar elke aanroep kost twee kernel-contextwisselingen, kopieën van socketbuffers en wire-serialisatie.

Wij hebben HQQ gebouwd nadat we een directe vraag hadden gesteld: wat als de C-bibliotheek in een afzonderlijk OS-proces met een eigen virtuele adresruimte zou leven, terwijl Go en C frames rechtstreeks uitwisselen via gedeelde CPU-cache-lijnen zonder op het snelle pad de kernel te betreden?

| Integratiemodel                    | `CGO_ENABLED=0` | Foutisolatie          | Syscalls op het snelle pad | Heap-allocaties per aanroep | Upstream C11-getrouwheid |
| :--------------------------------- | :-------------- | :-------------------- | -------------------------: | --------------------------: | :----------------------- |
| **`cgo` In-Process Linking**       | Nee             | Geen (gedeelde heap)  |                          0 |                    0 tot 1+ | Exact upstream C         |
| **Socket / Pipe Subprocess RPC**   | Ja              | Procesgrens           |                    2 tot 4 |   Meerdere (framing + I/O) | Exact upstream C         |
| **Mechanische C-naar-Go-vertaling** | Ja             | In-process Go         |                          0 |          Hoog bij complexe aanroepen | Geëmuleerde C-runtime |
| **HQQ Shared-Memory IPC**          | Ja              | Procesgrens           |        0 (adaptief spinnen) |      0 (`StandardLink`) | Exact upstream C         |

## Architectuuroverzicht: vier lagen over procesgrenzen heen

HQQ scheidt procesisolatie in vier combineerbare lagen, zodat de mechaniek van shared-memory-ringbuffers, getypeerde RPC-codegeneratie, verificatie van binaire integriteit en supervisie van kindprocessen onafhankelijk blijven.

```text
+-------------------------------------------------------------------+
| Go Application Process (CGO_ENABLED=0)                            |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Generated Go Client   |     | Process Supervisor            |  |
|  | (gosuda.org/hqq/      |     | (gosuda.org/hqq/launch +      |  |
|  |  bindgen/rt)          |     |  gosuda.org/hqq/secureexec)   |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|  +-----------v-----------+                     | Anonymous Pipe   |
|  | Transport Tier        |                     | Lifeline         |
|  | (StandardLink /       |                     | (--parent-stdin) |
|  |  Advanced MPMC)       |                     |                  |
+--------------+---------------------------------+------------------+
               |                                 |
===============|========= Shared Memory =========|===================
               |     (mmap / CreateFileMapping)  |
+--------------v---------------------------------v------------------+
| C11 Worker Process (Isolated Address Space)                       |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Generated C11 Server  |---->| Generation-Checked Handle     |  |
|  | & Batch Shims         |     | Table (hqq_rpc.c)             |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|              +----------------+----------------+                  |
|                               |                                   |
|                     +---------v---------+                         |
|                     | Native C Library  |                         |
|                     | (SQLite, Zstd...) |                         |
|                     +-------------------+                         |
+-------------------------------------------------------------------+
```

### 1. Shared-Memory-transport (`gosuda.org/hqq` en `c/`)

Het basispakket `gosuda.org/hqq` en het C11-equivalent in `c/` implementeren twee shared-memory-transportmodi via POSIX `mmap` (Linux en macOS) en Win32 `CreateFileMappingW` (Windows). Elk gedeeld segment begint met een geverifieerde `HQQ_STL3`-superblock die de protocolversie, de geometrie van de buffers en de endpoint-PID's vastlegt voordat enige ringindex wordt gelezen.

Alle hot control words en slotheaders zijn uitgelijnd op grenzen van 64 bytes (cache-lijnen), zodat producer- en consumercores nooit om dezelfde cache-lijn concurreren via false sharing.

```text
StandardLink Slot Layout (64-Byte Cache-Line Aligned)
Byte Offset:  0x00                0x08                0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Header Line:  | seq (u64 atomic)  | len (u32) | flags | reserved (zero-padded)      |
              +-------------------+-------------------+-----------------------------+
Payload Area: | payload bytes [0 .. BufferSize - 1] (directly encoded / decoded)    |
              +---------------------------------------------------------------------+
```

| Veld      | Offset |         Grootte | Betekenis                                                                                                                   |
| :-------- | -----: | --------------: | :-------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00` |             8 B | Monotone sequentieteller. Oneven waarden markeren een schrijfactie in uitvoering; even waarden publiceren een vastgelegd frame via store-release. |
| `len`     | `0x08` |             4 B | Lengte in bytes van de payload die in de slot is geschreven (`0 <= len <= BufferSize`).                                     |
| `flags`   | `0x0C` |             4 B | Besturingsbits van het frame (vervolg, foutstatus of melding van afsluiting).                                               |
| `payload` | `0x40` | `BufferSize`    | Aaneengesloten payloadgebied dat ter plaatse wordt gelezen en geschreven zonder tussentijdse kopieën.                       |

Aanroepers kiezen tussen twee transportlagen, afhankelijk van de concurrentiestructuur:

- **`StandardLink` (1:1 SPSC-sessie)**: Koppelt een client- en een serversessie via toegewijde verzoek- en antwoordslots die worden beschermd door een sequence lock (`SeqLock`). Op het snelle pad serialiseert de schrijver rechtstreeks in de gemapte slot en legt vast met een atomaire store-release; de lezer spint kort in gebruikersruimte voordat hij terugvalt op OS-wekprimitieven (`futex` op Linux, `__ulock_wait` op macOS, `WaitOnAddress` op Windows). Een enkele frame-round-trip voltooit in ongeveer 200 ns met `0 allocs/op`.
- **`Advanced` (MPMC-dispatchring)**: Multiplext `M` gelijktijdige Go-aanroepers over `N` C-workerthreads met behulp van een lock-free multi-producer multi-consumer-indieningsring, een gedeelde slotpool en een geïndexeerde completion-tabel. Aanroepers reserveren een slot, publiceren de slotindex naar de ring en wachten op hun completion-entry, terwijl workerthreads verzoeken uit de ring halen en deze buiten volgorde afhandelen.

Beide lagen volgen de levendigheid van de peer doorlopend. Elk endpoint werkt via `Heartbeat()` een atomaire, monotone tijdstempel bij en controleert tijdens het wachten `PeerAlive(timeout)`. Als een workerproces midden in een aanroep crasht, detecteert de peer de verouderde heartbeat of de dode PID onmiddellijk, breekt blokkerende wachtacties af met `ErrPeerGone` en ondersteunt atomaire sessieovername via `Takeover`, zonder achtergebleven shared-memory-bestanden.

### 2. Zero-allocation RPC en handles met generatiecontrole (`gosuda.org/hqq/bindgen`)

Shared-memory-framing met de hand schrijven voor een C-bibliotheek met tientallen functies is vervelend en foutgevoelig. `hqq-bindgen` is een schemacompiler die een Interface Definition Language-bestand (`.idl`) leest (of er direct een opzet van maakt vanuit een C-`.h`-header via `hqq-bindgen init`) en een zero-cgo Go-clientpakket genereert, samen met een C11-serverskelet.

De gevaarlijkste fout bij elke C-binding is het blootstellen van ruwe C-pointers (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) over de taalgrens heen. Als een aanroeper een pointer doorgeeft nadat deze is vrijgegeven, een pointer doorgeeft die aan een andere sessie toebehoort, of een geheugenadres als geheel getal beschadigt, dereferentieert de C-bibliotheek ongeldig geheugen.

`hqq-bindgen` verbiedt ruwe pointers op de wire. In plaats daarvan modelleert de IDL stateful C-resources als getypeerde `handle`-declaraties:

```text
service sqlite3 {
    handle Db;
    handle Stmt @owned_by(Db);

    fn open_v2(path: string, flags: i32) -> result<Db, i32>;
    fn prepare_one(db: Db, sql: string) -> result<Stmt, i32>;
    fn finalize(stmt: @consume Stmt) -> result<void, i32>;
    fn close(db: @consume Db) -> result<void, i32>;
}
```

Op de wire is elke handle een 64-bits geheel getal dat een 32-bits tabelindex en een 32-bits generatieteller die niet nul is, bevat: `(uint64_t)generation << 32 | index`. De C-runtime (`c/src/hqq_rpc.c`) onderhoudt per sessie een handletabel met drie invarianten:

1. **Generatievalidatie**: Elke opzoekactie verifieert dat de slot bezet is, dat de opgeslagen generatie overeenkomt met de bovenste 32 bits van de handle, en dat het typelabel van de handle overeenkomt met het parametertype (`Db` versus `Stmt`). Het hergebruiken van een gesloten handle mislukt onmiddellijk met `HQQRPC_E_BAD_HANDLE`, nog voordat de C-bibliotheek wordt aangeroepen.
2. **Hiërarchisch eigendom (`@owned_by`)**: Omdat `Stmt` is geannoteerd met `@owned_by(Db)`, doorloopt het sluiten van een `Db`-handle automatisch alle levende kind-`Stmt`-handles die aan die `Db` toebehoren, en voert hun destructors uit in omgekeerde volgorde van allocatie (`sqlite3_finalize` vóór `sqlite3_close_v2`).
3. **Opruimingsveegactie bij sessie-einde**: Als een Go-client de verbinding verbreekt of crasht zonder zijn handles te sluiten, veegt de destructor van de C-serversessie de volledige handletabel van de sessie in omgekeerde volgorde leeg, waarmee nul gelekte C-allocaties over clientherstarts heen wordt gegarandeerd.

Voordat een sessie zijn eerste RPC-aanroep verwerkt, wisselen client en server een 32-byte SHA-256-interfacedigest uit, berekend over de gecanonicaliseerde IDL (met voorvoegsel `HQQ-BINDGEN-IDL-V1\n`). Als de Go-client en de C-worker zijn gecompileerd vanuit onverenigbare IDL-schema's, wijst de handshake de verbinding af bij de opstart.

### 3. Manipulatievrije binaire uitvoering (`gosuda.org/hqq/secureexec`)

Het verplaatsen van C-code naar een workerbinair bestand roept een vraag over de toeleveringsketen op: hoe weet het Go-proces dat het workerbestand op schijf niet is gewijzigd of vervangen tussen verificatie en uitvoering?

Het aanroepen van `crypto/sha256` op een bestandspad, gevolgd door `os/exec.Command(path).Start()`, laat een Time-of-Check to Time-of-Use (TOCTOU)-venster open waarin een ander proces het bestand kan overschrijven of verwisselen voordat de kernel het uitvoert. `gosuda.org/hqq/secureexec` sluit dit venster:

- Op Linux opent `secureexec` de binaire, streamt de inhoud naar een anonieme geheugenbestandsdescriptor die is aangemaakt via `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)`, terwijl de SHA-256-hash wordt berekend, verifieert de hash tegen een vastgelegde 32-byte digest of een door Ed25519 ondertekend releasemanifest (`cmd/hqq-manifest`), past `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` toe zodat geen enkel proces op het systeem de pagina's kan wijzigen, en start het kindproces vanaf de verzegelde descriptor.
- Op macOS en Windows materialiseert `secureexec` geverifieerde binaire bestanden in cachemappen met vergrendelde rechten, die worden geïdentificeerd op basis van de SHA-256-digest, en verifieert de bestandsgrootte en digestintegriteit vóór de start.

### 4. Supervisie van kindprocessen via een ouder-lifeline (`gosuda.org/hqq/launch`)

Wanneer een Go-applicatie een C-workerproces op de achtergrond start, mag de worker nooit als wees na zijn ouder blijven voortbestaan, zelfs wanneer het Go-ouderproces wordt beëindigd met `SIGKILL` (`kill -9`) en geen `defer`-opruimhandlers kan uitvoeren.

`gosuda.org/hqq/launch` dwingt levenscycluskoppeling af via een anonieme OS-pipe die met de vlag `--parent-stdin` aan de standaardinvoer van de worker wordt doorgegeven. Het Go-ouderproces houdt het schrijfeinde van de pipe vast en schrijft er nooit naartoe; de C-worker bewaakt het leeseinde op een speciale lifeline-thread. Als het Go-proces normaal afsluit, in paniek raakt of door de kernel van het besturingssysteem wordt gedood (`SIGKILL` of de OOM-killer), sluit de kernel het schrijfdescriptor onmiddellijk. De C-worker ontvangt binnen microseconden `EOF` op de pipe, onderbreekt eventuele actieve aanroepen, voert zijn destructors van sessiehandles uit, verwijdert de shared-memory-bestanden en beëindigt zich netjes.

## Snelstart: een gegenereerde service aanroepen

Met `hqq-bindgen` en `launch` lijkt het aanroepen van een geïsoleerde C-worker vanuit Go op een gewone getypeerde Go-client, terwijl `CGO_ENABLED=0` behouden blijft:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"gosuda.org/hqq/bindgen/rt"
	"gosuda.org/hqq/launch"
	"gosuda.org/hqq/secureexec"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	hash, err := secureexec.ParseHash("7649b1b45dacd934cde8149f35f4fcc3937f6486fbac23ce976466719b3752bb")
	if err != nil {
		log.Fatal(err)
	}

	listener, err := launch.NewListenerName("demo")
	if err != nil {
		log.Fatal(err)
	}

	srv, err := launch.Start(ctx, launch.Config{
		Binary:   "/path/to/verified-worker",
		SHA256:   hash,
		Listener: listener,
		Args:     []string{listener},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer srv.Close()

	fmt.Printf("worker running on shared-memory listener %s (opts=%+v)\n", listener, rt.SessionOptions{})
}
```

## Repositorystructuur

Elk subsysteem in `gosuda.org/hqq` is georganiseerd in gerichte pakketten met expliciete grenzen:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) and low-level shared-memory ring primitives
├── advanced.go / slotpool.go  # Advanced (MPMC) dispatcher, slot pool, and completion table
├── shm_*.go / wait_*.go       # OS-specific mmap/CreateFileMapping and futex/ulock/WaitOnAddress
├── c/                         # C11 transport library (hqq.c) and RPC server runtime (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL parser, verifier (V1-V41), and Go/C11 code generator
│   └── rt/                    # Zero-allocation Go client runtime for generated services
├── secureexec/                # SHA-256 / Ed25519 manifest verification and memfd-sealed execution
├── launch/                    # Worker process supervisor with --parent-stdin lifeline management
├── cmd/hqq-manifest/          # CLI tool for generating and signing release binary manifests
└── spec/                      # Executable wire specifications and golden test vectors (BINDGEN.md)
```

Om te zien hoe HQQ drop-in productievervangers aandrijft voor twee van de meest gebruikte `cgo`-bibliotheken in het Go-ecosysteem, bekijkt u de bijbehorende pakketten die op deze stack zijn gebouwd:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Drop-in `database/sql`-driver compatibel met `github.com/mattn/go-sqlite3`, die native C-SQLite `3.54.1` via shared memory uitvoert met gepijplijnde batch-prefetching.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Drop-in vervanging van `github.com/DataDog/zstd`, die native C `libzstd 1.5.7` uitvoert in een geïsoleerde shared-memory-worker met zero-allocation-contexten en bulkbatching.
- **GitHub Repository**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
