---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Isolierung von C-Bibliotheken gegenüber Go über cache-line-geteilten Speicher'
description: Zero-cgo-IPC-Laufzeitumgebung, IDL-Compiler (hqq-bindgen) und verifizierter Prozess-Supervisor, die native C11-Bibliotheken in isolierten Worker-Prozessen über Shared-Memory-Ringpuffer ausführen.
language: de
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` ist eine zero-cgo Inter-Process-Communication-Laufzeitumgebung und Code-Generierungs-Toolchain für Go und C11. Sie isoliert native C-Bibliotheken in getrennten Betriebssystem-Worker-Prozessen und verbindet sie über speicherabgebildete, auf Cache-Zeilen ausgerichtete Shared-Memory-Ringpuffer mit Go-Aufrufern. Das Projekt kombiniert zwei Transportstufen (`StandardLink` für dedizierte Single-Producer-Single-Consumer-Sitzungen und `Advanced` für Multi-Worker-MPMC-Dispatch), den IDL-Compiler `hqq-bindgen` sowie einen manipulationsgeprüften Prozess-Supervisor (`secureexec` und `launch`).

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

## Die Kosten der gemeinsamen Nutzung eines Adressraums mit C

Jeder Go-Systemingenieur steht früher oder später vor derselben Weggabelung. Sie entwickeln einen Go-Dienst, der mit `CGO_ENABLED=0` in Sekunden kompiliert, als einzelnes statisches Binary ausgeliefert wird, ohne Sysroot von einem Laptop aus für Linux- oder Windows-Zielplattformen cross-kompiliert wird und innerhalb eines speichersicheren, garbage-collected Heaps läuft. Dann benötigen Sie eine ausgereifte C-Bibliothek: SQLite für eingebettete relationale Speicherung, Zstandard (`libzstd`) für Dictionary-Kompression oder eine hardwarebeschleunigte Medien- oder Kryptografie-Bibliothek.

Die Aktivierung von `cgo`, um diese C-Bibliothek in den Go-Prozess einzubinden, löst das unmittelbare Bindungsproblem, führt jedoch drei strukturelle Nachteile ein:

1. **Kopplung von Toolchain und Cross-Kompilierung**: `CGO_ENABLED=1` erfordert für jedes Zielbetriebssystem und jede Zielarchitektur einen C-Compiler, die Header der Ziel-libc und einen plattformspezifischen Linker. Der Bau von `linux/arm64`- oder `windows/arm64`-Artefakten von einer `darwin/arm64`-Workstation verwandelt einen einzigen `go build`-Befehl in eine Matrix aus Cross-Compilern und Sysroots.
2. **Overhead der Laufzeit-Schedulierung**: Jeder `cgo`-Aufruf überschreitet die Grenze zur Go-Laufzeit, wechselt von einem wachsenden Goroutine-Stack auf einen Systemstack und fixiert einen Betriebssystem-Thread (`M`). Bei feingranularen Aufrufen summieren sich die Übergangskosten (ca. 50 bis 150 ns pro Aufruf) und die Scheduler-Konkurrenz rasch.
3. **Zusammenbruch der Fehlerdomäne**: C-Code läuft im selben virtuellen Adressraum wie die Go-Laufzeit. Ein einzelner Out-of-Bounds-Schreibzugriff, Use-after-Free, Stack-Überlauf oder `abort()` innerhalb einer C-Funktion beschädigt die Metadaten des Go-Heaps oder beendet den gesamten Serverprozess augenblicklich, wobei `recover()` umgangen wird und jede laufende HTTP- oder RPC-Anfrage mit ihm endet.

Versuchen Teams, `cgo` zu vermeiden, erzwingen die üblichen Alternativen einen anderen Kompromiss. Rein in Go implementierte mechanische Übersetzungen von C-Codebasen bewahren `CGO_ENABLED=0`, hinken jedoch häufig den aktuellen C-Releases hinterher, enthalten eine emulierte virtuelle Speicherschicht und allozieren unter Last stark. Das Auslagern der C-Bibliothek in einen Hilfsprozess über Loopback-TCP oder Unix-Domain-Sockets stellt die Fehlerisolation wieder her, doch jeder Aufruf zahlt für zwei Kontextwechsel im Kernel, Kopien in Socket-Puffer und Wire-Serialisierung.

Wir haben HQQ entwickelt, nachdem wir eine direkte Frage gestellt hatten: Was wäre, wenn die C-Bibliothek in einem separaten Betriebssystemprozess mit eigenem virtuellen Adressraum lebt, während Go und C Frames direkt über gemeinsam genutzte CPU-Cache-Zeilen austauschen, ohne auf dem schnellen Pfad in den Kernel zu wechseln?

| Integrationsmodell                  | `CGO_ENABLED=0` | Fehlerisolation    | Syscalls auf dem schnellen Pfad | Heap-Allokationen pro Aufruf | Treue zu upstream C11 |
| :---------------------------------- | :-------------- | :----------------- | ------------------------------: | ---------------------------: | :-------------------- |
| **`cgo` In-Process-Linking**        | Nein            | Keine (geteilter Heap) |                               0 |                    0 bis 1+ | Exakt upstream C      |
| **Socket- / Pipe-Subprozess-RPC**   | Ja              | Prozessgrenze      |                         2 bis 4 |  Mehrere (Framing + E/A)     | Exakt upstream C      |
| **Mechanische C-zu-Go-Übersetzung** | Ja              | In-Process-Go      |                               0 |     Hoch bei komplexen Aufrufen | Emulierte C-Laufzeit |
| **HQQ Shared-Memory-IPC**           | Ja              | Prozessgrenze      |  0 (adaptives Spinning)         |     0 (`StandardLink`)       | Exakt upstream C      |

## Architekturüberblick: Vier Schichten über Prozessgrenzen hinweg

HQQ trennt die Prozessisolation in vier kombinierbare Schichten, sodass Shared-Memory-Ring-Mechanik, typisierte RPC-Codegenerierung, binäre Integritätsprüfung und die Aufsicht über Kindprozesse unabhängig voneinander bleiben.

```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` und `c/`)

Das Basispaket `gosuda.org/hqq` und sein C11-Gegenstück in `c/` implementieren zwei Shared-Memory-Transportmodi über POSIX `mmap` (Linux und macOS) und Win32 `CreateFileMappingW` (Windows). Jedes gemeinsame Segment beginnt mit einem verifizierten `HQQ_STL3`-Superblock, der Protokollversion, Puffergeometrie und Endpunkt-PIDs festlegt, bevor ein Ring-Index gelesen wird.

Alle heißen Steuerwörter und Slot-Header sind an 64-Byte-Cache-Zeilengrenzen ausgerichtet, sodass Producer- und Consumer-Kerne niemals durch False Sharing um dieselbe Cache-Zeile konkurrieren.

```text
StandardLink-Slot-Layout (an 64-Byte-Cache-Zeilen ausgerichtet)
Byte-Offset:  0x00                0x08                0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Header-Zeile: | seq (u64 atomic)  | len (u32) | flags | reserved (mit Nullen aufgefüllt) |
              +-------------------+-------------------+-----------------------------+
