---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: C-kirjastojen eristäminen Go-kielestä välimuistirivillä jaetun muistin avulla'
description: Nollacgo-IPC-ajoaika, IDL-kääntäjä (hqq-bindgen) ja todennettu prosessivalvoja, jotka suorittavat natiivit C11-kirjastot eristetyissä työprosesseissa jaettujen muistipuskurien (shared-memory rings) kautta.
language: fi
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` on cgo-vapaa prosessien välinen kommunikointiajoaika (inter-process communication runtime) ja koodigenerointityökalu Go:lle ja C11:lle. Se eristää natiivit C-kirjastot erillisiin käyttöjärjestelmän worker-prosesseihin ja yhdistää ne Go-kutsujiin muistikartoitettujen, välimuistirivin mukaan kohdistettujen jaettujen muistirenkaiden (ring) kautta. Projekti yhdistää kaksi siirtotasoa (`StandardLink` yksittäisille SPSC-istunnoille ja `Advanced` moniworkerisille MPMC-jakelulle), `hqq-bindgen` IDL-kääntäjän sekä todennetun prosessivalvojan (`secureexec` ja `launch`).

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

## Jaetun osoiteavaruuden hinta C-koodin kanssa

Jokainen Go-järjestelmäinsinööri kohtaa lopulta saman haarautumiskohdan. Rakennat Go-palvelua, joka kääntyy sekunneissa komennolla `CGO_ENABLED=0`, toimitetaan yksittäisenä staattisena binäärinä, ristikääntyy kannettavalta tietokoneelta Linux- tai Windows-kohteisiin ilman sysroot-hakemistoa ja ajetaan muistiturvallisen, roskienkeruun hallitseman keon sisällä. Sitten tarvitset kypsän C-kirjaston: SQLiten upotettuun relaatiotietokantatallennukseen, Zstandardin (`libzstd`) sanakirjapakkaukseen tai laitekiihdytetyn media- tai salauskirjaston.

C-kirjaston linkittäminen Go-prosessiin `cgo`:n avulla ratkaisee välittömän sidontaongelman, mutta tuo mukanaan kolme rakenteellista riskiä:

1. **Työkaluketjun ja ristikääntämisen kytkentä**: `CGO_ENABLED=1` edellyttää C-kääntäjää, kohde-libc-otsikkotiedostoja ja alustakohtaisen linkittäjän jokaiselle kohde-käyttöjärjestelmälle ja -arkkitehtuurille. `linux/arm64`- tai `windows/arm64`-artefaktien rakentaminen `darwin/arm64`-työasemalta muuttaa yksittäisen `go build` -komennon ristikääntäjien ja sysroot-hakemistojen matriisiksi.
2. **Ajonaikaisen ajastuksen ylikuormitus**: Jokainen `cgo`-kutsu ylittää Go-ajonaikaisen rajan, vaihtaa kasvavasta goroutine-pinosta järjestelmäpinoon ja sitoo käyttöjärjestelmän säikeen (`M`). Hienojakoisissa kutsuissa siirtymäkustannus (noin 50 ja 150 ns kutsua kohden) sekä ajastimen kilpailu kertyvät nopeasti.
3. **Vikaalueen romahtaminen**: C-koodi toimii samassa virtuaalisessa osoiteavaruudessa kuin Go-ajonaikainen. Yksi ulkopuolinen kirjoitus, käyttö vapautetun muistin jälkeen, pinon ylivuoto tai `abort()` C-funktion sisällä korruptoi Go-keon metatiedot tai lopettaa koko palvelinprosessin välittömästi ohittaen `recover()`-kutsun ja vieden mukanaan jokaisen käsiteltävänä olevan HTTP- tai RPC-pyynnön.

Kun tiimit yrittävät välttää `cgo`:n, yleiset vaihtoehdot pakottavat toisenlaiseen kompromissiin. Puhtaasti Go-kielisiin mekaanisiin käännöksiin perustuvat C-koodikantojen muunnokset säilyttävät `CGO_ENABLED=0`:n, mutta jäävät usein jälkeen C-julkaisuista, sisältävät emuloidun virtuaalimuistikerroksen ja varaavat kuormituksen alla runsaasti muistia. C-kirjaston siirtäminen aputtajaan loopback-TCP:n tai Unix-domain-socketien kautta palauttaa vikaeristyksen, mutta jokainen kutsu maksaa kahdesta ytimen kontekstinvaihdosta, socket-puskurikopioinneista ja siirtoformaatin serialisoinnista.

Rakensimme HQQ:n esitettyämme yhden suoran kysymyksen: entä jos C-kirjasto asuisi erillisessä käyttöjärjestelmäprosessissa omalla virtuaalisella osoiteavaruudellaan, ja Go sekä C vaihtaisivat kehyksiä suoraan jaettujen CPU-välimuistirivien kautta ilman ytimeen siirtymistä nopealla polulla?

| Integraatiomalli                   | `CGO_ENABLED=0` | Vikaeristys          | Nopean polun järjestelmäkutsut | Kutsukohtaiset keon allokaatiot | Upstream C11 -uskollisuus |
| :--------------------------------- | :-------------- | :------------------- | -----------------------------: | ------------------------------: | :------------------------ |
| **`cgo`-sisäinen linkitys**        | Ei              | Ei (jaettu keko)     |                              0 |                       0 tai enemmän | Tarkka upstream C         |
| **Socket / putki -aliprosessi RPC** | Kyllä          | Prosessiraja         |                            2–4 |     Useita (kehystys + I/O)     | Tarkka upstream C         |
| **Mekaaninen C-to-Go-käännös**     | Kyllä           | Prosessinsisäinen Go |                              0 |          Korkea monimutkaisissa kutsuissa | Emuloitu C-ajonaikainen |
| **HQQ jaetun muistin IPC**         | Kyllä           | Prosessiraja         |          0 (adaptiivinen odotus) |     0 (`StandardLink`)          | Tarkka upstream C         |

## Arkkitehtuurin yleiskuvaus: neljä kerrosta prosessirajojen yli

HQQ erottaa prosessieristyksen neljään yhdisteltävään kerrokseen, jotta jaetun muistin renkaiden mekaniikka, tyypitetty RPC-koodigenerointi, binäärien eheyden varmistaminen ja lapsiprosessien valvonta pysyvät toisistaan riippumattomina.

```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. Jaetun muistin siirto (`gosuda.org/hqq` ja `c/`)

