---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Izolace C knihoven od Go prostřednictvím sdílené paměti zarovnané na cache line'
description: Běhové prostředí IPC bez cgo, kompilátor IDL (hqq-bindgen) a ověřený supervizor procesů, které spouštějí nativní knihovny C11 v izolovaných pracovních procesech prostřednictvím kruhových front ve sdílené paměti.
language: cs
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` je runtime pro meziprocesovou komunikaci (IPC) bez zero-cgo a nástroj pro generování kódu pro Go a C11. Izoluje nativní C knihovny uvnitř samostatných procesů operačního systému (worker procesů) a připojuje je k volajícím v Go prostřednictvím sdílených paměťových kruhových front (ring) mapovaných do paměti, zarovnaných na hranice cache line. Projekt kombinuje dvě úrovně přenosu (`StandardLink` pro vyhrazené relace typu single-producer single-consumer a `Advanced` pro multi-worker MPMC dispatch), kompilátor IDL `hqq-bindgen` a supervizor procesů s ověřováním integrity (`secureexec` a `launch`).

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

## Cena sdílení adresního prostoru s C

Každý inženýr systémů v Go se dříve či později dostane na stejnou křižovatku. Vytváříte službu v Go, která se kompiluje během několika sekund s `CGO_ENABLED=0`, je distribuována jako jediný statický binární soubor, lze ji křížově kompilovat z notebooku pro cílové systémy Linux nebo Windows bez sysroot a běží uvnitř paměťově bezpečné haldy spravované garbage collectorem. Následně potřebujete vyzrálou C knihovnu: SQLite pro vestavěné relační úložiště, Zstandard (`libzstd`) pro kompresi se slovníkem, nebo hardwarově akcelerovanou knihovnu pro média či kryptografii.

Povolení `cgo` pro propojení této C knihovny do procesu Go sice vyřeší bezprostřední problém vazby, avšak zavádí tři strukturální nevýhody:

1. **Vazba na toolchain a křížovou kompilaci**: `CGO_ENABLED=1` vyžaduje C kompilátor, hlavičkové soubory cílové libc a platformní linker pro každý cílový operační systém a architekturu. Sestavení artefaktů `linux/arm64` nebo `windows/arm64` z pracovní stanice `darwin/arm64` promění jediný příkaz `go build` v matici křížových kompilátorů a sysroot.
2. **Režie plánování za běhu**: Každé volání `cgo` překračuje hranici runtime jazyka Go, přepíná z rostoucího zásobníku goroutiny na systémový zásobník a váže OS vlákno (`M`). U jemnozrnných volání se rychle nahromadí náklady na přechod (přibližně 50 až 150 ns na volání) a konkurence plánovače.
3. **Kolaps doménы poruchy**: Kód C běží ve stejném virtuálním adresním prostoru jako runtime Go. Jediný zápis mimo hranice, použití po uvolnění (use-after-free), přetečení zásobníku nebo `abort()` uvnitř funkce C poškodí metadata haldy Go nebo okamžitě ukončí celý serverový proces, čímž obejde `recover()` a shodí každý probíhající požadavek HTTP nebo RPC.

Když se týmy pokusí `cgo` obejít, běžné alternativy vynucují jiný kompromis. Čistě Go mechanické překlady C kódu zachovávají `CGO_ENABLED=0`, avšak často zaostávají za upstream vydáními C, nesou emulovanou vrstvu virtuální paměti a při zátěži intenzivně alokují. Přesun C knihovny do pomocného procesu přes loopback TCP nebo Unix doménové sockety obnoví izolaci poruch, ale každé volání platí dvěma přepnutími kontextu jádra, kopírováním socketových bufferů a serializací na drátu.

HQQ jsme vytvořili poté, co jsme si položili přímou otázku: co kdyby C knihovna žila v samostatném procesu operačního systému s vlastním virtuálním adresním prostorem, zatímco Go a C by si rámce vyměňovaly přímo prostřednictvím sdílených cache line procesoru, bez vstupu do jádra na rychlé cestě?

| Model integrace                    | `CGO_ENABLED=0` | Izolace poruch     | Systémová volání na rychlé cestě | Alokace na haldě na volání | Věrnost upstream C11 |
| :--------------------------------- | :-------------- | :----------------- | -------------------------------: | -------------------------: | :------------------- |
| **`cgo` propojení v procesu**      | Ne              | Žádná (sdílená halda) |                                0 |                    0 až 1+ | Přesné upstream C    |
| **Socket / Pipe subprocesové RPC** | Ano             | Hranice procesu    |                           2 až 4 |  Několik (rámcování + I/O) | Přesné upstream C    |
| **Mechanický překlad C do Go**     | Ano             | Proces v Go        |                                0 |     Vysoké u složitých volání | Emulovaný runtime C  |
| **HQQ sdílená paměť IPC**          | Ano             | Hranice procesu    |  0 (adaptivní spin)              |     0 (`StandardLink`)     | Přesné upstream C    |

## Architektonický přehled: čtyři vrstvy napříč hranicemi procesů

HQQ rozděluje izolaci procesů do čtyř skladatelných vrstev, takže mechanika sdílených kruhových front, typově bezpečné generování RPC kódu, ověřování binární integrity a dohled nad podřízenými procesy zůstávají nezávislé.

```text
+-------------------------------------------------------------------+
| Procesu aplikace v Go (CGO_ENABLED=0)                             |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Generovaný klient Go  |     | Dohled nad procesy            |  |
|  | (gosuda.org/hqq/      |     | (gosuda.org/hqq/launch +      |  |
|  |  bindgen/rt)          |     |  gosuda.org/hqq/secureexec)   |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|  +-----------v-----------+                     | Anonymní pipe    |
|  | Vrstva přenosu        |                     | Lifeline         |
|  | (StandardLink /       |                     | (--parent-stdin) |
|  |  Advanced MPMC)       |                     |                  |
+--------------+---------------------------------+------------------+
               |                                 |