Nutzlastbereich: | Nutzlast-Bytes [0 .. BufferSize - 1] (direkt kodiert / dekodiert)  |
              +---------------------------------------------------------------------+
```

| Feld      | Offset |         Größe | Bedeutung                                                                                                                  |
| :-------- | -----: | ------------: | :------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00` |           8 B | Monotoner Sequenzzähler. Ungerade Werte kennzeichnen einen laufenden Schreibvorgang; gerade Werte veröffentlichen einen committeten Frame per Store-Release. |
| `len`     | `0x08` |           4 B | Länge der in den Slot geschriebenen Nutzlast in Bytes (`0 <= len <= BufferSize`).                                         |
| `flags`   | `0x0C` |           4 B | Frame-Steuerbits (Fortsetzung, Fehlerstatus oder Abschaltmeldung).                                                         |
| `payload` | `0x40` | `BufferSize` | Zusammenhängender Nutzlastbereich, der ohne Zwischenkopien direkt gelesen und geschrieben wird.                            |

Aufrufer wählen je nach Nebenläufigkeitsstruktur zwischen zwei Transportstufen:

- **`StandardLink` (1:1-SPSC-Sitzung)**: Verbindet eine Client- und eine Server-Sitzung über dedizierte Request- und Response-Slots, die durch einen Sequenzlock (`SeqLock`) geschützt sind. Auf dem schnellen Pfad serialisiert der Schreiber direkt in den abgebildeten Slot und committet mit einem atomaren Store-Release; der Leser dreht kurz im Benutzerraum, bevor er auf Betriebssystem-Weckprimitive zurückgreift (`futex` unter Linux, `__ulock_wait` unter macOS, `WaitOnAddress` unter Windows). Ein Round-Trip mit einem einzelnen Frame wird in ca. 200 ns mit `0 allocs/op` abgeschlossen.
- **`Advanced` (MPMC-Dispatch-Ring)**: Multiplext `M` nebenläufige Go-Aufrufer auf `N` C-Worker-Threads, wobei ein lock-freier Multi-Producer-Multi-Consumer-Submission-Ring, ein gemeinsamer Slot-Pool und eine indizierte Completion-Tabelle verwendet werden. Aufrufer reservieren einen Slot, veröffentlichen den Slot-Index im Ring und warten auf ihren Completion-Eintrag, während Worker-Threads Anfragen außer der Reihe entnehmen und abschließen.

