---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Izolarea bibliotecilor C de Go prin memorie partajată aliniată la linia de cache'
description: Runtime IPC fără cgo, compilator IDL (hqq-bindgen) și supervizor de procese verificat, care execută biblioteci native C11 în procese worker izolate, prin inele de memorie partajată.
language: ro
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` este un runtime de comunicare inter-proces fără cgo și un set de instrumente de generare de cod pentru Go și C11. Acesta izolează bibliotecile C native în procese worker separate ale sistemului de operare și le conectează la apelanții Go prin inele de memorie partajată, mapate în memorie și aliniate la linia de cache. Proiectul combină două niveluri de transport (`StandardLink` pentru sesiuni dedicate single-producer single-consumer și `Advanced` pentru dispecerizare MPMC cu mai mulți worker), compilatorul IDL `hqq-bindgen` și un supervizor de procese verificat împotriva modificărilor (`secureexec` și `launch`).

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

## Costul partajării unui spațiu de adrese cu C

Fiecare inginer de sisteme Go ajunge, mai devreme sau mai târziu, în aceeași bifurcație. Construiți un serviciu Go care se compilează în câteva secunde cu `CGO_ENABLED=0`, se livrează ca un singur binar static, se compilează încrucișat de pe un laptop către ținte Linux sau Windows fără sysroot și rulează într-un heap cu colectare a gunoiului și siguranță a memoriei. Apoi aveți nevoie de o bibliotecă C matură: SQLite pentru stocare relațională încorporată, Zstandard (`libzstd`) pentru compresie cu dicționar sau o bibliotecă hardware-accelerată pentru media sau criptografie.

Activarea `cgo` pentru a lega acea bibliotecă C în procesul Go rezolvă problema imediată a legării, dar introduce trei vulnerabilități structurale:

1. **Cuplarea lanțului de unelte și a compilării încrucișate**: `CGO_ENABLED=1` necesită un compilator C, anteturi libc ale țintei și un linker specific platformei pentru fiecare sistem de operare și arhitectură țintă. Construirea de artefacte `linux/arm64` sau `windows/arm64` de pe o stație de lucru `darwin/arm64` transformă o singură comandă `go build` într-o matrice de compilatoare încrucișate și sysroot-uri.
2. **Costul de planificare la rulare**: Fiecare apel `cgo` trece granița runtime-ului Go, trece de la o stivă de goroutine extensibilă la o stivă de sistem și fixează un fir de execuție al sistemului de operare (`M`). Pentru apeluri granulare, costul tranziției (~50 până la 150 ns per apel) și contenția planificatorului se acumulează rapid.
3. **Prăbușirea domeniului de defecțiune**: Codul C rulează în același spațiu de adrese virtuale ca runtime-ul Go. Un singur acces în afara limitelor, o utilizare după eliberare, un depășire a stivei sau un `abort()` într-o funcție C corupe metadatele heap-ului Go sau termină imediat întregul proces server, ocolind `recover()` și doborând odată cu el fiecare cerere HTTP sau RPC aflată în curs.

Când echipele încearcă să evite `cgo`, alternativele uzuale impun un alt compromis. Traducerile mecanice pur Go ale bazelor de cod C păstrează `CGO_ENABLED=0`, dar rămân adesea în urma versiunilor C upstream, poartă un strat de memorie virtuală emulat și alocă intens sub sarcină. Mutarea bibliotecii C într-un proces ajutător prin TCP loopback sau socket-uri de domeniu Unix restabilește izolarea defecțiunilor, dar fiecare apel plătește pentru două comutări de context în nucleu, copieri ale buffer-elor de socket și serializare pe fir.

Am construit HQQ după ce ne-am pus o întrebare directă: ce s-ar întâmpla dacă biblioteca C ar trăi într-un proces separat al sistemului de operare, cu propriul spațiu de adrese virtuale, în timp ce Go și C ar schimba cadre direct prin liniile de cache ale procesorului, fără a intra în nucleu pe calea rapidă?

| Model de integrare                 | `CGO_ENABLED=0` | Izolarea defecțiunilor | Apeluri de sistem pe calea rapidă | Alocări heap per apel | Fidelitate față de C11 upstream |
| :--------------------------------- | :-------------- | :--------------------- | --------------------------------: | --------------------: | :------------------------------ |
| **Legare `cgo` în proces**         | Nu              | Niciuna (heap comun)   |                                 0 |                 0 până la 1+ | C upstream exact                |
| **RPC subproces prin socket / pipe** | Da            | Granița procesului     |                           2 până la 4 | Multiple (încadrare + I/O) | C upstream exact                |
| **Traducere mecanică C-to-Go**     | Da              | Go în proces           |                                 0 | Ridicată la apeluri complexe | Runtime C emulat                |
| **Memorie partajată IPC HQQ**      | Da              | Granița procesului     |          0 (spin adaptiv)        | 0 (`StandardLink`)    | C upstream exact                |

## Prezentare arhitecturală: patru straturi peste granițele proceselor

HQQ separă izolarea proceselor în patru straturi componibile, astfel încât mecanica inelelor din memoria partajată, generarea tipizată de cod RPC, verificarea integrității binarelor și supravegherea procesului copil să rămână independente.

```text
+-------------------------------------------------------------------+
| Proces aplicație Go (CGO_ENABLED=0)                               |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Client Go generat     |     | Supervizor de procese         |  |
|  | (gosuda.org/hqq/      |     | (gosuda.org/hqq/launch +      |  |
|  |  bindgen/rt)          |     |  gosuda.org/hqq/secureexec)   |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|  +-----------v-----------+                     | Pipe anonim      |
|  | Nivel de transport    |                     | Legătură de viață|
|  | (StandardLink /       |                     | (--parent-stdin) |
|  |  Advanced MPMC)       |                     |                  |
+--------------+---------------------------------+------------------+
               |                                 |
