---
id: 43974d116e135fd9d3478f658dcf71c6
author: Lemon Mint
title: 'HQQ: Isolamento de Bibliotecas C em Relação ao Go por Meio de Memória Compartilhada Alinhada a Linhas de Cache'
description: Runtime de IPC sem cgo, compilador de IDL (hqq-bindgen) e supervisor de processos verificado que executam bibliotecas C11 nativas em processos de trabalho isolados por meio de anéis de memória compartilhada.
language: pt
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` é um runtime de comunicação entre processos sem cgo e uma cadeia de ferramentas de geração de código para Go e C11. Ele isola bibliotecas nativas em C dentro de processos de trabalho separados do sistema operacional e os conecta a chamadores em Go por meio de anéis de memória compartilhada mapeados em memória e alinhados à linha de cache. O projeto combina duas camadas de transporte (`StandardLink`, para sessões dedicadas do tipo single-producer single-consumer, e `Advanced`, para despacho MPMC com múltiplos workers), o compilador de IDL `hqq-bindgen` e um supervisor de processos com verificação de integridade (`secureexec` e `launch`).

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

## O Custo de Compartilhar um Espaço de Endereçamento com C

Todo engenheiro de sistemas em Go, em algum momento, se depara com a mesma bifurcação. Você está construindo um serviço em Go que compila em segundos com `CGO_ENABLED=0`, é distribuído como um único binário estático, é compilado de forma cruzada a partir de um notebook para alvos Linux ou Windows sem um sysroot e executa dentro de um heap com coleta de lixo e segurança de memória. Então, você precisa de uma biblioteca em C madura: SQLite para armazenamento relacional embarcado, Zstandard (`libzstd`) para compressão com dicionário, ou uma biblioteca de mídia ou criptografia com aceleração por hardware.

Habilitar `cgo` para vincular essa biblioteca em C ao processo Go resolve o problema imediato de ligação, mas introduz três passivos estruturais:

1. **Acoplamento de Toolchain e Compilação Cruzada**: `CGO_ENABLED=1` exige um compilador C, cabeçalhos da libc do alvo e um linker da plataforma para cada sistema operacional e arquitetura de destino. Construir artefatos `linux/arm64` ou `windows/arm64` a partir de uma estação `darwin/arm64` transforma um único comando `go build` em uma matriz de compiladores cruzados e sysroots.
2. **Sobrecarga de Escalonamento em Runtime**: Toda chamada `cgo` cruza a fronteira do runtime de Go, troca uma pilha de goroutine扩展ável por uma pilha de sistema e fixa uma thread do sistema operacional (`M`). Para chamadas de granularidade fina, o custo de transição (~50 a 150 ns por chamada) e a contenção do escalonador se acumulam rapidamente.
3. **Colapso do Domínio de Falhas**: O código C executa no mesmo espaço de endereçamento virtual do runtime de Go. Uma única escrita fora dos limites, um uso após liberação, um estouro de pilha ou um `abort()` dentro de uma função C corrompe os metadados do heap de Go ou encerra imediatamente todo o processo do servidor, contornando `recover()` e derrubando consigo todas as requisições HTTP ou RPC em andamento.

Quando as equipes tentam evitar `cgo`, as alternativas comuns impõem outro compromisso. Traduções mecânicas puras de bases de código C para Go preservam `CGO_ENABLED=0`, mas frequentemente ficam atrás das versões upstream em C, carregam uma camada de memória virtual emulada e alocam intensamente sob carga. Mover a biblioteca C para um processo auxiliar por meio de TCP em loopback ou sockets de domínio Unix restaura o isolamento de falhas, mas cada chamada paga por duas trocas de contexto no kernel, cópias de buffers de socket e serialização de transporte.

Construímos o HQQ após nos fazermos uma pergunta direta: e se a biblioteca C residisse em um processo separado do sistema operacional, com seu próprio espaço de endereçamento virtual, enquanto Go e C trocassem quadros diretamente por meio de linhas de cache de CPU compartilhadas, sem entrar no kernel no caminho rápido?

| Modelo de Integração              | `CGO_ENABLED=0` | Isolamento de Falhas         | Syscalls no Caminho Rápido | Alocações de Heap por Chamada | Fidelidade ao C11 Upstream |
| :-------------------------------- | :-------------- | :--------------------------- | -------------------------: | ----------------------------: | :------------------------- |
| **Vinculação `cgo` In-Process**   | Não             | Nenhum (heap compartilhado)  |                          0 |                      0 a 1+   | C upstream exato           |
| **RPC de Subprocesso via Socket / Pipe** | Sim      | Fronteira de processo        |                    2 a 4   | Múltiplas (enquadramento + E/S) | C upstream exato         |
| **Tradução Mecânica de C para Go** | Sim             | Go in-process                |                          0 | Alta em chamadas complexas    | Runtime de C emulado       |
| **HQQ IPC com Memória Compartilhada** | Sim          | Fronteira de processo        | 0 (spin adaptativo)        | 0 (`StandardLink`)            | C upstream exato           |

## Visão Arquitetural: Quatro Camadas Através das Fronteiras de Processo

O HQQ separa o isolamento de processos em quatro camadas combináveis, de modo que a mecânica dos anéis de memória compartilhada, a geração de RPC tipado, a verificação de integridade binária e a supervisão de processos filhos permaneçam independentes.

```text
+-------------------------------------------------------------------+
| Processo da Aplicação Go (CGO_ENABLED=0)                          |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Cliente Go Gerado     |     | Supervisor de Processos       |  |
|  | (gosuda.org/hqq/      |     | (gosuda.org/hqq/launch +      |  |
|  |  bindgen/rt)          |     |  gosuda.org/hqq/secureexec)   |  |
|  +-----------+-----------+     +---------------+---------------+  |
|              |                                 |                  |
|  +-----------v-----------+                     | Pipe Anônimo     |
|  | Camada de Transporte  |                     | Lifeline         |
|  | (StandardLink /       |                     | (--parent-stdin) |
|  |  Advanced MPMC)       |                     |                  |
+--------------+---------------------------------+------------------+
               |                                 |