Beide Stufen überwachen die Lebendigkeit des Gegenübers kontinuierlich. Jeder Endpunkt aktualisiert über `Heartbeat()` einen atomaren, monotonen Zeitstempel und prüft während des Wartens `PeerAlive(timeout)`. Stürzt ein Worker-Prozess mitten in einem Aufruf ab, erkennt die Gegenseite den veralteten Heartbeat oder die tote PID unverzüglich, bricht blockierte Wartevorgänge mit `ErrPeerGone` ab und unterstützt eine atomare Sitzungsübernahme (`Takeover`), ohne verwaiste Shared-Memory-Dateien zurückzulassen.

### 2. Zuweisungsfreier RPC und generierungsgeprüfte Handles (`gosuda.org/hqq/bindgen`)

Das manuelle Schreiben des Shared-Memory-Frammings für eine C-Bibliothek mit Dutzenden von Funktionen ist mühsam und fehleranfällig. `hqq-bindgen` ist ein Schema-Compiler, der eine Interface-Definition-Language-Datei (`.idl`) liest (oder direkt aus einem C-`.h`-Header über `hqq-bindgen init` eine solche erzeugt) und ein zero-cgo Go-Client-Paket zusammen mit einem C11-Server-Gerüst ausgibt.

Der gefährlichste Fehler bei jeder C-Bindung besteht darin, rohe C-Zeiger (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) über die Sprachgrenze hinweg offenzulegen. Übergibt ein Aufrufer einen Zeiger nach dessen Freigabe, übergibt er einen Zeiger, der zu einer anderen Sitzung gehört, oder verfälscht er eine ganzzahlige Adresse, dereferenziert die C-Bibliothek ungültigen Speicher.

`hqq-bindgen` verbietet rohe Zeiger auf dem Draht. Stattdessen modelliert die IDL zustandsbehaftete C-Ressourcen als typisierte `handle`-Deklarationen:

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

Auf dem Draht ist jedes Handle eine 64-Bit-Ganzzahl, die einen 32-Bit-Tabellenindex und einen 32-Bit-Generationszähler ungleich null enthält: `(uint64_t)generation << 32 | index`. Die C-Laufzeit (`c/src/hqq_rpc.c`) verwaltet eine Handle-Tabelle pro Sitzung mit drei Invarianten:

1. **Generationsvalidierung**: Jede Suche prüft, ob der Slot belegt ist, ob seine gespeicherte Generation mit den oberen 32 Bit des Handles übereinstimmt und ob sein Handle-Typ-Tag dem Parametertyp entspricht (`Db` vs. `Stmt`). Die Wiederverwendung eines geschlossenen Handles schlägt sofort mit `HQQRPC_E_BAD_HANDLE` fehl, bevor die C-Bibliothek aufgerufen wird.
2. **Hierarchische Eigentümerschaft (`@owned_by`)**: Da `Stmt` mit `@owned_by(Db)` annotiert ist, durchläuft das Schließen eines `Db`-Handles automatisch alle lebenden untergeordneten `Stmt`-Handles, die diesem `Db` gehören, und führt deren Destruktoren in umgekehrter Allokationsreihenfolge aus (`sqlite3_finalize` vor `sqlite3_close_v2`).
3. **Bereinigung der Sitzung**: Trennt sich ein Go-Client oder stürzt er ab, ohne seine Handles zu schließen, durchläuft der Sitzungsdestruktor des C-Servers die gesamte Handle-Tabelle der Sitzung in umgekehrter Reihenfolge und garantiert so über Client-Neustarts hinweg null verlorene C-Allokationen.

Bevor eine Sitzung den ersten RPC-Aufruf verarbeitet, tauschen Client und Server einen 32-Byte-SHA-256-Interface-Digest aus, der über die kanonisierte IDL (Präfix `HQQ-BINDGEN-IDL-V1\n`) berechnet wird. Wurden der Go-Client und der C-Worker aus inkompatiblen IDL-Schemata kompiliert, weist der Handshake die Verbindung beim Start zurück.

### 3. Manipulationssichere Binärausführung (`gosuda.org/hqq/secureexec`)

Das Auslagern von C-Code in ein Worker-Binary wirft eine Lieferketten-Frage auf: Wie erkennt der Go-Prozess, dass das Worker-Binary auf dem Datenträger zwischen Prüfung und Ausführung nicht verändert oder ersetzt wurde?

