---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Isolering av C-biblioteker fra Go over Cache-Line Shared Memory'
description: Kjøretid for IPC uten cgo, IDL-kompilator (hqq-bindgen) og verifisert prosessovervåker som kjører native C11-biblioteker i isolerte arbeiderprosesser over delte minneringbuffere.
language: "no"
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 kjøretid for interprosesskommunikasjon og verktøykjede for kodegenerering for Go og C11. Den isolerer native C-biblioteker i separate operativsystemprosesser for arbeidere og kobler dem til Go-kallere via minnekartlagte, cache-linjejusterte delte minneringer. Prosjektet kombinerer to transportnivåer (`StandardLink` for dedikerte single-producer single-consumer-sesjoner og `Advanced` for multi-worker MPMC-dispatch), IDL-kompilatoren `hqq-bindgen`, og en prosessovervåker med manipulasjonsverifisering (`secureexec` og `launch`).

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

## Kostnaden ved å dele adresserom med C

Enhver Go-systemingeniør står før slutt det samme veivalget. Du bygger en Go-tjeneste som kompileres på sekunder med `CGO_ENABLED=0`, leveres som en enkelt statisk binærfil, krysskompileres fra en bærbar datamaskin til Linux- eller Windows-mål uten sysroot, og kjører i en minnesikker, søppelinnsamlet heap. Deretter trenger du et modent C-bibliotek: SQLite for innebygd relasjonslagring, Zstandard (`libzstd`) for ordbokbasert komprimering, eller et maskinvareakselerert medie- eller kryptografibibliotek.

Ved å aktivere `cgo` for å lenke C-biblioteket inn i Go-prosessen løses det umiddelbare bindingsproblemet, men tre strukturelle ulemper introduseres:

1. **Kobling mellom verktøykjede og krysskompilering**: `CGO_ENABLED=1` krever en C-kompilator, headere for målets libc og en plattformlinker for hvert mål-OS og hver arkitektur. Bygging av `linux/arm64`- eller `windows/arm64`-artefakter fra en `darwin/arm64`-arbeidsstasjon gjør en enkelt `go build`-kommando om til en matrise av krysskompilatorer og sysroots.
2. **Overhead ved kjøretidsskedulering**: Hvert `cgo`-kall krysser grensen til Go-kjøretiden, bytter fra en voksende goroutine-stakk til en systemstakk og låser en operativsystemtråd (`M`). For finkornede kall akkumuleres overgangskostnaden (~50 til 150 ns per kall) og konkurranse om skedulereren raskt.
3. **Kollaps av feildomene**: C-kode kjører i samme virtuelle adresserom som Go-kjøretiden. Én enkelt skriving utenfor grensene, bruk etter frigjøring, stakkoverflyt eller `abort()` inne i en C-funksjon korrumperer metadata i Go-heapen eller avslutter hele tjenerprosessen umiddelbart, forbikobler `recover()` og tar med seg alle pågående HTTP- eller RPC-forespørsler.

Når team forsøker å unngå `cgo`, tvinger de vanlige alternativene frem et annet kompromiss. Rene Go-baserte mekaniske oversettelser av C-kodebaser bevarer `CGO_ENABLED=0`, men henger ofte etter oppstrøms C-utgivelser, har et emulert virtuelt minnelag og allokerer mye under last. Å flytte C-biblioteket til en hjelpeprosess over loopback-TCP eller Unix-domenesockets gjenoppretter feilisolasjon, men hvert kall betaler for to kontekstbytter i kjernen, kopiering av socketbuffere og wire-serialisering.

Vi bygde HQQ etter å ha stilt et direkte spørsmål: Hva om C-biblioteket lå i en separat operativsystemprosess med sitt eget virtuelle adresserom, mens Go og C utvekslet rammer direkte gjennom delte CPU-cache-linjer uten å gå inn i kjernen på den raske banen?

