---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: C könyvtárak elszigetelése Go-tól gyorsítótár-sor méretű megosztott memórián keresztül'
description: Cgo-mentes IPC futtatókörnyezet, IDL-fordító (hqq-bindgen) és ellenőrzött folyamatfelügyelő, amely natív C11 könyvtárakat izolált munkafolyamatokban, megosztott memóriagyűrűkön keresztül hajt végre.
language: hu
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` egy nulla-cgo folyamatok közötti kommunikációs futtatókörnyezet és kódgeneráló eszköztár Go és C11 számára. Az izolált natív C könyvtárakat külön operációs rendszer szintű worker folyamatokban futtatja, és memória-leképezett, gyorsítótár-sorhoz igazított megosztott memóriagyűrűkön keresztül köti össze őket Go hívókkal. A projekt két átviteli szintet (`StandardLink` a dedikált egy-termelős egy-fogyasztós munkamenetekhez és `Advanced` a többworkeres MPMC kiszolgáláshoz), a `hqq-bindgen` IDL-fordítót, valamint egy manipuláció-ellenőrzött folyamatfelügyelőt (`secureexec` és `launch`) egyesít.

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

## A C-vel közös címtér megosztásának költsége

Minden Go rendszermérnök előbb-utóbb ugyanarra az elágazásra jut. Olyan Go szolgáltatást építesz, amely `CGO_ENABLED=0` mellett másodpercek alatt fordul, egyetlen statikus bináris fájlként kerül kiadásra, laptopról Linux vagy Windows célplatformra sysroot nélkül keresztfordítható, és memóriabiztos, szemétgyűjtött heapen fut. Ekkor viszont szükséged van egy érett C könyvtárra: SQLite a beágyazott relációs tároláshoz, Zstandard (`libzstd`) a szótáralapú tömörítéshez, vagy hardveresen gyorsított média- vagy kriptográfiai könyvtárra.

A `cgo` engedélyezése, amellyel ezt a C könyvtárat a Go folyamatba linkeled, megoldja az azonnali kötési problémát, ugyanakkor három szerkezeti kockázatot vezet be:

1. **Eszköztár- és keresztfordítási csatolás**: A `CGO_ENABLED=1` minden céloperációs rendszerhez és architektúrához C fordítót, célplatform libc fejléceket és platformlinkert igényel. A `linux/arm64` vagy `windows/arm64` artefaktumok előállítása egy `darwin/arm64` munkaállomásról egyetlen `go build` parancsot fordítóprogramok és sysrootok mátrixává alakít.
2. **Futtatókörnyezeti ütemezési többletterhelés**: Minden `cgo` hívás átlépi a Go futtatókörnyezet határát, a növekvő goroutine-veremről rendszerveremre vált, és egy operációs rendszer szintű szálat (`M`) rögzít. Finomszemcsés hívások esetén az átmeneti költség (hívásonként ~50 és 150 ns között) és az ütemezői versengés gyorsan felhalmozódik.
3. **Hibatartomány-összeomlás**: A C kód ugyanabban a virtuális címtérben fut, mint a Go futtatókörnyezet. Egyetlen C függvényen belüli határon kívüli írás, felszabadítás utáni használat, veremtúlcsordulás vagy `abort()` azonnal megsérti a Go heap metaadatait, vagy leállítja a teljes szerverfolyamatot, megkerülve a `recover()` függvényt, és magával rántva minden folyamatban lévő HTTP- vagy RPC-kérést.

Amikor a csapatok megpróbálják elkerülni a `cgo` használatát, a szokásos alternatívák más kompromisszumot kényszerítenek ki. A C kódbázisok tiszta Go nyelvű mechanikus fordításai megőrzik a `CGO_ENABLED=0` lehetőségét, de gyakran lemaradnak a felfelé irányuló C kiadásoktól, emulált virtuális memóriaréteget hordoznak, és terhelés alatt intenzíven foglalnak memóriát. A C könyvtár helyi TCP vagy Unix tartományi socketeken keresztül történő segédfolyamatba költöztetése visszaállítja a hibaizolációt, ám minden hívás két kernel-kontextusváltás, socket-puffermásolás és vezetékes szerializáció költségét fizeti meg.

A HQQ-t egy közvetlen kérdés után építettük: mi történne, ha a C könyvtár külön operációs rendszer-folyamatban, saját virtuális címtérrel élne, miközben a Go és a C a keretekeket közvetlenül, megosztott CPU-gyorsítótár-sorokon keresztül cserélné ki, a gyors úton nem lépve be a kernelbe?

| Integrációs modell                 | `CGO_ENABLED=0` | Hibaizoláció          | Gyors úti rendszerhívások | Hívásonkénti heap-foglalások | Felfelé irányuló C11 hűség |
| :--------------------------------- | :-------------- | :-------------------- | ------------------------: | ---------------------------: | :------------------------- |
| **`cgo` folyamaton belüli linkelés** | Nem             | Nincs (megosztott heap) |                         0 |                     0 és 1+ | Pontos upstream C          |
| **Socket / csőalapú segédfolyamat RPC** | Igen          | Folyamathatár         |                    2 és 4 |     Több (keretezés + I/O)   | Pontos upstream C          |
| **Mechanikus C-ből Go-ba fordítás**  | Igen            | Folyamaton belüli Go  |                         0 |          Magas összetett hívásoknál | Emulált C futtatókörnyezet |
| **HQQ megosztott memóriás IPC**      | Igen            | Folyamathatár         |   0 (adaptív pörgetés)    |       0 (`StandardLink`)     | Pontos upstream C          |

## Architekturális áttekintés: négy réteg a folyamathatárok átívelésében

A HQQ a folyamatizolációt négy egymással kombinálható rétegre bontja, így a megosztott memóriagyűrű-mechanika, a típusos RPC-kódgenerálás, a bináris integritás-ellenőrzés és a gyermekfolyamatok felügyelete egymástól függetlenek maradnak.

```text
+-------------------------------------------------------------------+
| Go alkalmazásfolyamat (CGO_ENABLED=0)                             |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Generált Go kliens    |     | Folyamatfelügyelő             |  |
|  | (gosuda.org/hqq/      |     | (gosuda.org/hqq/launch +      |  |
|  |  bindgen/rt)          |     |  gosuda.org/hqq/secureexec)   |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|  +-----------v-----------+                     | Anonim cső       |
|  | Átviteli szint        |                     | életvonal        |
|  | (StandardLink /       |                     | (--parent-stdin) |
|  |  Advanced MPMC)       |                     |                  |
+--------------+---------------------------------+------------------+
               |                                 |
