---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Isolering av C-bibliotek från Go via cache-line-delat minne'
description: Körtidsmiljö för IPC utan cgo, IDL-kompilator (hqq-bindgen) och verifierad processövervakare som kör inbyggda C11-bibliotek i isolerade arbetsprocesser via delade minnesringbuffertar.
language: sv
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` är en zero-cgo-runtime för interprocesskommunikation och verktygskedja för kodgenerering för Go och C11. Den isolerar inbyggda C-bibliotek i separata OS-arbetsprocesser och ansluter dem till Go-anropare via minnesmappade, cache-linjeanpassade delade minnesringar. Projektet kombinerar två transportnivåer (`StandardLink` för dedicerade single-producer single-consumer-sessioner och `Advanced` för multi-worker MPMC-dispatch), IDL-kompilatorn `hqq-bindgen` samt en tamper-verifierad processupervisor (`secureexec` och `launch`).

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

## Kostnaden för att dela adressrymd med C

Varje systemingenjör inom Go står förr eller senare inför samma vägskäl. Du bygger en Go-tjänst som kompileras på sekunder med `CGO_ENABLED=0`, distribueras som en enda statisk binär, korskompileras från en bärbar dator till Linux- eller Windows-mål utan sysroot och körs i en minnessäker, skräpsamlad heap. Sedan behöver du ett moget C-bibliotek: SQLite för inbäddad relationell lagring, Zstandard (`libzstd`) för ordboksbaserad komprimering eller ett hårdvaruaccelererat bibliotek för media eller kryptografi.

Att aktivera `cgo` för att länka C-biblioteket in i Go-processen löser det omedelbara bindningsproblemet, men introducerar tre strukturella nackdelar:

1. **Koppling mellan verktygskedja och korskompilering**: `CGO_ENABLED=1` kräver en C-kompilator, målplattformens libc-headerfiler och en plattformslänkare för varje mål-OS och varje arkitektur. Att bygga `linux/arm64`- eller `windows/arm64`-artefakter från en `darwin/arm64`-arbetsstation förvandlar ett enda `go build`-kommando till en matris av korskompilatorer och sysroots.
2. **Schemaläggningskostnader i runtime**: Varje `cgo`-anrop korsar gränsen till Go-runtime, byter från en växande goroutine-stack till en systemstack och fäster en OS-tråd (`M`). För finkorniga anrop ackumuleras övergångskostnaden (ungefär 50 till 150 ns per anrop) och schemaläggningskonkurrensen snabbt.
3. **Kollaps av felområde**: C-kod körs i samma virtuella adressrymd som Go-runtime. En enda out-of-bounds-skrivning, use-after-free, stackspill eller `abort()` inuti en C-funktion korrumperar Go-heapmetadata eller avslutar hela serverprocessen omedelbart, med förbigående av `recover()` och med alla pågående HTTP- eller RPC-förfrågningar som följd.

När team försöker undvika `cgo` tvingar de vanliga alternativen fram en annan kompromiss. Rena Go-översättningar av C-kodbaser bevarar `CGO_ENABLED=0`, men ligger ofta efter upstream-releaser av C, bär på ett emulerat virtuellt minneslager och allokerar kraftigt under belastning. Att flytta C-biblioteket till en hjälpprocess över loopback-TCP eller Unix-domänsockets återställer felisoleringen, men varje anrop betalar för två kontextbyten i kärnan, kopiering av socketbuffertar och wire-serialisering.

Vi byggde HQQ efter att ha ställt en direkt fråga: tänk om C-biblioteket levde i en separat OS-process med egen virtuell adressrymd, medan Go och C utbytte ramar direkt via delade CPU-cache-linjer utan att gå in i kärnan på snabbvägen?

| Integrationsmodell                 | `CGO_ENABLED=0` | Felisolering       | Systemanrop på snabbvägen | Heapallokeringar per anrop | Trohet mot upstream C11 |
| :--------------------------------- | :-------------- | :----------------- | ------------------------: | -------------------------: | :---------------------- |
| **`cgo` i-process-länkning**       | Nej             | Ingen (delad heap) |                         0 |                  0 till 1+ | Exakt upstream C        |
| **Socket- / pipe-subprocess-RPC**  | Ja              | Processgräns       |                    2 till 4 |   Flera (inramning + I/O) | Exakt upstream C        |
| **Mekanisk C-till-Go-översättning** | Ja             | Go i process       |                         0 |        Hög vid komplexa anrop | Emulerad C-runtime      |
| **HQQ delat-minne-IPC**            | Ja              | Processgräns       |         0 (adaptiv spinning) |  0 (`StandardLink`)       | Exakt upstream C        |

## Arkitektonisk översikt: fyra lager över processgränser

HQQ delar upp processisolering i fyra sammansättningsbara lager, så att mekaniken för delade minnesringar, typad RPC-kodgenerering, verifiering av binär integritet och övervakning av barnprocesser förblir oberoende av varandra.

```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 delat minne (`gosuda.org/hqq` och `c/`)

