---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Isolering af C-biblioteker fra Go via delt hukommelse på cache-linjeniveau'
description: Runtime for IPC uden cgo, IDL-kompilator (hqq-bindgen) og verificeret procesovervågning, der eksekverer native C11-biblioteker i isolerede arbejdsprocesser via delte hukommelsesringbuffere.
language: da
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` er en zero-cgo runtime for interproceskommunikation og et kodegenereringsværktøjssæt for Go og C11. Det isolerer native C-biblioteker i separate OS-arbejdsprocesser og forbinder dem med Go-kaldere via hukommelsesafbildede, cache-linje-justerede delte hukommelsesringbuffere. Projektet kombinerer to transportniveauer (`StandardLink` for dedikerede single-producer single-consumer-sessioner og `Advanced` for multi-worker MPMC-dispatch), `hqq-bindgen` IDL-compileren og en manipulationsverificeret procesovervåger (`secureexec` og `launch`).

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

## Omkostningen ved at dele et adresserum med C

Enhver Go-systemingeniør står før det samme vejvalg. Du bygger en Go-tjeneste, der kompileres på få sekunder med `CGO_ENABLED=0`, distribueres som en enkelt statisk binærfil, krydskompileres fra en laptop til Linux- eller Windows-mål uden et sysroot og kører i en hukommelsessikker, affaldsindsamlet heap. Derefter har du brug for et modent C-bibliotek: SQLite til indlejret relationel lagring, Zstandard (`libzstd`) til ordbogsbaseret komprimering eller et hardwareaccelereret medie- eller kryptografibibliotek.

Ved at aktivere `cgo` for at linke C-biblioteket ind i Go-processen løses det umiddelbare bindingsproblem, men det introducerer tre strukturelle ulemper:

1. **Kobling mellem værktøjskæde og krydskompilering**: `CGO_ENABLED=1` kræver en C-compiler, C-bibliotekets headere for målet og en platformslinker for hvert mål-OS og hver arkitektur. Opbygning af `linux/arm64`- eller `windows/arm64`-artefakter fra en `darwin/arm64`-arbejdsstation gør et enkelt `go build`-kald til en matrix af krydskompilere og sysroots.
2. **Overhead ved runtime-planlægning**: Hvert `cgo`-kald krydser grænsen til Go-runtime, skifter fra en voksende goroutine-stak til en systemstak og fastlåser en OS-tråd (`M`). For finkornede kald akkumuleres overgangsomkostningen (~50 til 150 ns per kald) og planlæggerkonkurrencen hurtigt.
3. **Kollaps af fejldomæne**: C-kode kører i samme virtuelle adresserum som Go-runtime. Et enkelt skrivning uden for grænserne, use-after-free, stakoverløb eller `abort()` inde i en C-funktion korrumperer Go-heap-metadata eller afslutter hele serverprocessen med det samme, under omgåelse af `recover()` og tager alle igangværende HTTP- eller RPC-anmodninger med sig.

Når teams forsøger at undgå `cgo`, tvinger de almindelige alternativer til et andet kompromis. Rene Go-mekaniske oversættelser af C-kodebaser bevarer `CGO_ENABLED=0`, men halter ofte efter upstream C-udgivelser, medfører et emuleret virtuelt hukommelseslag og allokerer kraftigt under belastning. Flytning af C-biblioteket til en hjælpeproces over loopback-TCP eller Unix-domænesockets genskaber fejlisolering, men hvert kald betaler for to kernelkontekstskift, kopiering af socketbuffere og wire-serialisering.

Vi byggede HQQ ud fra et direkte spørgsmål: Hvad hvis C-biblioteket levede i en separat OS-proces med sit eget virtuelle adresserum, mens Go og C udvekslede rammer direkte gennem delte CPU-cache-linjer uden at gå ind i kernen på den hurtige sti?

| Integrationsmodel                  | `CGO_ENABLED=0` | Fejlisolering      | Syscalls på hurtig sti | Heap-allokeringer pr. kald | Upstream C11-troskab |
| :--------------------------------- | :-------------- | :----------------- | ---------------------: | -------------------------: | :------------------- |
| **`cgo` In-Process-linking**       | Nej             | Ingen (delt heap)  |                      0 |                    0 til 1+ | Præcis upstream C    |
| **Socket / Pipe-underprocess-RPC** | Ja              | Procesgrænse       |                    2 til 4 |   Flere (indramning + I/O) | Præcis upstream C    |
| **Mekanisk C-til-Go-oversættelse** | Ja              | Go i processen     |                      0 |     Høj ved komplekse kald | Emuleret C-runtime   |
| **HQQ Shared-Memory IPC**          | Ja              | Procesgrænse       |  0 (adaptiv spin)      |        0 (`StandardLink`)  | Præcis upstream C    |

## Arkitektonisk oversigt: Fire lag på tværs af procesgrænser

HQQ adskiller procesisolering i fire sammensættelige lag, så mekanikken for delte hukommelsesringbuffere, typede RPC-kodegenereringer, binær integritetsverifikation og supervision af underprocesser forbliver uafhængige.

```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. Transport via delt hukommelse (`gosuda.org/hqq` og `c/`)