===============|========= Memorie partajată ======|===================
               |     (mmap / CreateFileMapping)  |
+--------------v---------------------------------v------------------+
| Proces worker C11 (spațiu de adrese izolat)                       |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Server C11 generat    |---->| Tabel de handle-uri cu        |  |
|  | & shim-uri de lot     |     | verificare a generației       |  |
|  |                       |     | (hqq_rpc.c)                   |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|              +----------------+----------------+                  |
|                               |                                   |
|                     +---------v---------+                         |
|                     | Bibliotecă C nativă|                        |
|                     | (SQLite, Zstd...) |                         |
|                     +-------------------+                         |
+-------------------------------------------------------------------+
```

### 1. Transport prin memorie partajată (`gosuda.org/hqq` și `c/`)

Pachetul de bază `gosuda.org/hqq` și omologul său C11 din `c/` implementează două moduri de transport prin memorie partajată, peste `mmap` POSIX (Linux și macOS) și `CreateFileMappingW` din Win32 (Windows). Fiecare segment partajat începe cu un superbloc `HQQ_STL3` verificat, care fixează versiunea protocolului, geometria buffer-elor și PID-urile punctelor finale înainte ca vreun index de inel să fie citit.

Toate cuvintele de control fierbinți și antetele sloturilor sunt aliniate la granițele de linie de cache de 64 de octeți, astfel încât nucleele producătorului și consumatorului să nu concureze niciodată pentru aceeași linie de cache din cauza partajării false.

```text
Structura sloturilor StandardLink (aliniată la linia de cache de 64 de octeți)
Deplasament octet:  0x00              0x08              0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Linia antet:  | seq (u64 atomic)  | len (u32) | flags | rezervat (completat cu zero)|
              +-------------------+-------------------+-----------------------------+
Zona de sarcină: | octeți de sarcină [0 .. BufferSize - 1] (codificați/decodificați direct) |
              +---------------------------------------------------------------------+
```

| Câmp      | Deplasament |   Dimensiune | Semantică                                                                                                                  |
| :-------- | ----------: | -----------: | :------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00`      |          8 B | Contor de secvență monoton. Valorile impare marchează o scriere în curs; valorile pare publică un cadru confirmat prin store-release. |
| `len`     | `0x08`      |          4 B | Lungimea în octeți a sarcinii scrise în slot (`0 <= len <= BufferSize`).                                                   |
| `flags`   | `0x0C`      |          4 B | Biți de control ai cadrului (continuare, stare de eroare sau notificare de oprire).                                        |
| `payload` | `0x40`      | `BufferSize` | Regiune contiguă de sarcină, citită și scrisă pe loc, fără copii intermediare.                                             |

Apelanții aleg între două niveluri de transport, în funcție de structura concurenței:

- **`StandardLink` (sesiune SPSC 1:1)**: Asociază o sesiune client și una server prin sloturi dedicate de cerere și răspuns, protejate printr-un seqlock (`SeqLock`). Pe calea rapidă, scriitorul serializează direct în slotul mapat și confirmă printr-un store-release atomic; cititorul așteaptă scurt în spațiul utilizator înainte de a recurge la primitivele de trezire ale sistemului de operare (`futex` pe Linux, `__ulock_wait` pe macOS, `WaitOnAddress` pe Windows). O rundă completă pentru un singur cadru se finalizează în ~200 ns cu `0 allocs/op`.
- **`Advanced` (inel de dispecerizare MPMC)**: Multiplexează `M` apelanți Go concurenți peste `N` fire de execuție worker C, folosind un inel de trimitere multi-producer multi-consumer fără blocaje, un pool partajat de sloturi și un tabel indexat de completare. Apelanții rezervă un slot, publică indexul slotului în inel și așteaptă intrarea lor de completare, în timp ce firele worker extrag cereri și le finalizează în afara ordinii.

Ambele niveluri urmăresc continuu vitalitatea peer-ului. Fiecare punct final actualizează un timestamp monoton atomic prin `Heartbeat()` și verifică `PeerAlive(timeout)` în timpul așteptării. Dacă un proces worker se prăbușește în mijlocul unui apel, peer-ul detectează imediat heartbeat-ul expirat sau PID-ul mort, întrerupe așteptările blocate cu `ErrPeerGone` și permite preluarea atomică a sesiunii prin `Takeover`, fără a lăsa în urmă fișiere de memorie partajată blocate.

### 2. RPC fără alocări și handle-uri cu verificarea generației (`gosuda.org/hqq/bindgen`)

Scrierea manuală a încadrării în memorie partajată pentru o bibliotecă C cu zeci de funcții este obositoare și predispusă la erori. `hqq-bindgen` este un compilator de scheme care citește un fișier de limbaj de definire a interfețelor (`.idl`) (sau care generează unul direct dintr-un antet C `.h` prin `hqq-bindgen init`) și emite un pachet client Go fără cgo, alături de un schelet de server C11.

Cea mai periculoasă greșeală în orice legare C este expunerea pointerilor C bruti (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) peste granița limbajului. Dacă un apelant transmite un pointer după ce l-a eliberat, transmite un pointer aparținând altei sesiuni sau corupe o adresă întreagă, biblioteca C dereferențiază memorie invalidă.

`hqq-bindgen` interzice pointerii bruti pe fir. În schimb, IDL modelează resursele C cu stare ca declarații tipizate `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>;
}
```

Pe fir, fiecare handle este un întreg de 64 de biți care împachetează un index de tabel de 32 de biți și un contor de generație nenul de 32 de biți: `(uint64_t)generation << 32 | index`. Runtime-ul C (`c/src/hqq_rpc.c`) menține un tabel de handle-uri pentru fiecare sesiune, cu trei invariante:

1. **Validarea generației**: Fiecare căutare verifică faptul că slotul este ocupat, că generația stocată se potrivește cu cei 32 de biți superiori ai handle-ului și că eticheta de tip a handle-ului se potrivește cu tipul parametrului (`Db` față de `Stmt`). Reutilizarea unui handle închis eșuează imediat cu `HQQRPC_E_BAD_HANDLE`, înainte de a apela biblioteca C.
2. **Proprietate ierarhică (`@owned_by`)**: Deoarece `Stmt` este adnotat cu `@owned_by(Db)`, închiderea unui handle `Db` parcurge automat toate handle-urile `Stmt` copil active, deținute de acel `Db`, și rulează destructorii acestora în ordinea inversă a alocării (`sqlite3_finalize` înaintea `sqlite3_close_v2`).
3. **Măturarea la încheierea sesiunii**: Dacă un client Go se deconectează sau se prăbușește fără a închide handle-urile, destructorul sesiunii serverului C parcurge în ordine inversă întregul tabel de handle-uri al sesiunii, garantând absența alocărilor C scurse între reporniri ale clientului.

Înainte ca o sesiune să proceseze primul apel RPC, clientul și serverul schimbă un digest de interfață SHA-256 de 32 de octeți, calculat peste IDL-ul canonicalizat (prefixul `HQQ-BINDGEN-IDL-V1\n`). Dacă clientul Go și worker-ul C au fost compilate din scheme IDL incompatibile, handshake-ul respinge conexiunea la inițializare.

### 3. Execuție binară protejată împotriva modificărilor (`gosuda.org/hqq/secureexec`)

Mutarea codului C într-un binar worker ridică o întrebare de lanț de aprovizionare: cum știe procesul Go că binarul worker de pe disc nu a fost modificat sau înlocuit între verificare și execuție?