| Integrasjonsmodell                 | `CGO_ENABLED=0` | Feilisolasjon      | Systemkall på raskt spor | Heap-allokeringer per kall | Trofasthet mot oppstrøms C11 |
| :--------------------------------- | :-------------- | :----------------- | -----------------------: | -------------------------: | :--------------------------- |
| **`cgo` med lenking i prosessen**  | Nei             | Ingen (delt heap)  |                        0 |                   0 til 1+ | Eksakt oppstrøms C           |
| **Socket / Pipe-underprosess-RPC** | Ja              | Prosessgrense      |                    2 til 4 |     Flere (rammer + I/O)   | Eksakt oppstrøms C           |
| **Mekanisk C-til-Go-oversettelse** | Ja              | Go i prosessen     |                        0 |     Høy ved komplekse kall | Emulert C-kjøretid           |
| **HQQ delt-minne-IPC**             | Ja              | Prosessgrense      |  0 (adaptiv spinn)       |   0 (`StandardLink`)       | Eksakt oppstrøms C           |

## Arkitekturoversikt: Fire lag på tvers av prosessgrenser

HQQ deler prosessisolasjon inn i fire komponerbare lag, slik at mekanikken for delte minneringer, typet RPC-kodegenerering, verifisering av binærintegritet og overvåking av barneprosesser forblir uavhengige.

```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 over delt minne (`gosuda.org/hqq` og `c/`)

Basispakken `gosuda.org/hqq` og dens C11-motstykke i `c/` implementerer to transportmoduser for delt minne over POSIX `mmap` (Linux og macOS) og Win32 `CreateFileMappingW` (Windows). Hvert delte segment begynner med en verifisert `HQQ_STL3`-superblokk som låser protokollversjon, bufferGeometri og endepunkt-PIDer før noen ringindeks leses.

Alle varme kontrollord og slot-headere er justert til 64-byte cache-linjegrenser, slik at produsent- og konsumentkjerner aldri konkurrerer om samme cache-linje gjennom 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 | Semantikk                                                                                                                       |
| :-------- | -----: | ----------------: | :------------------------------------------------------------------------------------------------------------------------------ |
| `seq`     | `0x00` |               8 B | Monoton sekvensteller. Oddetallsverdier markerer en pågående skriving; partallsverdier publiserer en forpliktet ramme via store-release. |
| `len`     | `0x08` |               4 B | Lengde i byte på nyttelasten skrevet inn i slotten (`0 <= len <= BufferSize`).                                                   |
| `flags`   | `0x0C` |               4 B | Kontrollbiter for rammen (fortsettelse, feilstatus eller avslutningsvarsel).                                                     |
| `payload` | `0x40` | `BufferSize`      | Sammenhengende nyttelastområde som leses og skrives på stedet uten mellomliggende kopier.                                        |

Kallere velger mellom to transportnivåer avhengig av samtidighetsstrukturen:

- **`StandardLink` (1:1 SPSC-sesjon)**: Parer en klient- og en tjenersesjon over dedikerte forespørsels- og responsslots beskyttet av et sekvenslås (`SeqLock`). På den raske banen serialiserer skriveren direkte inn i den kartlagte slotten og forpliktes med en atomisk store-release; leseren spinner kort i brukerrom før den faller tilbake til OS-vekkingsprimitiver (`futex` på Linux, `__ulock_wait` på macOS, `WaitOnAddress` på Windows). En enkelt rammes rundtur fullføres på ~200 ns med `0 allocs/op`.
- **`Advanced` (MPMC-dispatch-ring)**: Multiplekser `M` samtidige Go-kallere over `N` C-arbeidertråder ved hjelp av en låsefri multi-producer multi-consumer-innsendingsring, en delt slotpool og en indeksert fullføringstabell. Kallere reserverer en slot, publiserer slotindeksen til ringen og venter på sin fullføringsoppføring, mens arbeidertrådene tar ut og fullfører forespørsler i uordnet rekkefølge.

Begge nivåene sporer kontinuerlig om peer er i live. Hvert endepunkt oppdaterer et atomisk monotont tidsstempel via `Heartbeat()` og inspiserer `PeerAlive(timeout)` mens det venter. Hvis en arbeiderprosess krasjer midt i et kall, oppdager peeren den utdaterte heartbeaten eller den døde PID-en umiddelbart, avbryter blokkerte ventinger med `ErrPeerGone`, og støtter atomisk sesjonsovertakelse (`Takeover`) uten å etterlate låste delte minnefiler.

### 2. Nullallokerings-RPC og generasjonssjekkede handles (`gosuda.org/hqq/bindgen`)

Å skrive rammeverk for delt minne for hånd til et C-bibliotek med dusinvis av funksjoner er kjedelig og feilutsatt. `hqq-bindgen` er en skjemakompilator som leser en Interface Definition Language-fil (`.idl`) (eller oppretter en direkte fra en C-header `.h` via `hqq-bindgen init`) og produserer en zero-cgo Go-klientpakke sammen med et C11-tjenerskjelett.

Den farligste feilen i enhver C-binding er å eksponere rå C-pekere (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) over språkgrensen. Hvis en kaller sender en peker etter at den er frigjort, sender en peker som tilhører en annen sesjon, eller korrumperer en heltallsadresse, derefererer C-biblioteket ugyldig minne.

`hqq-bindgen` forbyr rå pekere på ledningen. I stedet modellerer IDL-en tilstandsfulle C-ressurser som typede `handle`-deklarasjoner:

```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å ledningen er hvert handle et 64-bits heltall som pakker en 32-bits tabellindeks og en 32-bits generasjonsteller som ikke er null: `(uint64_t)generation << 32 | index`. C-kjøretiden (`c/src/hqq_rpc.c`) vedlikeholder en handle-tabell per sesjon med tre invarianter:

1. **Generasjonsvalidering**: Hvert oppslag verifiserer at slotten er opptatt, at dens lagrede generasjon samsvarer med de øvre 32 bitene av handle-en, og at dens handle-typetag samsvarer med parametertypen (`Db` mot `Stmt`). Gjenbruk av et lukket handle feiler umiddelbart med `HQQRPC_E_BAD_HANDLE` før kall inn i C-biblioteket.
2. **Hierarkisk eierskap (`@owned_by`)**: Fordi `Stmt` er annotert med `@owned_by(Db)`, går lukking av et `Db`-handle automatisk gjennom alle levende barn-`Stmt`-handles eid av det `Db` og kjører deres destruktorer i omvendt allokeringsrekkefølge (`sqlite3_finalize` før `sqlite3_close_v2`).
3. **Feiing ved sesjonsavslutning**: Hvis en Go-klient kobler fra eller krasjer uten å lukke sine handles, feier sesjonsdestruktoren på C-tjeneren hele sesjonens handle-tabell i omvendt rekkefølge, og garanterer null lekkede C-allokeringer på tvers av klientomstarter.

Før en sesjon behandler sitt første RPC-kall, utveksler klienten og tjeneren et 32-byte SHA-256-grensesnittsammendrag beregnet over den kanoniserte IDL-en (prefikset `HQQ-BINDGEN-IDL-V1\n`). Hvis Go-klienten og C-arbeideren ble kompilert fra inkompatible IDL-skjemaer, avviser håndtrykket tilkoblingen ved oppstart.

### 3. Manipulasjonssikker binærkjøring (`gosuda.org/hqq/secureexec`)

Å flytte C-kode inn i en arbeiderbinærfil reiser et spørsmål om forsyningskjeden: Hvordan vet Go-prosessen at arbeiderbinærfilen på disken ikke ble endret eller erstattet mellom verifisering og kjøring?

Å kalle `crypto/sha256` på en filsti etterfulgt av `os/exec.Command(path).Start()` etterlater et vindu for tidssjekk til tidsbruk (TOCTOU), der en annen prosess kan overskrive eller bytte ut filen før kjernen kjører den. `gosuda.org/hqq/secureexec` lukker dette vinduet:

- På Linux åpner `secureexec` binærfilen og strømmer innholdet inn i en anonym minnefildeskriptor opprettet via `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` mens den beregner SHA-256-hashen, verifiserer hashen mot et festet 32-byte sammendrag eller et Ed25519-signert utgivelsesmanifest (`cmd/hqq-manifest`), anvender `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` slik at ingen prosess på systemet kan endre sidene, og starter barneprosessen fra den forseglede deskriptoren.
- På macOS og Windows materialiserer `secureexec` verifiserte binærfiler i tillatelseslåste cachekataloger nøklet etter SHA-256-sammendrag, og verifiserer filstørrelse og integritet ved hjelp av sammendrag før oppstart.

### 4. Overvåking av prosesser med forelderlivline (`gosuda.org/hqq/launch`)

Når en Go-applikasjon starter en bakgrunnsprosess for en C-arbeider, må arbeiderprosessen aldri overleve sin forelder som en foreldreløs prosess, selv om Go-forelderen avsluttes med `SIGKILL` (`kill -9`) og ikke kan kjøre `defer`-opprydningsbehandlere.

`gosuda.org/hqq/launch` håndhever livssyklusbinding gjennom en anonym OS-pipe som sendes til arbeiderens standard inngang med flagget `--parent-stdin`. Go-forelderen holder skrive-enden av pipen og skriver aldri til den; C-arbeideren overvåker lese-enden på en dedikert livline-tråd. Hvis Go-prosessen avslutter normalt, får panikk, eller blir drept av operativsystemkjernen (`SIGKILL` eller OOM-drepen), lukker kjernen umiddelbart skrivedeskriptoren. C-arbeideren mottar `EOF` på pipen innen mikrosekunder, avbryter eventuelle aktive kall, kjører sine sesjonshandle-destruktorer, fjerner koblingen til filene for delt minne og avslutter rent.

## Hurtigstart: Kalle en generert tjeneste

Med `hqq-bindgen` og `launch` ser kall til en isolert C-arbeider fra Go ut som en vanlig typet Go-klient, samtidig som `CGO_ENABLED=0` opprettholdes:

```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 organisert i fokuserte pakker med eksplisitte grenser:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) og lavnivå-primitiver for delt minnering
├── advanced.go / slotpool.go  # Advanced (MPMC)-dispatcher, slotpool og fullføringstabell
├── shm_*.go / wait_*.go       # OS-spesifikk mmap/CreateFileMapping og futex/ulock/WaitOnAddress
├── c/                         # C11-transportbibliotek (hqq.c) og RPC-tjenerkjøretid (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL-parser, verifiserer (V1-V41) og Go/C11-kodegenerator
│   └── rt/                    # Nullallokerings Go-klientkjøretid for genererte tjenester
├── secureexec/                # SHA-256/Ed25519-manifestverifisering og memfd-forseglet kjøring
├── launch/                    # Arbeiderprosessovervåker med --parent-stdin-livline-håndtering
├── cmd/hqq-manifest/          # CLI-verktøy for generering og signering av binære utgivelsesmanifester
└── spec/                      # Kjørbare wire-spesifikasjoner og gullstandard-testvektorer (BINDGEN.md)
```

For å se hvordan HQQ driver direkte erstatninger i produksjon for to av de mest brukte `cgo`-bibliotekene i Go-økosystemet, se følgende søsterpakker bygget på denne stakken:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Direkte erstatning for `database/sql`-driver kompatibel med `github.com/mattn/go-sqlite3`, som kjører native C SQLite `3.54.1` over delt minne med pipelinet batch-prefetching.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Direkte erstatning for `github.com/DataDog/zstd`, som kjører native C `libzstd 1.5.7` i en isolert arbeider med delt minne, med nullallokeringskontekster og bulk-batching.
- **GitHub-repositorium**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