Peruspaketti `gosuda.org/hqq` ja sen C11-vastine hakemistossa `c/` toteuttavat kaksi jaetun muistin siirtotilaa POSIX-`mmap`:n (Linux ja macOS) sekä Win32-`CreateFileMappingW`:n (Windows) päällä. Jokainen jaettu segmentti alkaa todennetulla `HQQ_STL3`-superlohkolla, joka kiinnittää protokollaversion, puskurigeometrian ja päätepisteiden PID-tunnisteet ennen kuin mitään renkaan indeksiä luetaan.

Kaikki aktiiviset ohjaussanat ja slotien otsikot on kohdistettu 64 tavun välimuistirivin rajoille, jotta tuottaja- ja kuluttajaytimet eivät kilpaile samasta välimuistirivistä väärän jakamisen (false sharing) kautta.

```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)    |
              +---------------------------------------------------------------------+
```

| Kenttä    | Siirtymä | Koko         | Merkitys                                                                                                                                  |
| :-------- | -------: | -----------: | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `seq`     | `0x00`   | 8 B          | Monotoninen sekvenssilaskuri. Parittomat arvot merkitsevät kesken olevaa kirjoitusta; parilliset arvot julkaisevat vahvistetun kehyksen store-release-operaatiolla. |
| `len`     | `0x08`   | 4 B          | Slottiin kirjoitetun hyötykuorman tavupituus (`0 <= len <= BufferSize`).                                                                   |
| `flags`   | `0x0C`   | 4 B          | Kehyksen ohjausbitit (jatkuvuus, virhetila tai sammutusilmoitus).                                                                          |
| `payload` | `0x40`   | `BufferSize` | Yhtenäinen hyötykuormaalue, johon luetaan ja josta kirjoitetaan paikallaan ilman välikopiointeja.                                          |

Kutsuja valitsee kahden siirtotason välillä samanaikaisuusrakenteen mukaan:

- **`StandardLink` (1:1 SPSC-istunto)**: Muodostaa client- ja server-istuntoparin omistettujen pyyntö- ja vastausslottien kautta, joita suojaa sekvenssilukko (`SeqLock`). Nopealla polulla kirjoittaja serialisoi tiedot suoraan kartoitettuun slottiin ja vahvistaa ne atomisella store-release-operaatiolla. Lukija odottaa lyhyesti käyttäjätilassa ennen kuin siirtyy käyttöjärjestelmän herätysprimitiiveihin (`futex` Linuxilla, `__ulock_wait` macOS:llä, `WaitOnAddress` Windowsilla). Yhden kehyksen kierros valmistuu noin 200 ns:ssa ja `0 allocs/op`.
- **`Advanced` (MPMC-jakelurengas)**: Multipleksoi `M` samanaikaista Go-kutsujaa `N` C-worker-säikeelle lukkovapaan moniesittäjä-moniottajan lähetysrenkaan, jaetun slottialtaan ja indeksoidun valmistumistaulun avulla. Kutsujat varaavat slotin, julkaisevat slotin indeksin renkaaseen ja odottavat valmistumismerkintäänsä, kun worker-säikeet poistavat pyyntöjä jonosta ja suorittavat ne epäjärjestyksessä.