Baspaketet `gosuda.org/hqq` och dess C11-motsvarighet i `c/` implementerar två transportlägen för delat minne över POSIX `mmap` (Linux och macOS) och Win32 `CreateFileMappingW` (Windows). Varje delat segment inleds med en verifierad `HQQ_STL3`-superblock som fastställer protokollversion, buffertgeometri och endpoint-PID:ar innan något ringindex läses.

Alla heta kontrollord och slotheaders är justerade till 64-byte cache-linjegränser, så att producent- och konsumentkärnor aldrig konkurrerar om samma cache-linje genom falsk delning.

```text
StandardLink-slotlayout (64-byte cache-linjeanpassad)
Byteoffset:   0x00                0x08                0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Headerlinje:  | seq (u64 atomic)  | len (u32) | flags | reserved (nollutfylld)      |
              +-------------------+-------------------+-----------------------------+
Payloadyta:   | payloadbytes [0 .. BufferSize - 1] (kodas / avkodas direkt)         |
              +---------------------------------------------------------------------+
```

| Fält      | Offset |         Storlek | Semantik                                                                                                                       |
| :-------- | -----: | --------------: | :----------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00` |            8 B | Monoton sekvensräknare. Udda värden markerar en pågående skrivning; jämna värden publicerar en committad ram via store-release. |
| `len`     | `0x08` |            4 B | Payloadens bytelängd skriven i slotten (`0 <= len <= BufferSize`).                                                              |
| `flags`   | `0x0C` |            4 B | Kontrollbitar för ramen (fortsättning, felstatus eller nedstängningsnotis).                                                     |
| `payload` | `0x40` | `BufferSize`   | Sammanhängande payloadregion som läses och skrivs på plats utan mellanliggande kopior.                                          |

Anropare väljer mellan två transportnivåer beroende på konkurrensstruktur:

- **`StandardLink` (1:1 SPSC-session)**: Parar en klient- och en serversession över dedicerade request- och response-slottar skyddade av ett sekvenslås (`SeqLock`). På snabbvägen serialiserar skrivaren direkt in i den mappade slotten och committar med en atomär store-release; läsaren spinnar kort i användarutrymme innan den faller tillbaka på OS-väckningsprimitiver (`futex` på Linux, `__ulock_wait` på macOS, `WaitOnAddress` på Windows). En rundresa med en enda ram slutförs på ungefär 200 ns med `0 allocs/op`.
- **`Advanced` (MPMC-dispatchring)**: Multiplexerar `M` samtidiga Go-anropare över `N` C-arbetartrådar med hjälp av en lock-fri multi-producer multi-consumer-inlämningsring, en delad slotpool och en indexerad komplettringstabell. Anropare reserverar en slot, publicerar slotindexet till ringen och väntar på sin komplettringspost, medan arbetartrådar köar ut och slutför förfrågningar i godtycklig ordning.

Båda nivåerna följer kontinuerligt peer-livslängd. Varje endpoint uppdaterar en atomär monoton tidsstämpel via `Heartbeat()` och kontrollerar `PeerAlive(timeout)` medan den väntar. Om en arbetarprocess kraschar mitt i ett anrop upptäcker peer:en den inaktuella heartbeaten eller det döda PID:t omedelbart, avbryter blockerade väntanden med `ErrPeerGone` och stödjer atomär sessionsövertagning (`Takeover`) utan att lämna kvar fastlåsta filer för delat minne.

### 2. Anropsfri RPC och generationskontrollerade handtag (`gosuda.org/hqq/bindgen`)

Att skriva delad minnesinramning för hand för ett C-bibliotek med dussintals funktioner är tråkigt och felbenäget. `hqq-bindgen` är en schemakompilator som läser en Interface Definition Language-fil (`.idl`) (eller bootstrappar en sådan direkt från en C-headerfil `.h` via `hqq-bindgen init`) och genererar ett zero-cgo-Go-klientpaket tillsammans med ett C11-serverskelett.

Det allvarligaste misstaget i varje C-bindning är att exponera råa C-pekare (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) över språkgränsen. Om en anropare skickar en pekare efter att ha frigjort den, skickar en pekare som tillhör en annan session eller korrumperar en heltalsadress, derefererar C-biblioteket ogiltigt minne.

`hqq-bindgen` förbjuder råa pekare på transportvägen. I stället modellerar IDL:en tillståndsbärande C-resurser som typade `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å transportvägen är varje handtag ett 64-bitars heltal som packar ett 32-bitars tabellindex och en 32-bitars icke-noll generationsräknare: `(uint64_t)generation << 32 | index`. C-runtime (`c/src/hqq_rpc.c`) underhåller en handtagstabell per session med tre invarianter:

1. **Generationsvalidering**: Varje uppslagning verifierar att slotten är upptagen, att dess lagrade generation matchar de övre 32 bitarna i handtaget och att dess handtagstyptagg matchar parametertypen (`Db` mot `Stmt`). Återanvändning av ett stängt handtag misslyckas omedelbart med `HQQRPC_E_BAD_HANDLE` innan C-biblioteket anropas.
2. **Hierarkiskt ägarskap (`@owned_by`)**: Eftersom `Stmt` är annoterad med `@owned_by(Db)` går stängning av ett `Db`-handtag automatiskt igenom alla levande underordnade `Stmt`-handtag som ägs av det `Db` och kör deras destruktorer i omvänd allokeringsordning (`sqlite3_finalize` före `sqlite3_close_v2`).
3. **Svep vid sessionsavslut**: Om en Go-klient kopplar från eller kraschar utan att stänga sina handtag, sveper sessionsdestruktorn på C-servern hela sessionens handtagstabell i omvänd ordning, vilket garanterar noll läckta C-allokeringar över klientomstarter.

Innan en session behandlar sitt första RPC-anrop utväxlar klienten och servern en 32-byte SHA-256-gränssnittsdigest beräknad över den kanoniserade IDL:en (med prefixet `HQQ-BINDGEN-IDL-V1\n`). Om Go-klienten och C-arbetaren kompilerades från inkompatibla IDL-scheman avvisar handskakningen anslutningen vid bootstrap.

### 3. Manipulationssäker binärexekvering (`gosuda.org/hqq/secureexec`)

Att flytta C-kod till en arbetarbinär väcker en leveranskedjefråga: hur vet Go-processen att arbetarbinären på disken inte har ändrats eller ersatts mellan verifiering och exekvering?