Der Aufruf von `crypto/sha256` auf einem Dateipfad, gefolgt von `os/exec.Command(path).Start()`, lässt ein Time-of-Check-to-Time-of-Use-(TOCTOU-)Fenster offen, in dem ein anderer Prozess die Datei überschreiben oder austauschen kann, bevor der Kernel sie ausführt. `gosuda.org/hqq/secureexec` schließt dieses Fenster:

- Unter Linux öffnet `secureexec` das Binary, streamt seinen Inhalt in einen anonymen Speicherdateideskriptor, der über `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` erzeugt wird, und berechnet dabei seinen SHA-256-Hash. Anschließend prüft es den Hash gegen einen fixierten 32-Byte-Digest oder ein Ed25519-signiertes Release-Manifest (`cmd/hqq-manifest`), wendet `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` an, sodass kein Prozess im System die Seiten verändern kann, und startet den Kindprozess aus dem versiegelten Deskriptor.
- Unter macOS und Windows materialisiert `secureexec` verifizierte Binaries in berechtigungsgesperrten Cache-Verzeichnissen, die nach SHA-256-Digest benannt sind, und prüft Dateigröße und Digest-Integrität vor dem Start.

### 4. Aufsicht über Kindprozesse mit Parent-Lifeline (`gosuda.org/hqq/launch`)

Startet eine Go-Anwendung einen Hintergrund-C-Worker-Prozess, darf der Worker seinen Elternprozess niemals als verwaisten Prozess überleben, selbst wenn der Go-Elternprozess mit `SIGKILL` (`kill -9`) beendet wird und keine `defer`-Bereinigungsroutinen ausführen kann.

`gosuda.org/hqq/launch` erzwingt die Lebensdauerkopplung über eine anonyme OS-Pipe, die dem Worker über das Flag `--parent-stdin` als Standardeingabe übergeben wird. Der Go-Elternprozess hält das Schreibende der Pipe und schreibt niemals hinein; der C-Worker überwacht das Leseende in einem dedizierten Lifeline-Thread. Beendet sich der Go-Prozess normal, gerät in eine Panic oder wird er vom Betriebssystemkern beendet (`SIGKILL` oder OOM-Killer), schließt der Kernel den Schreibdeskriptor sofort. Der C-Worker empfängt innerhalb von Mikrosekunden `EOF` an der Pipe, unterbricht aktive Aufrufe, führt seine Destruktoren für Sitzungs-Handles aus, entfernt die Shared-Memory-Dateien und beendet sich sauber.

## Schnellstart: Aufruf eines generierten Dienstes

Mit `hqq-bindgen` und `launch` wirkt der Aufruf eines isolierten C-Workers aus Go wie ein gewöhnlicher typisierter Go-Client, während `CGO_ENABLED=0` erhalten bleibt:

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

Jedes Teilsystem in `gosuda.org/hqq` ist in fokussierte Pakete mit klar definierten Grenzen gegliedert:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) und systemnahe Shared-Memory-Ring-Primitive
├── advanced.go / slotpool.go  # Advanced-(MPMC-)Dispatcher, Slot-Pool und Completion-Tabelle
├── shm_*.go / wait_*.go       # Betriebssystemspezifisches mmap/CreateFileMapping und futex/ulock/WaitOnAddress
├── c/                         # C11-Transportbibliothek (hqq.c) und RPC-Server-Laufzeit (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL-Parser, Verifier (V1-V41) und Go/C11-Codegenerator
│   └── rt/                    # Zuweisungsfreie Go-Client-Laufzeit für generierte Dienste
├── secureexec/                # SHA-256-/Ed25519-Manifestprüfung und memfd-versiegelte Ausführung
├── launch/                    # Worker-Prozessaufsicht mit --parent-stdin-Lifeline-Verwaltung
├── cmd/hqq-manifest/          # CLI-Werkzeug zur Erzeugung und Signierung von Release-Binary-Manifesten
└── spec/                      # Ausführbare Wire-Spezifikationen und Golden-Testvektoren (BINDGEN.md)
```

Um zu sehen, wie HQQ direkt einsetzbare produktionsreife Ersatzimplementierungen für zwei der meistgenutzten `cgo`-Bibliotheken im Go-Ökosystem antreibt, sehen Sie sich die auf diesem Stack aufbauenden Begleitpakete an:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Drop-in-`database/sql`-Treiber, kompatibel mit `github.com/mattn/go-sqlite3`, der nativ C-SQLite `3.54.1` über Shared Memory mit pipelinierendem Batch-Prefetching ausführt.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Drop-in-Ersatz für `github.com/DataDog/zstd`, der nativ C-`libzstd 1.5.7` in einem isolierten Shared-Memory-Worker mit zuweisungsfreien Kontexten und Bulk-Batching ausführt.
- **GitHub-Repository**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