===============|========= Megosztott memória =====|===================
               |     (mmap / CreateFileMapping)  |
+--------------v---------------------------------v------------------+
| C11 worker folyamat (izolált címtér)                              |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Generált C11 szerver  |---->| Generáció-ellenőrzött          |  |
|  | és köteg-shimek       |     | fogantyútábla (hqq_rpc.c)     |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|              +----------------+----------------+                  |
|                               |                                   |
|                     +---------v---------+                         |
|                     | Natív C könyvtár  |                         |
|                     | (SQLite, Zstd...) |                         |
|                     +-------------------+                         |
+-------------------------------------------------------------------+
```

### 1. Megosztott memórián alapuló átvitel (`gosuda.org/hqq` és `c/`)

A `gosuda.org/hqq` alapcsomag és annak C11 megfelelője a `c/` könyvtárban két megosztott memórián alapuló átviteli módot valósít meg POSIX `mmap` (Linux és macOS) és Win32 `CreateFileMappingW` (Windows) felett. Minden megosztott szegmens egy ellenőrzött `HQQ_STL3` szuperblokkal kezdődik, amely rögzíti a protokollverziót, a pufferelrendezést és a végpontok PID-jeit, mielőtt bármely gyűrűindex olvasásra kerülne.

Minden forró vezérlőszó és slotfejléc 64 bájtos gyorsítótár-sor határokhoz igazított, így a termelő és a fogyasztó magok soha nem versenyeznek ugyanazon a gyorsítótár-soron hamis megosztás (false sharing) miatt.

```text
StandardLink slot-elrendezés (64 bájtos gyorsítótár-sorhoz igazított)
Bájteltolás:  0x00                0x08                0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Fejléc sor:   | seq (u64 atomi)   | len (u32) | flags | fenntartott (nullával töltött) |
              +-------------------+-------------------+-----------------------------+