Apelarea `crypto/sha256` pe o cale de fișier urmată de `os/exec.Command(path).Start()` lasă o fereastră de tip Time-of-Check to Time-of-Use (TOCTOU), în care un alt proces poate suprascrie sau înlocui fișierul înainte ca nucleul să-l execute. `gosuda.org/hqq/secureexec` închide această fereastră:

- Pe Linux, `secureexec` deschide binarul, transmite conținutul său într-un descriptor de fișier de memorie anonim creat prin `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` și calculează hash-ul SHA-256 în timpul acestui proces, verifică hash-ul față de un digest fixat de 32 de octeți sau față de un manifest de release semnat cu Ed25519 (`cmd/hqq-manifest`), aplică `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` astfel încât niciun proces din sistem să nu poată modifica paginile și pornește procesul copil din descriptorul sigilat.
- Pe macOS și Windows, `secureexec` materializează binarele verificate în directoare de cache cu permisiuni blocate, indexate după digest-ul SHA-256, și verifică integritatea dimensiunii fișierului și a digest-ului înainte de lansare.

### 4. Supravegherea procesului prin legătură de viață cu părintele (`gosuda.org/hqq/launch`)

Când o aplicație Go pornește un proces worker C în fundal, worker-ul nu trebuie să supraviețuiască niciodată părintelui său ca proces orfan, chiar dacă procesul Go părinte este terminat cu `SIGKILL` (`kill -9`) și nu poate rula handler-ele de curățare `defer`.

`gosuda.org/hqq/launch` impune cuplarea duratei de viață printr-un pipe anonim al sistemului de operare, transmis pe intrarea standard a worker-ului cu flag-ul `--parent-stdin`. Procesul Go părinte deține capătul de scriere al pipe-ului și nu scrie niciodată în el; worker-ul C monitorizează capătul de citire pe un fir dedicat de legătură de viață. Dacă procesul Go iese normal, intră în panică sau este ucis de nucleul sistemului de operare (`SIGKILL` sau OOM killer), nucleul închide imediat descriptorul de scriere. Worker-ul C primește `EOF` pe pipe în câteva microsecunde, întrerupe apelurile active, rulează destructorii handle-urilor de sesiune, elimină fișierele de memorie partajată și iese curat.

## Ghid rapid: apelarea unui serviciu generat

Cu `hqq-bindgen` și `launch`, apelarea unui worker C izolat din Go arată ca un client Go tipizat obișnuit, în timp ce `CGO_ENABLED=0` rămâne activ:

```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 rulând pe ascultătorul de memorie partajată %s (opts=%+v)\n", listener, rt.SessionOptions{})
}
```

## Structura depozitului

Fiecare subsistem din `gosuda.org/hqq` este organizat în pachete focalizate, cu limite explicite:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) și primitivele de inel de memorie partajată de nivel jos
├── advanced.go / slotpool.go  # Dispecerul Advanced (MPMC), pool-ul de sloturi și tabelul de completare
├── shm_*.go / wait_*.go       # mmap/CreateFileMapping specifice sistemului de operare și futex/ulock/WaitOnAddress
├── c/                         # Biblioteca de transport C11 (hqq.c) și runtime-ul serverului RPC (hqq_rpc.c)
├── bindgen/                   # Parserul IDL hqq-bindgen, verificatorul (V1-V41) și generatorul de cod Go/C11
│   └── rt/                    # Runtime client Go fără alocări pentru serviciile generate
├── secureexec/                # Verificarea manifestelor SHA-256 / Ed25519 și execuția sigilată prin memfd
├── launch/                    # Supervizorul proceselor worker cu gestionarea legăturii de viață --parent-stdin
├── cmd/hqq-manifest/          # Instrument CLI pentru generarea și semnarea manifestelor binarelor de release
└── spec/                      # Specificații de protocol executabile și vectori de test de referință (BINDGEN.md)
```

Pentru a vedea cum HQQ alimentează înlocuiri de producție, gata de utilizare, pentru două dintre cele mai utilizate biblioteci `cgo` din ecosistemul Go, consultați pachetele companion construite pe această stivă:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Driver `database/sql` compatibil cu `github.com/mattn/go-sqlite3`, care rulează SQLite nativ C `3.54.1` prin memorie partajată, cu prefetch pipelined al loturilor.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Înlocuire directă pentru `github.com/DataDog/zstd`, care rulează `libzstd 1.5.7` nativ C într-un worker izolat cu memorie partajată, cu contexte fără alocări și procesare în loturi.
- **Repository GitHub**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