===============|========= Memória Compartilhada =|===================
               |     (mmap / CreateFileMapping)  |
+--------------v---------------------------------v------------------+
| Processo Worker C11 (Espaço de Endereçamento Isolado)             |
|                                                                   |
|  +-----------------------+     +-------------------------------+  |
|  | Servidor C11 Gerado   |---->| Tabela de Handles com         |  |
|  | & Shims de Lote       |     | Verificação de Geração        |  |
|  +-----------+-----------+     | (hqq_rpc.c)                   |  |
|              |                 +---------------+---------------+  |
|              +----------------+----------------+                  |
|                               |                                   |
|                     +---------v---------+                         |
|                     | Biblioteca C      |                         |
|                     | Nativa (SQLite,   |                         |
|                     | Zstd...)          |                         |
|                     +-------------------+                         |
+-------------------------------------------------------------------+
```

### 1. Transporte por Memória Compartilhada (`gosuda.org/hqq` e `c/`)

O pacote base `gosuda.org/hqq` e sua contraparte em C11, em `c/`, implementam dois modos de transporte por memória compartilhada sobre `mmap` do POSIX (Linux e macOS) e `CreateFileMappingW` da Win32 (Windows). Todo segmento compartilhado começa com um superbloco `HQQ_STL3` verificado, que fixa a versão do protocolo, a geometria dos buffers e os PIDs dos endpoints antes que qualquer índice de anel seja lido.

Todas as palavras de controle ativas e os cabeçalhos de slot são alinhados a fronteiras de linha de cache de 64 bytes, de modo que os núcleos produtor e consumidor nunca disputem a mesma linha de cache por meio de false sharing.

```text
Layout de Slot do StandardLink (Alinhado à Linha de Cache de 64 Bytes)
Deslocamento em Bytes:  0x00              0x08              0x10                          0x40
              +-------------------+-------------------+-----------------------------+
Linha de Cabeçalho:  | seq (u64 atômico) | len (u32) | flags | reservado (preenchido com zeros) |
              +-------------------+-------------------+-----------------------------+
Área de Payload:     | bytes de payload [0 .. BufferSize - 1] (codificados / decodificados diretamente) |
              +---------------------------------------------------------------------+