Payload terület: | payload bájtok [0 .. BufferSize - 1] (közvetlenül kódolva / dekódolva) |
              +---------------------------------------------------------------------+
```

| Mező      | Eltolás | Méret        | Jelentés                                                                                                                    |
| :-------- | ------: | -----------: | :-------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00`  | 8 B          | Monoton sorszámláló. A páratlan értékek folyamatban lévő írást jelölnek; a páros értékek store-release művelettel publikálnak egy véglegesített keretet. |
| `len`     | `0x08`  | 4 B          | A slotba írt payload bájthossza (`0 <= len <= BufferSize`).                                                                 |
| `flags`   | `0x0C`  | 4 B          | Keretvezérlő bitek (folytatás, hibaállapot vagy leállítási értesítés).                                                      |
| `payload` | `0x40`  | `BufferSize` | Összefüggő payload terület, amely közbenső másolások nélkül, helyben kerül olvasásra és írásra.                              |

A hívók a párhuzamossági szerkezettől függően két átviteli szint közül választhatnak:

- **`StandardLink` (1:1 SPSC munkamenet)**: Egy kliens- és egy szerver-munkamenetet páros köt össze dedikált kérés- és válaszslotokon, amelyeket szekvenciazár (`SeqLock`) véd. A gyors úton az író közvetlenül a leképezett slotba szerializál, és atomi store-release művelettel véglegesít; az olvasó rövid ideig felhasználói térben pörög, mielőtt visszaesne az operációs rendszer ébresztőprimitívjeihez (`futex` Linuxon, `__ulock_wait` macOS-en, `WaitOnAddress` Windowson). Egyetlen keret körútja ~200 ns alatt zajlik, `0 allocs/op` mellett.
- **`Advanced` (MPMC diszpécser-gyűrű)**: `M` egyidejű Go hívót multiplexel `N` C worker szálon, zárolásmentes többtermelős többfogyasztós beküldő gyűrűt, megosztott slotkészletet és indexelt befejezési táblát használva. A hívók slotot foglalnak, közzéteszik a slot indexét a gyűrűbe, és befejezési bejegyzésükön várakoznak, miközben a worker szálak sorrenden kívül emelik ki és fejezik be a kéréseket.

Mindkét szint folyamatosan követi a partnerek életjelét. Minden végpont atomi, monoton időbélyeget frissít a `Heartbeat()` hívással, és várakozás közben a `PeerAlive(timeout)` függvénnyel ellenőriz. Ha egy worker folyamat összeomlik hívás közben, a partner azonnal észleli az elavult heartbeatet vagy a megszűnt PID-et, megszakítja a blokkolt várakozásokat `ErrPeerGone` hibával, és támogatja az atomi munkamenet-átvételt (`Takeover`) anélkül, hogy megrekedt megosztott memóriafájlokat hagyna maga után.

### 2. Nulla-allokációs RPC és generáció-ellenőrzött fogantyúk (`gosuda.org/hqq/bindgen`)

Megosztott memóriás keretezés kézi írása egy tucatnyi függvényt tartalmazó C könyvtárhoz fárasztó és hibára hajlamos. A `hqq-bindgen` egy sémafordító, amely egy Interface Definition Language (`.idl`) fájlt olvas (vagy közvetlenül egy C `.h` fejlécből indítja el egy fájl létrehozását a `hqq-bindgen init` paranccsal), és egy nulla-cgo Go kliens csomagot állít elő egy C11 szerver-vázlattal együtt.