===============|========= Sdílená paměť =========|===================
               |     (mmap / CreateFileMapping)  |
+--------------v---------------------------------v------------------+
| Worker proces C11 (izolovaný adresní prostor)                     |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Generovaný server C11 |---->| Tabulka handle s kontrolou    |  |
|  | a dávkové shimy       |     | generací (hqq_rpc.c)          |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|              +----------------+----------------+                  |
|                               |                                   |
|                     +---------v---------+                         |
|                     | Nativní knihovna C|                         |
|                     | (SQLite, Zstd...) |                         |
|                     +-------------------+                         |
+-------------------------------------------------------------------+
```

### 1. Přenos přes sdílenou paměť (`gosuda.org/hqq` a `c/`)

Základní balíček `gosuda.org/hqq` a jeho protějšek v C11 v adresáři `c/` implementují dva režimy přenosu přes sdílenou paměť nad POSIX `mmap` (Linux a macOS) a Win32 `CreateFileMappingW` (Windows). Každý sdílený segment začíná ověřeným superblokem `HQQ_STL3`, který fixuje verzi protokolu, geometrii bufferů a PID koncových bodů dříve, než je přečten jakýkoli index kruhové fronty.

Všechna řídicí slova za běhu a hlavičky slotů jsou zarovnána na hranice cache line o velikosti 64 bajtů, aby producent a konzument na různých jádrech nikdy nesoupeřili o stejnou cache line kvůli falešnému sdílení (false sharing).

```text
Rozložení slotu StandardLink (zarovnáno na cache line 64 bajtů)
Posun v bajtech: 0x00                0x08                0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Řádek hlavičky: | seq (u64 atomic)  | len (u32) | flags | rezervováno (doplněno nulami) |
              +-------------------+-------------------+-----------------------------+
Oblast payloadu: | bajty payloadu [0 .. BufferSize - 1] (kódovány / dekódovány přímo)  |
              +---------------------------------------------------------------------+
```

| Pole      | Posun  |         Velikost | Význam                                                                                                                       |
| :-------- | -----: | ---------------: | :--------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00` |             8 B | Monotónní sekvenční čítač. Liché hodnoty označují zápis v procesu; sudé hodnoty publikují potvrzený rámec pomocí store-release. |
| `len`     | `0x08` |             4 B | Délka payloadu v bajtech zapsaného do slotu (`0 <= len <= BufferSize`).                                                      |
| `flags`   | `0x0C` |             4 B | Řídicí bity rámce (pokračování, stav chyby nebo oznámení o ukončení).                                                         |
| `payload` | `0x40` | `BufferSize`     | Souvislá oblast payloadu čtená a zapisovaná na místě bez mezikopírování.                                                      |

Volající volí mezi dvěma úrovněmi přenosu podle struktury souběžnosti:

- **`StandardLink` (relace SPSC 1:1)**: Páruje klientskou a serverovou relaci přes vyhrazené požadavkové a odpovědní sloty chráněné sekvenčním zámkem (`SeqLock`). Na rychlé cestě zapisovatel serializuje data přímo do mapovaného slotu a potvrzuje je atomickým store-release; čtenář krátce spinuje v uživatelském prostoru, než se uchýlí k primitivům probuzení OS (`futex` na Linuxu, `__ulock_wait` na macOS, `WaitOnAddress` na Windows). Jeden rámec v round tripu se dokončí přibližně za 200 ns s `0 allocs/op`.
- **`Advanced` (MPMC dispatch ring)**: Multiplexuje `M` souběžných volajících v Go přes `N` vláken C workeru pomocí lock-free multi-producer multi-consumer odesílací fronty, sdíleného fondu slotů a indexované tabulky dokončení. Volající rezervuje slot, publikuje jeho index do fronty a čeká na svou položku dokončení, zatímco vlákna workeru vyjímají požadavky z fronty a dokončují je mimo pořadí.

Obě úrovně průběžně sledují živost protějšku. Každý koncový bod aktualizuje atomické monotónní časové razítko přes `Heartbeat()` a při čekání kontroluje `PeerAlive(timeout)`. Pokud worker proces během volání spadne, protějšek okamžitě zjistí zastaralý heartbeat nebo mrtvé PID, přerušené čekání ukončí chybou `ErrPeerGone` a podporuje atomické převzetí relace (`Takeover`) bez ponechání zablokovaných souborů sdílené paměti.

### 2. RPC bez alokací a handly s kontrolou generací (`gosuda.org/hqq/bindgen`)

Ruční psaní rámcování sdílené paměti pro C knihovnu s desítkami funkcí je zdlouhavé a náchylné k chybám. `hqq-bindgen` je kompilátor schémat, který čte soubor v jazyce pro definici rozhraní (Interface Definition Language, `.idl`) (nebo jej může přímo vytvořit z hlavičky C `.h` příkazem `hqq-bindgen init`) a generuje klientský balíček pro Go bez zero-cgo spolu s kostrou serveru v C11.

Nejnebezpečnějším omylem v jakékoli vazbě na C je vystavení surových ukazatelů C (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) přes hranici jazyka. Pokud volající předá ukazatel poté, co jej uvolnil, předá ukazatel patřící jiné relaci, nebo poškodí celočíselnou adresu, knihovna C dereferencuje neplatnou paměť.

`hqq-bindgen` zakazuje surové ukazatele na drátu. Místo toho IDL modeluje stavové prostředky C jako typované deklarace `handle`:

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

Na drátu je každý handle 64bitové celé číslo, které obsahuje 32bitový index tabulky a 32bitový nenulový čítač generace: `(uint64_t)generation << 32 | index`. Runtime v C (`c/src/hqq_rpc.c`) udržuje pro každou relaci tabulku handlů se třemi invarianty:

1. **Validace generace**: Každé vyhledání ověří, že slot je obsazen, že jeho uložená generace odpovídá horním 32 bitům handlu a že jeho typová značka handlu odpovídá typu parametru (`Db` vs. `Stmt`). Opětovné použití uzavřeného handlu okamžitě selže s `HQQRPC_E_BAD_HANDLE`, aniž by se volala knihovna C.
2. **Hierarchické vlastnictví (`@owned_by`)**: Protože `Stmt` je označen `@owned_by(Db)`, uzavření handlu `Db` automaticky projde všechny živé podřízené handly `Stmt`, které tomuto `Db` patří, a spustí jejich destruktory v opačném pořadí alokace (`sqlite3_finalize` před `sqlite3_close_v2`).
3. **Úklid relace při ukončení**: Pokud se klient v Go odpojí nebo spadne, aniž by uzavřel své handly, destruktor relace na serveru C projde celou tabulku handlů relace v obráceném pořadí a zaručí nulový únik alokací C napříč restarty klienta.

Předtím, než relace zpracuje první volání RPC, si klient a server vymění 32bajtový SHA-256 digest rozhraní, vypočtený nad kanonicky zapsaným IDL (prefix `HQQ-BINDGEN-IDL-V1\n`). Pokud byl klient v Go a worker v C zkompilován z nekompatibilních schémat IDL, handshake spojení při inicializaci odmítne.

### 3. Binární spouštění odolné proti manipulaci (`gosuda.org/hqq/secureexec`)

Přesun kódu C do binárního workeru vyvolává otázku dodavatelského řetězce: jak proces v Go pozná, že binární soubor workeru na disku nebyl mezi ověřením a spuštěním změněn nebo nahrazen?