Basispakken `gosuda.org/hqq` og dens C11-modstykke i `c/` implementerer to transporttilstande via delt hukommelse over POSIX `mmap` (Linux og macOS) og Win32 `CreateFileMappingW` (Windows). Hvert delte segment begynder med en verificeret `HQQ_STL3`-superblok, der fastlægger protokolversion, bufferGeometri og endpoint-PID'er, før noget ringindeks læses.

Alle varme kontrolord og slot-headere er justeret til 64-byte cache-linjegrænser, så producent- og forbrugerkerner aldrig konkurrerer om samme cache-linje via falsk deling.

```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)    |
              +---------------------------------------------------------------------+
```

| Felt      | Offset |         Størrelse | Semantik                                                                                                                       |
| :-------- | -----: | ----------------: | :----------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00` |               8 B | Monotont sekvenstæller. Ulige værdier markerer en igangværende skrivning; lige værdier offentliggør en committet ramme via store-release. |
| `len`     | `0x08` |               4 B | Payloadens bytelængde skrevet i slotten (`0 <= len <= BufferSize`).                                                            |
| `flags`   | `0x0C` |               4 B | Rammekontrolbits (fortsættelse, fejlstatus eller lukningsbesked).                                                              |
| `payload` | `0x40` | `BufferSize`      | Sammenhængende payloadområde, der læses og skrives in place uden mellemliggende kopier.                                        |

Kaldere vælger mellem to transportniveauer afhængigt af samtidighedsstrukturen:

- **`StandardLink` (1:1 SPSC-session)**: Parrer en klient- og en serversession over dedikerede anmodnings- og svarslots beskyttet af en sekvenslås (`SeqLock`). På den hurtige sti serialiserer skriveren direkte ind i den afbildede slot og committer med et atomisk store-release; læseren spinner kort i brugerrummet, før den falder tilbage til OS-vækkeprimitiver (`futex` på Linux, `__ulock_wait` på macOS, `WaitOnAddress` på Windows). En enkelt rammes round trip fuldføres på ~200 ns med `0 allocs/op`.
- **`Advanced` (MPMC-dispatch-ring)**: Multiplekser `M` samtidige Go-kaldere over `N` C-arbejdertråde ved hjælp af en låsefri multi-producer multi-consumer-indsendelsesring, en delt slotpulje og en indekseret færdiggørelsestabel. Kaldere reserverer en slot, offentliggør slotindekset til ringen og venter på deres færdiggørelsespost, mens arbejdertrådene udtager og færdiggør anmodninger uden for rækkefølge.

Begge niveauer overvåger løbende peers levetid. Hvert endpoint opdaterer et atomisk monotont tidsstempel via `Heartbeat()` og undersøger `PeerAlive(timeout)` under ventetiden. Hvis en arbejdsproces går ned midt i et kald, registrerer peeren det forældede heartbeat eller den døde PID med det samme, afbryder blokerede ventetider med `ErrPeerGone` og understøtter atomisk session-`Takeover` uden at efterlade hængende delte hukommelsesfiler.

### 2. Nul-allokerings-RPC og generationskontrollerede handles (`gosuda.org/hqq/bindgen`)

At skrive indramning af delt hukommelse i hånden til et C-bibliotek med snesevis af funktioner er kedeligt og fejlbehæftet. `hqq-bindgen` er en skemakompiler, der læser en Interface Definition Language-fil (`.idl`) (eller opretter en direkte fra en C-`.h`-header via `hqq-bindgen init`) og udsender en zero-cgo Go-klientpakke sammen med et C11-serverskelet.

Den farligste fejl i enhver C-binding er at eksponere rå C-pointere (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) over sproggrænsen. Hvis en kalder sender en pointer efter at have frigivet den, sender en pointer, der tilhører en anden session, eller korrumperer en heltalsadresse, dereferencer C-biblioteket ugyldig hukommelse.

`hqq-bindgen` forbyder rå pointere på wiren. I stedet modellerer IDL'en stateful C-ressourcer som typede `handle`-deklarationer:

```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>;
}
```

På wiren er hver handle et 64-bit heltal, der pakker et 32-bit tabelindeks og en 32-bit generationstæller forskellig fra nul: `(uint64_t)generation << 32 | index`. C-runtime (`c/src/hqq_rpc.c`) vedligeholder en handle-tabel pr. session med tre invarianter:

1. **Generationsvalidering**: Hvert opslag verificerer, at slotten er optaget, at dens lagrede generation svarer til de øverste 32 bit af handlen, og at dens handle-typetag svarer til parametertypen (`Db` vs. `Stmt`). Genbrug af en lukket handle fejler straks med `HQQRPC_E_BAD_HANDLE`, før der kaldes ind i C-biblioteket.
2. **Hierarkisk ejerskab (`@owned_by`)**: Fordi `Stmt` er annoteret med `@owned_by(Db)`, går lukning af en `Db`-handle automatisk igennem alle levende børne-`Stmt`-handles, der ejes af denne `Db`, og kører deres destruktorer i omvendt allokeringsrækkefølge (`sqlite3_finalize` før `sqlite3_close_v2`).
3. **Oprydningsfejning ved session-nedlukning**: Hvis en Go-klient afbryder forbindelsen eller går ned uden at lukke sine handles, fejer sessionsdestruktoren på C-serveren hele session-handle-tabellen i omvendt rækkefølge og garanterer nul lækkede C-allokeringer på tværs af klientgenstarter.

Før en session behandler sit første RPC-kald, udveksler klient og server et 32-byte SHA-256-interfacedigest beregnet over den kanoniserede IDL (præfiks `HQQ-BINDGEN-IDL-V1\n`). Hvis Go-klienten og C-arbejderen er kompileret fra inkompatible IDL-skemaer, afviser håndtrykket forbindelsen ved opstart.

### 3. Manipulationsfri binær eksekvering (`gosuda.org/hqq/secureexec`)

At flytte C-kode ind i en arbejderbinærfil rejser et spørgsmål om forsyningskæden: Hvordan ved Go-processen, at arbejderbinærfilen på disken ikke blev ændret eller erstattet mellem verifikation og eksekvering?

Kald af `crypto/sha256` på en filsti efterfulgt af `os/exec.Command(path).Start()` efterlader et Time-of-Check to Time-of-Use (TOCTOU)-vindue, hvor en anden proces kan overskrive eller udskifte filen, før kernen eksekverer den. `gosuda.org/hqq/secureexec` lukker dette vindue:

- På Linux åbner `secureexec` binærfilen og streamer dens indhold ind i en anonym hukommelsesfil-deskriptor oprettet via `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)`, mens dens SHA-256-hash beregnes. Den verificerer hashen mod et fastlagt 32-byte digest eller et Ed25519-signeret udgivelsesmanifest (`cmd/hqq-manifest`), anvender `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)`, så ingen proces på systemet kan ændre siderne, og starter barnet fra den forseglede deskriptor.
- På macOS og Windows materialiserer `secureexec` verificerede binærfiler i tilladelseslåste cache-mapper, der indekseres efter SHA-256-digest, og verificerer filstørrelse og digest-integritet før opstart.

### 4. Forælder-livline-procesovervågning (`gosuda.org/hqq/launch`)

Når en Go-applikation starter en baggrunds-C-arbejderproces, må arbejderen aldrig overleve sin forælder som en forældreløs proces, selv hvis Go-forælderen afsluttes med `SIGKILL` (`kill -9`) og ikke kan køre `defer`-oprydningshandlere.

`gosuda.org/hqq/launch` håndhæver livstidskobling via en anonym OS-pipe, der sendes til arbejderens standardinput med flaget `--parent-stdin`. Go-forælderen holder pipens skrivende ende og skriver aldrig til den; C-arbejderen overvåger den læsende ende på en dedikeret livlinetråd. Hvis Go-processen afslutter normalt, går i panik eller dræbes af OS-kernen (`SIGKILL` eller OOM-dræberen), lukker kernen straks skrivedeskriptoren. C-arbejderen modtager `EOF` på pipen inden for mikrosekunder, afbryder eventuelle aktive kald, kører sine session-handle-destruktorer, fjerner de delte hukommelsesfiler (unlink) og afslutter rent.

## Hurtigstart: Kald af en genereret tjeneste

Med `hqq-bindgen` og `launch` ser kald af en isoleret C-arbejder fra Go ud som en almindelig typet Go-klient, samtidig med at `CGO_ENABLED=0` bevares:

```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{})
}
```

## Repository-struktur

Hvert delsystem i `gosuda.org/hqq` er organiseret i fokuserede pakker med eksplicitte grænser:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) og lavniveau-primitiver til delte hukommelsesringbuffere
├── advanced.go / slotpool.go  # Advanced (MPMC)-dispatcher, slotpulje og færdiggørelsestabel
├── shm_*.go / wait_*.go       # OS-specifik mmap/CreateFileMapping og futex/ulock/WaitOnAddress
├── c/                         # C11-transportbibliotek (hqq.c) og RPC-serverruntime (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL-parser, verifikator (V1-V41) og Go/C11-kodegenerator
│   └── rt/                    # Nul-allokerings Go-klientruntime for genererede tjenester
├── secureexec/                # SHA-256 / Ed25519-manifestverifikation og memfd-forseglet eksekvering
├── launch/                    # Arbejderprocesovervåger med --parent-stdin-livlinehåndtering
├── cmd/hqq-manifest/          # CLI-værktøj til generering og signering af manifester for binære udgivelser
└── spec/                      # Eksekverbare wire-specifikationer og golden test-vektorer (BINDGEN.md)
```

For at se hvordan HQQ driver drop-in produktionserstatninger for to af de mest udbredte `cgo`-biblioteker i Go-økosystemet, se de medfølgende pakker bygget på denne stak:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Drop-in `database/sql`-driver kompatibel med `github.com/mattn/go-sqlite3`, der kører native C SQLite `3.54.1` over delt hukommelse med pipelinet batch-prefetching.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Drop-in erstatning for `github.com/DataDog/zstd`, der kører native C `libzstd 1.5.7` i en isoleret arbejder med delt hukommelse, med nul-allokerings-kontekster og bulk-batching.
- **GitHub-repository**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