Bármely C-kötés legveszélyesebb hibája a nyers C mutatók (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) nyilvános kitétele a nyelvi határon. Ha a hívó felszabadítás után továbbad egy mutatót, egy másik munkamenethez tartozó mutatót ad át, vagy egy egész címet megront, a C könyvtár érvénytelen memóriát dereferál.

A `hqq-bindgen` tiltja a nyers mutatók használatát a vezetéken. Ehelyett az IDL az állapotos C erőforrásokat típusos `handle` deklarációkként modellezi:

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

A vezetéken minden fogantyú egy 64 bites egész, amely egy 32 bites táblaindexet és egy 32 bites, nullától eltérő generációszámlálót tartalmaz: `(uint64_t)generation << 32 | index`. A C futtatókörnyezet (`c/src/hqq_rpc.c`) munkamenetenként egy fogantyútáblát tart fenn három invariánssal:

1. **Generáció-validálás**: Minden keresés ellenőrzi, hogy a slot foglalt-e, tárolt generációja megegyezik-e a fogantyú felső 32 bitjével, és típuscímkéje megegyezik-e a paraméter típusával (`Db` vs. `Stmt`). Egy lezárt fogantyú újrahasználata azonnal `HQQRPC_E_BAD_HANDLE` hibával meghiúsul, mielőtt a C könyvtárat meghívnák.
2. **Hierarchikus tulajdonjog (`@owned_by`)**: Mivel a `Stmt` `@owned_by(Db)` annotációt visel, a `Db` fogantyú lezárása automatikusan bejárja az adott `Db` tulajdonában álló összes élő `Stmt` gyermekfogantyút, és fordított allokációs sorrendben futtatja azok destruktorait (`sqlite3_finalize` a `sqlite3_close_v2` előtt).
3. **Munkamenet-lezárási seprés**: Ha egy Go kliens a fogantyúinak lezárása nélkül bomlik le vagy omlik össze, a C szerver munkamenet-destruktora fordított sorrendben végigsöpri a teljes munkamenet-fogantyútáblát, így garantálva, hogy a kliensek újraindítása után sem marad szivárgó C allokáció.

Mielőtt egy munkamenet feldolgozná első RPC-hívását, a kliens és a szerver kicserél egy 32 bájtos SHA-256 interfész-digestet, amelyet a kanonizált IDL felett számítanak ki (`HQQ-BINDGEN-IDL-V1\n` előtaggal). Ha a Go kliens és a C worker inkompatibilis IDL sémákból készült, a kézfogás már az indításkor elutasítja a kapcsolatot.

### 3. Manipulációmentes bináris végrehajtás (`gosuda.org/hqq/secureexec`)

A C kód worker bináris fájlba helyezése ellátási lánc kérdést vet fel: honnan tudja a Go folyamat, hogy a lemezen lévő worker bináris nem módosult-e vagy cserélődött-e le az ellenőrzés és a végrehajtás között?

A `crypto/sha256` hívása egy fájlútvonalon, majd az `os/exec.Command(path).Start()` meghívása Time-of-Check to Time-of-Use (TOCTOU) ablakot hagy nyitva, amelyben egy másik folyamat felülírhatja vagy felcserélheti a fájlt, mielőtt a kernel végrehajtaná. A `gosuda.org/hqq/secureexec` lezárja ezt az ablakot:

- Linuxon a `secureexec` megnyitja a binárist, és tartalmát egy `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` hívással létrehozott anonim memória-fájlleíróba streameli, miközben kiszámítja annak SHA-256 hash-ét. Ellenőrzi a hash-t egy rögzített 32 bájtos digesttel vagy egy Ed25519-aláírt kiadási manifesttel (`cmd/hqq-manifest`), alkalmazza a `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` lezárást, hogy a rendszeren egyetlen folyamat se módosíthassa a lapokat, majd a gyermekfolyamatot a lezárt leíróból indítja.
- macOS-en és Windowson a `secureexec` az ellenőrzött binárisokat SHA-256 digest szerint kulcsolt, jogosultság-zárolt gyorsítótár-könyvtárakba helyezi, és az indítás előtt ellenőrzi a fájlméret és a digest integritását.