Výpočet `crypto/sha256` nad cestou k souboru následovaný voláním `os/exec.Command(path).Start()` ponechává okno typu Time-of-Check to Time-of-Use (TOCTOU), v němž může jiný proces soubor přepsat nebo vyměnit dříve, než jej jádro spustí. `gosuda.org/hqq/secureexec` toto okno uzavírá:

- Na Linuxu `secureexec` otevře binární soubor, streamuje jeho obsah do anonymního paměťového souboru (deskriptoru) vytvořeného přes `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` a přitom počítá jeho SHA-256 hash, ověří hash proti připnutému 32bajtovému digestu nebo podepsanému manifestu vydání Ed25519 (`cmd/hqq-manifest`), aplikuje `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)`, takže žádný proces v systému nemůže stránky změnit, a podřízený proces spustí z zapečetěného deskriptoru.
- Na macOS a Windows `secureexec` ověřené binární soubory materializuje do adresářů uzamčených oprávněními, indexovaných podle SHA-256 digestu, a před spuštěním ověřuje integritu velikosti souboru a digestu.

### 4. Dohled nad procesy s parent-lifeline (`gosuda.org/hqq/launch`)

Když aplikace v Go spustí worker proces C na pozadí, worker nesmí nikdy přežít svého rodiče jako osiřelý proces, a to ani tehdy, když je rodičovský proces v Go ukončen signálem `SIGKILL` (`kill -9`) a nemůže spustit úklidové obslužné rutiny `defer`.

`gosuda.org/hqq/launch` vynucuje provázání životnosti prostřednictvím anonymní systémové roury (pipe), předané na standardní vstup workeru s příznakem `--parent-stdin`. Rodičovský proces v Go drží zápisový konec roury a nikdy do něj nezapisuje; worker C sleduje čtecí konec na vyhrazeném vlákně lifeline. Pokud proces Go skončí normálně, spadne na panice nebo je ukončen jádrem operačního systému (`SIGKILL` nebo OOM killer), jádro okamžitě uzavře zapisovací deskriptor. Worker C obdrží `EOF` na rouře během několika mikrosekund, přeruší všechna aktivní volání, spustí destruktory handlů relace, odstraní soubory sdílené paměti (unlink) a čistě se ukončí.

## Rychlý start: volání generované služby

S nástroji `hqq-bindgen` a `launch` vypadá volání izolovaného worker procesu C z Go jako běžný typovaný klient v Go, a přitom zůstává zachováno `CGO_ENABLED=0`:

```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 běží na posluchači sdílené paměti %s (opts=%+v)\n", listener, rt.SessionOptions{})
}
```

## Struktura repozitáře

Každý subsystém v `gosuda.org/hqq` je organizován do zaměřených balíčků s explicitními hranicemi:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) a nízkoúrovňové primitivy kruhové fronty ve sdílené paměti
├── advanced.go / slotpool.go  # Dispatcher Advanced (MPMC), fond slotů a tabulka dokončení
├── shm_*.go / wait_*.go       # Specifické pro OS: mmap/CreateFileMapping a futex/ulock/WaitOnAddress
├── c/                         # Transportní knihovna C11 (hqq.c) a runtime RPC serveru (hqq_rpc.c)
├── bindgen/                   # Parser IDL hqq-bindgen, verifikátor (V1-V41) a generátor kódu Go/C11
│   └── rt/                    # Klientský runtime pro Go bez alokací pro generované služby
├── secureexec/                # Ověřování manifestů SHA-256 / Ed25519 a spouštění zapečetěné přes memfd
├── launch/                    # Dohled nad worker procesy se správou lifeline přes --parent-stdin
├── cmd/hqq-manifest/          # Nástroj CLI pro generování a podepisování manifestů vydaných binárních souborů
└── spec/                      # Spustitelné specifikace drátového protokolu a referenční testovací vektory (BINDGEN.md)
```

Chcete-li vidět, jak HQQ pohání plnohodnotné produkční náhrady dvou nejpoužívanějších knihoven `cgo` v ekosystému Go, podívejte se na doprovodné balíčky postavené na tomto zásobníku:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Drop-in ovladač `database/sql` kompatibilní s `github.com/mattn/go-sqlite3`, který běží nativní C SQLite `3.54.1` přes sdílenou paměť s pipelinovým předběžným načítáním dávek.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Drop-in náhrada za `github.com/DataDog/zstd`, která běží nativní C `libzstd 1.5.7` v izolovaném workeru se sdílenou pamětí s kontexty bez alokací a hromadným dávkováním.
- **Repozitář na GitHubu**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