Att anropa `crypto/sha256` på en sökväg följt av `os/exec.Command(path).Start()` lämnar ett Time-of-Check till Time-of-Use (TOCTOU)-fönster där en annan process kan skriva över eller byta ut filen innan kärnan exekverar den. `gosuda.org/hqq/secureexec` stänger detta fönster:

- På Linux öppnar `secureexec` binären, strömmar dess innehåll till en anonym minnesfildeskriptor skapad via `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` medan dess SHA-256-hash beräknas, verifierar hashen mot en fastställd 32-byte-digest eller ett Ed25519-signerat releasemanifest (`cmd/hqq-manifest`), tillämpar `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` så att ingen process i systemet kan mutera sidorna, och startar barnet från den förseglade deskriptorn.
- På macOS och Windows materialiserar `secureexec` verifierade binärer i behörighetslåsta cachekataloger nycklade efter SHA-256-digest och verifierar filstorlek och digestintegritet före start.

### 4. Förälderlivlina för processövervakning (`gosuda.org/hqq/launch`)

När en Go-applikation startar en C-arbetarprocess i bakgrunden får arbetaren aldrig överleva sin förälder som en föräldralös process, även om Go-föräldern avslutas med `SIGKILL` (`kill -9`) och inte kan köra `defer`-städhanterare.

`gosuda.org/hqq/launch` upprätthåller livstidskoppling via en anonym OS-pipe som skickas till arbetarens standard input med flaggan `--parent-stdin`. Go-föräldern håller pipens skrivände och skriver aldrig till den; C-arbetaren övervakar läsänden på en dedicerad livlinetråd. Om Go-processen avslutas normalt, panikar eller dödas av operativsystemets kärna (`SIGKILL` eller OOM-dödaren) stänger kärnan omedelbart skrivdeskriptorn. C-arbetaren får `EOF` på pipen inom mikrosekunder, avbryter eventuella aktiva anrop, kör sina sessionshandtagsdestruktorer, avlänkar de delade minnesfilerna och avslutas på ett rent sätt.

## Snabbstart: Anrop av en genererad tjänst

Med `hqq-bindgen` och `launch` ser anrop av en isolerad C-arbetare från Go ut som en vanlig typad Go-klient, samtidigt som `CGO_ENABLED=0` bibehålls:

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

## Repositoriestruktur

Varje delsystem i `gosuda.org/hqq` är organiserat i fokuserade paket med tydliga gränser:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) och primitiver för ringar i delat minne på låg nivå
├── advanced.go / slotpool.go  # Advanced (MPMC)-dispatcher, slotpool och komplettringstabell
├── shm_*.go / wait_*.go       # OS-specifik mmap/CreateFileMapping och futex/ulock/WaitOnAddress
├── c/                         # C11-transportbibliotek (hqq.c) och RPC-serverruntime (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL-parser, verifierare (V1-V41) och Go/C11-kodgenerator
│   └── rt/                    # Anropsfri Go-klientruntime för genererade tjänster
├── secureexec/                # SHA-256- / Ed25519-manifestverifiering och memfd-förseglad exekvering
├── launch/                    # Arbetarprocessupervisor med hantering av --parent-stdin-livlina
├── cmd/hqq-manifest/          # CLI-verktyg för att generera och signera manifest för releasebinärer
└── spec/                      # Körbara wirespecifikationer och referensvektorer för tester (BINDGEN.md)
```

För att se hur HQQ driver direkta produktionsersättningar för två av de mest använda `cgo`-biblioteken i Go-ekosystemet, ta en titt på de kompletterande paketen som byggts på denna stack:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Direktersättande `database/sql`-drivrutin kompatibel med `github.com/mattn/go-sqlite3`, som kör inbyggd C SQLite `3.54.1` över delat minne med pipelinebaserad batchförhämtning.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Direktersättning för `github.com/DataDog/zstd`, som kör inbyggd C `libzstd 1.5.7` i en isolerad arbetare med delat minne, med anropsfria kontexter och bulkbatchning.
- **GitHub-repositorium**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