```

| Campo     | Deslocamento |        Tamanho | Semântica                                                                                                                       |
| :-------- | -----------: | -------------: | :------------------------------------------------------------------------------------------------------------------------------ |
| `seq`     | `0x00`       |           8 B  | Contador de sequência monotônico. Valores ímpares marcam uma escrita em andamento; valores pares publicam um quadro confirmado por meio de store-release. |
| `len`     | `0x08`       |           4 B  | Tamanho em bytes do payload gravado no slot (`0 <= len <= BufferSize`).                                                          |
| `flags`   | `0x0C`       |           4 B  | Bits de controle do quadro (continuação, status de erro ou aviso de encerramento).                                               |
| `payload` | `0x40`       | `BufferSize`   | Região contígua de payload, lida e escrita no local, sem cópias intermediárias.                                                  |

Os chamadores escolhem entre duas camadas de transporte, dependendo da estrutura de concorrência:

- **`StandardLink` (Sessão SPSC 1:1)**: Emparelha uma sessão de cliente e uma de servidor por meio de slots dedicados de requisição e resposta, protegidos por um lock de sequência (`SeqLock`). No caminho rápido, o escritor serializa diretamente no slot mapeado e confirma com um store-release atômico; o leitor faz um spin breve no espaço do usuário antes de recorrer às primitivas de despertar do sistema operacional (`futex` no Linux, `__ulock_wait` no macOS, `WaitOnAddress` no Windows). Uma ida e volta de quadro único é concluída em ~200 ns, com `0 allocs/op`.
- **`Advanced` (Anel de Despacho MPMC)**: Multiplexa `M` chamadores concorrentes em Go entre `N` threads workers em C, usando um anel de submissão multi-producer multi-consumer sem locks, um pool de slots compartilhado e uma tabela de conclusão indexada. Os chamadores reservam um slot, publicam o índice do slot no anel e aguardam sua entrada de conclusão, enquanto as threads workers retiram requisições do anel e as concluem fora de ordem.

Ambas as camadas monitoram continuamente a vitalidade dos pares. Cada endpoint atualiza um timestamp monotônico atômico por meio de `Heartbeat()` e consulta `PeerAlive(timeout)` durante a espera. Se um processo worker falhar no meio de uma chamada, o par detecta imediatamente o heartbeat obsoleto ou o PID inativo, interrompe as esperas bloqueadas com `ErrPeerGone` e oferece suporte a `Takeover` atômico de sessão, sem deixar arquivos de memória compartilhada travados.

### 2. RPC Sem Alocações e Handles com Verificação de Geração (`gosuda.org/hqq/bindgen`)

Escrever o enquadramento de memória compartilhada manualmente para uma biblioteca C com dezenas de funções é tedioso e propenso a erros. O `hqq-bindgen` é um compilador de esquemas que lê um arquivo de Linguagem de Definição de Interface (`.idl`) (ou gera um diretamente a partir de um cabeçalho C `.h` por meio de `hqq-bindgen init`) e emite um pacote cliente Go sem cgo, junto com um esqueleto de servidor C11.

O erro mais perigoso em qualquer binding para C é expor ponteiros C brutos (`sqlite3*`, `sqlite3_stmt*`, `ZSTD_CCtx*`) através da fronteira de linguagem. Se um chamador passar um ponteiro após tê-lo liberado, passar um ponteiro pertencente a outra sessão ou corromper um endereço inteiro, a biblioteca C desreferenciará memória inválida.

O `hqq-bindgen` proíbe ponteiros brutos no fio. Em vez disso, o IDL modela recursos C com estado como declarações tipadas de `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>;
}
```

No fio, todo handle é um inteiro de 64 bits que empacota um índice de tabela de 32 bits e um contador de geração de 32 bits não nulo: `(uint64_t)generation << 32 | index`. O runtime C (`c/src/hqq_rpc.c`) mantém uma tabela de handles por sessão, com três invariantes:

1. **Validação de Geração**: Toda busca verifica se o slot está ocupado, se sua geração armazenada corresponde aos 32 bits superiores do handle e se sua etiqueta de tipo de handle corresponde ao tipo do parâmetro (`Db` versus `Stmt`). Reutilizar um handle fechado falha imediatamente com `HQQRPC_E_BAD_HANDLE`, antes de qualquer chamada à biblioteca C.
2. **Propriedade Hierárquica (`@owned_by`)**: Como `Stmt` é anotado com `@owned_by(Db)`, fechar um handle `Db` percorre automaticamente todos os handles `Stmt` filhos vivos pertencentes a esse `Db` e executa seus destrutores na ordem inversa de alocação (`sqlite3_finalize` antes de `sqlite3_close_v2`).
3. **Varredura de Encerramento da Sessão**: Se um cliente Go se desconectar ou falhar sem fechar seus handles, o destrutor de sessão do servidor C varre toda a tabela de handles da sessão em ordem inversa, garantindo zero alocações C vazadas entre reinicializações do cliente.

Antes que uma sessão processe sua primeira chamada RPC, o cliente e o servidor trocam um digest de interface SHA-256 de 32 bytes, calculado sobre o IDL canonicalizado (prefixo `HQQ-BINDGEN-IDL-V1\n`). Se o cliente Go e o worker C tiverem sido compilados a partir de esquemas IDL incompatíveis, o handshake rejeita a conexão já na inicialização.

### 3. Execução Binária à Prova de Adulteração (`gosuda.org/hqq/secureexec`)

Mover código C para um binário de worker levanta uma questão de cadeia de suprimentos: como o processo Go sabe que o binário do worker em disco não foi modificado ou substituído entre a verificação e a execução?

Chamar `crypto/sha256` sobre um caminho de arquivo seguido de `os/exec.Command(path).Start()` deixa uma janela de tempo entre verificação e uso (TOCTOU, Time-of-Check to Time-of-Use) em que outro processo pode sobrescrever ou trocar o arquivo antes que o kernel o execute. O `gosuda.org/hqq/secureexec` fecha essa janela:

- No Linux, o `secureexec` abre o binário, transmite seu conteúdo para um descritor de arquivo de memória anônimo criado via `memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING)` enquanto calcula seu hash SHA-256, verifica o hash contra um digest fixado de 32 bytes ou contra um manifesto de release assinado com Ed25519 (`cmd/hqq-manifest`), aplica `fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)` para que nenhum processo do sistema possa mutar as páginas e cria o processo filho a partir do descritor selado.
- No macOS e no Windows, o `secureexec` materializa binários verificados em diretórios de cache com permissões restritas, indexados pelo digest SHA-256, e verifica a integridade do tamanho e do digest do arquivo antes do lançamento.

### 4. Supervisão de Processos com Lifeline Parental (`gosuda.org/hqq/launch`)

Quando uma aplicação Go cria um processo worker C em segundo plano, o worker nunca deve sobreviver ao seu pai como um processo órfão, mesmo que o pai Go seja encerrado com `SIGKILL` (`kill -9`) e não consiga executar handlers de limpeza com `defer`.

O `gosuda.org/hqq/launch` impõe o acoplamento de tempo de vida por meio de um pipe anônimo do sistema operacional, passado à entrada padrão do worker com a flag `--parent-stdin`. O pai Go mantém a extremidade de escrita do pipe e nunca escreve nele; o worker C monitora a extremidade de leitura em uma thread lifeline dedicada. Se o processo Go terminar normalmente, entrar em pânico ou for morto pelo kernel do sistema operacional (`SIGKILL` ou pelo OOM killer), o kernel fecha imediatamente o descritor de escrita. O worker C recebe `EOF` no pipe em microssegundos, interrompe quaisquer chamadas ativas, executa seus destrutores de handles de sessão, remove os arquivos de memória compartilhada e encerra de forma limpa.

## Início Rápido: Chamando um Serviço Gerado

Com o `hqq-bindgen` e o `launch`, chamar um worker C isolado a partir de Go se parece com um cliente Go tipado comum, mantendo `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 em execução no listener de memória compartilhada %s (opts=%+v)\n", listener, rt.SessionOptions{})
}
```

## Estrutura do Repositório

Cada subsistema em `gosuda.org/hqq` é organizado em pacotes focados, com limites explícitos:

```text
gosuda.org/hqq
├── standard.go / ring.go      # StandardLink (SPSC) e primitivas de anel de memória compartilhada de baixo nível
├── advanced.go / slotpool.go  # Despachante Advanced (MPMC), pool de slots e tabela de conclusão
├── shm_*.go / wait_*.go       # mmap/CreateFileMapping específicos do SO e futex/ulock/WaitOnAddress
├── c/                         # Biblioteca de transporte C11 (hqq.c) e runtime do servidor RPC (hqq_rpc.c)
├── bindgen/                   # Parser IDL do hqq-bindgen, verificador (V1-V41) e gerador de código Go/C11
│   └── rt/                    # Runtime cliente Go sem alocações para serviços gerados
├── secureexec/                # Verificação de manifesto SHA-256 / Ed25519 e execução selada por memfd
├── launch/                    # Supervisor de processos worker com gerenciamento de lifeline --parent-stdin
├── cmd/hqq-manifest/          # Ferramenta de linha de comando para gerar e assinar manifestos de binários de release
└── spec/                      # Especificações executáveis de protocolo e vetores de teste dourados (BINDGEN.md)
```

Para ver como o HQQ viabiliza substitutos de produção prontos para uso de duas das bibliotecas `cgo` mais utilizadas no ecossistema Go, conheça os pacotes complementares construídos sobre essa pilha:

- **[`gosuda.org/sqlite3`](https://gosuda.org/sqlite3)**: Driver `database/sql` compatível com `github.com/mattn/go-sqlite3`, executando o SQLite nativo em C `3.54.1` sobre memória compartilhada, com pré-busca em lotes em pipeline.
- **[`gosuda.org/zstd`](https://gosuda.org/zstd)**: Substituto direto de `github.com/DataDog/zstd`, executando o `libzstd 1.5.7` nativo em C em um worker isolado com memória compartilhada, com contextos sem alocações e processamento em lote.
- **Repositório no GitHub**: [https://github.com/gosuda/HQQ](https://github.com/gosuda/HQQ)