### 4. Szülő-életvonalas folyamatfelügyelet (`gosuda.org/hqq/launch`)

Amikor egy Go alkalmazás háttér C worker folyamatot indít, a workernek soha nem szabad árvaként túlélnie szülőjét, még akkor sem, ha a Go szülő `SIGKILL` (`kill -9`) jelzéssel leáll, és nem tud `defer` takarító kezelőket futtatni.

A `gosuda.org/hqq/launch` egy anonim operációs rendszer-csövön keresztül érvényesíti az élettartam-csatolást, amelyet a worker szabványos bemenetére ad a `--parent-stdin` kapcsolóval. A Go szülő a cső írási végét tartja, és soha nem ír bele; a C worker a olvasási véget egy dedikált életvonal-szálon figyeli. Ha a Go folyamat normálisan kilép, pánikol, vagy az operációs rendszer kernele leállítja (`SIGKILL` vagy OOM killer), a kernel azonnal lezárja az írási leírót. A C worker mikroszekundumokon belül `EOF` jelzést kap a csövön, megszakítja az aktív hívásokat, lefuttatja munkamenet-fogantyú destruktorait, eltávolítja a megosztott memóriafájlokat, és tisztán kilép.

## Gyors kezdés: generált szolgáltatás hívása

A `hqq-bindgen` és a `launch` segítségével az izolált C worker hívása Go-ból szabályos, típusos Go kliensnek tűnik, miközben `CGO_ENABLED=0` marad:

```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) // Környezet 5 másodperces időkorláttal
	defer cancel()

	hash, err := secureexec.ParseHash("7649b1b45dacd934cde8149f35f4fcc3937f6486fbac23ce976466719b3752bb")
	if err != nil {
		log.Fatal(err)
	}

	listener, err := launch.NewListenerName("demo") // Figyelő nevének létrehozása
	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 fut a megosztott memóriás figyelőn %s (opts=%+v)\n", listener, rt.SessionOptions{})
}
```

## Tárolószerkezet

A `gosuda.org/hqq` minden alrendszere egyértelmű határokkal rendelkező, fókuszált csomagokba szerveződik:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) és alacsony szintű megosztott memóriagyűrű primitívek
├── advanced.go / slotpool.go  # Advanced (MPMC) diszpécser, slotkészlet és befejezési tábla
├── shm_*.go / wait_*.go       # OS-specifikus mmap/CreateFileMapping és futex/ulock/WaitOnAddress
├── c/                         # C11 átviteli könyvtár (hqq.c) és RPC szerver futtatókörnyezet (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL elemző, ellenőrző (V1-V41) és Go/C11 kódgenerátor
│   └── rt/                    # Nulla-allokációs Go kliens futtatókörnyezet generált szolgáltatásokhoz
├── secureexec/                # SHA-256 / Ed25519 manifest-ellenőrzés és memfd-lezárt végrehajtás
├── launch/                    # Worker folyamatfelügyelő --parent-stdin életvonal-kezeléssel
├── cmd/hqq-manifest/          # CLI eszköz release bináris manifestek generálásához és aláírásához
└── spec/                      # Végrehajtható vezetékes specifikációk és referencia tesztvektorok (BINDGEN.md)
```

Ahhoz, hogy lásd, hogyan teszi a HQQ lehetővé a Go ökoszisztémában legszélesebb körben használt két `cgo` könyvtár közvetlen, éles környezetbe illeszthető helyettesítését, tekintsd meg az erre a veremre épülő kísérőcsomagokat:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Közvetlenül cserélhető `database/sql` illesztőprogram, amely kompatibilis a `github.com/mattn/go-sqlite3` csomaggal, és natív C SQLite `3.54.1` verziót futtat megosztott memórián, csővezetékes köteg-előtöltéssel.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Közvetlenül cserélhető helyettesítés a `github.com/DataDog/zstd` csomaghoz, amely natív C `libzstd 1.5.7` verziót futtat egy izolált, megosztott memórián alapuló worker folyamatban, nulla-allokációs kontextusokkal és köteges feldolgozással.
- **GitHub repository**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