Molemmat tasot seuraavat vastapuolen elossaoloa jatkuvasti. Jokainen päätepiste päivittää atomisen monotonisen aikaleiman `Heartbeat()`-kutsulla ja tarkastaa `PeerAlive(timeout)`-funktiolla odottaessaan. Jos worker-prosessi kaatuu kesken kutsun, vastapuoli havaitsee vanhentuneen sykkeen tai kuolleen PID:n välittömästi, keskeyttää estävät odotukset virheellä `ErrPeerGone` ja tukee atomista istunnon `Takeover`-siirtoa jättämättä jäljelle jumiutuneita jaetun muistin tiedostoja.

### 2. Nollakeon RPC ja sukupolvitarkistetut kahvat (`gosuda.org/hqq/bindgen`)

Jaetun muistin kehystyksen kirjoittaminen käsin C-kirjastolle, jossa on kymmeniä funktioita, on työlästä ja virhealtista. `hqq-bindgen` on skeemakääntäjä, joka lukee Interface Definition Language (`.idl`) -tiedoston (tai alustaa sellaisen suoraan C-`.h`-otsikkotiedostosta komennolla `hqq-bindgen init`) ja tuottaa cgo-vapaan Go-client-paketin sekä C11-palvelinrungon.

Yleisin vakava virhe missä tahansa C-sidonnassa on paljastaa raakoja C-osoittimia (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) kielirajan yli. Jos kutsuja välittää osoittimen sen jälkeen, kun se on vapautettu, välittää osoittimen toiselle istunnolle tai korruptoi kokonaislukuosoitteen, C-kirjasto dereferoi virheellisen muistin.

`hqq-bindgen` kieltää raa'at osoittimet langalla. Sen sijaan IDL mallintaa tilalliset C-resurssit tyypitettyinä `handle`-määrittelyinä:

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

Langalla jokainen kahva on 64-bittinen kokonaisluku, joka pakkaa 32-bittisen taulukkoindeksin ja 32-bittisen nollasta poikkeavan sukupolvilaskurin: `(uint64_t)generation << 32 | index`. C-ajonaikainen (`c/src/hqq_rpc.c`) ylläpitää istuntokohtaista kahvataulukkoa, jossa on kolme invarianttia:

1. **Sukupolven validointi**: Jokainen haku varmistaa, että slotti on varattu, sen tallennettu sukupolvi vastaa kahvan ylempiä 32 bittiä ja sen kahvatyypin tunniste vastaa parametrin tyyppiä (`Db` vs. `Stmt`). Suljetun kahvan uudelleenkäyttö epäonnistuu välittömästi virheellä `HQQRPC_E_BAD_HANDLE` ennen kuin C-kirjastoa kutsutaan.
2. **Hierarkkinen omistajuus (`@owned_by`)**: Koska `Stmt` on merkitty tunnisteella `@owned_by(Db)`, `Db`-kahvan sulkeminen käy automaattisesti läpi kaikki sen omistamat elävät lapsi-`Stmt`-kahvat ja suorittaa niiden tuhoajat käänteisessä allokointijärjestyksessä (`sqlite3_finalize` ennen `sqlite3_close_v2`:ta).
3. **Istunnon purkupyyhkäisy**: Jos Go-client katkeaa tai kaatuu sulkematta kahvojaan, C-palvelimen istunnon tuhoaja pyyhkii koko istuntokahvataulukon käänteisessä järjestyksessä. Tämä takaa, ettei C-allokaatioita vuoda clientin uudelleenkäynnistysten yli.

Ennen kuin istunto käsittelee ensimmäisen RPC-kutsun, client ja palvelin vaihtavat 32 tavun SHA-256-rajapintatiivisteen, joka on laskettu kanonisoidusta IDL:stä (etuliite `HQQ-BINDGEN-IDL-V1\n`). Jos Go-client ja C-worker on käännetty yhteensopimattomista IDL-skeemoista, kättely hylkää yhteyden jo käynnistyksessä.

### 3. Väärennyksetön binäärien suoritus (`gosuda.org/hqq/secureexec`)

C-koodin siirtäminen worker-binääriin nostaa toimitusketjukysymyksen: miten Go-prosessi tietää, ettei levyllä oleva worker-binääri ole muuttunut tai korvattu tarkastuksen ja suorituksen välillä?

Jos `crypto/sha256` kutsutaan tiedostopolulle ja sen jälkeen suoritetaan `os/exec.Command(path).Start()`, jää Time-of-Check to Time-of-Use (TOCTOU) -ikkuna, jonka aikana toinen prosessi voi ylikirjoittaa tai vaihtaa tiedoston ennen kuin ydin suorittaa sen. `gosuda.org/hqq/secureexec` sulkee tämän ikkunan seuraavasti:

- Linuxilla `secureexec` avaa binäärin, virtaa sen sisällön anonyymiin muistitiedostokuvaajaan, joka luodaan kutsulla `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)`, ja laskee samalla sen SHA-256-tiivisteen. Se vertaa tiivistettä kiinnitettyyn 32 tavun tiivisteeseen tai Ed25519-allekirjoitettuun julkaisumanifestiin (`cmd/hqq-manifest`), soveltaa kutsua `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)`, jotta mikään järjestelmän prosessi ei voi muuttaa sivuja, ja käynnistää lapsiprosessin sinetöidystä kuvaajasta.
- macOS:llä ja Windowsilla `secureexec` materialisoi todennetut binäärit lukittuihin käyttöoikeuksiin sidottuihin välimuistihakemistoihin, jotka on nimetty SHA-256-tiivisteen mukaan, ja tarkistaa tiedoston koon sekä tiivisteen eheyden ennen käynnistystä.

### 4. Vanhempi-yhteyden prosessivalvonta (`gosuda.org/hqq/launch`)

Kun Go-sovellus käynnistää taustalla C-worker-prosessin, worker ei saa koskaan jäädä orvoksi vanhempansa kuoleman jälkeen, vaikka Go-vanhempi lopetettaisiin komennolla `SIGKILL` (`kill -9`) eikä se ehtisi suorittaa `defer`-siivoustoimintoja.

`gosuda.org/hqq/launch` pakottaa elinkaariyhteyden anonyymin käyttöjärjestelmän putken kautta, joka välitetään workerin vakiosyötteeseen lipulla `--parent-stdin`. Go-vanhempi pitää putken kirjoituspäätä eikä koskaan kirjoita siihen. C-worker valvoo lukupäätä omalla elinkaarisäikeellaan. Jos Go-prosessi päättyy normaalisti, kaatuu paniikkiin tai käyttöjärjestelmän ydin tappaa sen (`SIGKILL` tai OOM-killer), ydin sulkee kirjoituskuvaajan välittömästi. C-worker saa `EOF`-merkin putkesta muutamassa mikrosekunnissa, keskeyttää aktiiviset kutsut, suorittaa istuntokahvojen tuhoajat, poistaa jaetun muistin tiedostot linkityksistä ja poistuu siististi.

## Pikaopas: generoidun palvelun kutsuminen

`hqq-bindgen`:n ja `launch`:n avulla eristetyn C-workerin kutsuminen Go:sta näyttää tavalliselta tyypitetyltä Go-clientilta säilyttäen samalla `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 running on shared-memory listener %s (opts=%+v)\n", listener, rt.SessionOptions{})
}
```

## Repositorion rakenne

Jokainen `gosuda.org/hqq`:n osajärjestelmä on jäsennelty kohdennettuihin paketteihin, joilla on selkeät rajat:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) ja matalan tason jaetun muistin rengasprimitiivit
├── advanced.go / slotpool.go  # Advanced (MPMC) -jakelija, slottialtaa ja valmistumistaulu
├── shm_*.go / wait_*.go       # Käyttöjärjestelmäkohtainen mmap/CreateFileMapping sekä futex/ulock/WaitOnAddress
├── c/                         # C11-siirtokirjasto (hqq.c) ja RPC-palvelimen ajonaikainen (hqq_rpc.c)
├── bindgen/                   # hqq-bindgen IDL-jäsennin, verifioija (V1–V41) ja Go/C11-koodigeneraattori
│   └── rt/                    # Nollakeon Go-clientin ajonaikainen generoiduille palveluille
├── secureexec/                # SHA-256/Ed25519-manifestin verifiointi ja memfd-sinetöity suoritus
├── launch/                    # Worker-prosessivalvoja, jossa --parent-stdin-elinkaarihallinta
├── cmd/hqq-manifest/          # CLI-työkalu julkaisubinäärimanifestien generointiin ja allekirjoittamiseen
└── spec/                      # Suoritettavat siirtomäärittelyt ja kultaiset testivektorit (BINDGEN.md)
```

Nähdäksesi, miten HQQ tarjoaa pudotettavat tuotantokorvikkeet kahdelle laajimmin käytetylle `cgo`-kirjastolle Go-ekosysteemissä, tutustu tähän pinoon rakennettuihin kumppanipaketteihin:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Pudotettava `database/sql`-ajuri, joka on yhteensopiva `github.com/mattn/go-sqlite3`:n kanssa ja ajaa natiivia C SQLite `3.54.1`:tä jaetun muistin yli putkitetulla eräesihakulla.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Pudotettava korvike kirjastolle `github.com/DataDog/zstd`, joka ajaa natiivia C `libzstd 1.5.7`:ää eristetyssä jaetun muistin worker-prosessissa nollakeon konteksteilla ja eräkäsittelyllä.
- **GitHub-repositorio**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
