GoSuda

HQQ: Mengisolasi Pustaka C dari Go Melalui Memori Bersama *Cache-Line*

By Lemon Mint
views ...

gosuda.org/hqq adalah runtime komunikasi antar-proses zero-cgo dan toolchain pembuatan kode untuk Go dan C11. Toolchain ini mengisolasi pustaka C asli di dalam proses pekerja OS terpisah dan menghubungkannya ke pemanggil Go melalui ring memori bersama yang dipetakan memori dan disejajarkan dengan baris tembolok. Proyek ini menggabungkan dua lapisan transpor (StandardLink untuk sesi produsen tunggal konsumen tunggal yang berdedikasi dan Advanced untuk pengiriman MPMC multi-pekerja), kompiler IDL hqq-bindgen, dan pengawas proses yang diverifikasi dari manipulasi (secureexec dan launch).

1go get gosuda.org/hqq

Biaya Berbagi Ruang Alamat dengan C

Setiap insinyur sistem Go pada akhirnya akan mencapai persimpangan jalan yang sama. Anda sedang membangun layanan Go yang dikompilasi dalam hitungan detik dengan CGO_ENABLED=0, dikirimkan sebagai satu biner statis tunggal, dikompilasi silang dari laptop ke target Linux atau Windows tanpa sysroot, dan berjalan di dalam tumpukan terkelola pengumpulan sampah yang aman dari memori. Kemudian Anda memerlukan pustaka C yang matang: SQLite untuk penyimpanan relasional tertanam, Zstandard (libzstd) untuk kompresi kamus, atau pustaka media atau kriptografi yang dipercepat perangkat keras.

Mengaktifkan cgo untuk menautkan pustaka C tersebut ke dalam proses Go memecahkan masalah pengikatan langsung sekaligus memperkenalkan tiga kerentanan struktural:

  1. Penggabungan Toolchain dan Kompilasi Silang: CGO_ENABLED=1 memerlukan kompiler C, header libc target, dan penaut platform untuk setiap OS dan arsitektur target. Membangun artefak linux/arm64 atau windows/arm64 dari workstation darwin/arm64 mengubah perintah go build tunggal menjadi matriks kompiler silang dan sysroot.
  2. Kelebihan Penjadwalan Runtime: Setiap panggilan cgo melintasi batas runtime Go, beralih dari tumpukan goroutine yang dapat tumbuh ke tumpukan sistem, dan menyematkan utas OS (M). Untuk panggilan berbutir halus, pajak transisi (~50 hingga 150 ns per panggilan) dan kontensi penjadwal terakumulasi dengan cepat.
  3. Runtuhnya Domain Kesalahan: Kode C berjalan di ruang alamat virtual yang sama dengan runtime Go. Satu penulisan di luar batas, penggunaan setelah pembebasan, limpahan tumpukan, atau abort() di dalam fungsi C merusak metadata tumpukan Go atau langsung menghentikan seluruh proses server, mem-bypass recover() dan menjatuhkan setiap permintaan HTTP atau RPC yang sedang berlangsung bersamanya.

Ketika tim mencoba menghindari cgo, alternatif umum memaksa kompromi yang berbeda. Terjemahan mekanis murni-Go dari basis kode C mempertahankan CGO_ENABLED=0, tetapi sering kali tertinggal dari rilis C upstream, membawa lapisan memori virtual yang diemulasi, dan mengalokasikan banyak memori di bawah beban. Memindahkan pustaka C ke dalam proses pembantu melalui loopback TCP atau soket domain Unix memulihkan isolasi kesalahan, tetapi setiap panggilan membayar untuk dua peralihan konteks kernel, salinan buffer soket, dan serialisasi kawat.

Kami membangun HQQ setelah mengajukan pertanyaan langsung: bagaimana jika pustaka C berada di dalam proses OS terpisah dengan ruang alamat virtualnya sendiri, sementara Go dan C bertukar bingkai secara langsung melalui baris tembolok CPU bersama tanpa memasuki jalur cepat kernel?

Model IntegrasiCGO_ENABLED=0Isolasi KesalahanSyscall Jalur CepatAlokasi Tumpukan Per-PanggilanKesetiaan C11 Upstream
Penautan Dalam-Proses cgoTidakTidak ada (tumpukan bersama)00 hingga 1+C upstream persis
RPC Subproses Soket / PipaYaBatas proses2 hingga 4Beberapa (pembingkaian + I/O)C upstream persis
Terjemahan Mekanis C-ke-GoYaGo dalam-proses0Tinggi pada panggilan kompleksRuntime C emulasi
IPC Memori-Bersama HQQYaBatas proses0 (putaran adaptif)0 (StandardLink)C upstream persis

Tinjauan Arsitektur: Empat Lapisan Melintasi Batas Proses

HQQ memisahkan isolasi proses menjadi empat lapisan yang dapat disusun sehingga mekanika ring memori bersama, pembuatan kode RPC bertipe, verifikasi integritas biner, dan pengawasan proses anak tetap independen.

 1+-------------------------------------------------------------------+
 2| Proses Aplikasi Go (CGO_ENABLED=0)                                |
 3|                                                                   |
 4|  +-----------------------+     +-------------------------------+  |
 5|  | Klien Go yang Dihasilkan|   | Pengawas Proses               |  |
 6|  | (gosuda.org/hqq/      |     | (gosuda.org/hqq/launch +      |  |
 7|  |  bindgen/rt)          |     |  gosuda.org/hqq/secureexec)   |  |
 8|  +-----------+-----------+     +---------------+---------------+  |
 9|              |                                 |                  |
10|  +-----------v-----------+                     | Pipa Anonim      |
11|  | Lapisan Transpor      |                     | Garis Hidup      |
12|  | (StandardLink /       |                     | (--parent-stdin) |
13|  |  Advanced MPMC)       |                     |                  |
14+--------------+---------------------------------+------------------+
15               |                                 |
16===============|========= Memori Bersama ========|===================
17               |     (mmap / CreateFileMapping)  |
18+--------------v---------------------------------v------------------+
19| Proses Pekerja C11 (Ruang Alamat Terisolasi)                      |
20|                                                                   |
21|  +-----------------------+     +-------------------------------+  |
22|  | Server C11 yang       |---->| Tabel Pegangan                |  |
23|  | Dihasilkan & Shim Batch|    | Pemeriksaan-Generasi          |  |
24|  +-----------+-----------+     | (hqq_rpc.c)                   |  |
25|              |                 +---------------+---------------+  |
26|              |                                 |                  |
27|              +----------------+----------------+                  |
28|                               |                                   |
29|                     +---------v---------+                         |
30|                     | Pustaka C Asli    |                         |
31|                     | (SQLite, Zstd...) |                         |
32|                     +-------------------+                         |
33+-------------------------------------------------------------------+

1. Transpor Memori-Bersama (gosuda.org/hqq dan c/)

Paket dasar gosuda.org/hqq dan mitra C11-nya di c/ mengimplementasikan dua mode transpor memori-bersama di atas POSIX mmap (Linux dan macOS) dan Win32 CreateFileMappingW (Windows). Setiap segmen bersama dimulai dengan superblok HQQ_STL3 yang diverifikasi yang menyematkan versi protokol, geometri buffer, dan PID titik akhir sebelum indeks ring dibaca.

Semua kata kontrol panas dan tajuk slot disejajarkan dengan batas baris tembolok 64-byte sehingga inti produsen dan konsumen tidak pernah bersaing pada baris tembolok yang sama melalui berbagi palsu.

1Tata Letak Slot StandardLink (Disejajarkan Baris Tembolok 64-Byte)
2Imbuhan Byte:  0x00                0x08                0x10                          0x40
3              +-------------------+-------------------+-----------------------------+
4Baris Tajuk:  | seq (u64 atomik)  | len (u32) | bendera| dicadangkan (diganjal nol)  |
5              +-------------------+-------------------+-----------------------------+
6Area Muatan:  | byte muatan [0 .. BufferSize - 1] (dikodekan / didekodekan secara langsung) |
7              +---------------------------------------------------------------------+
MedanImbuhanUkuranSemantika
seq0x008 BPenghitung urutan monotonik. Nilai ganjil menandai penulisan yang sedang berlangsung; nilai genap mempublikasikan bingkai yang dikomit melalui rilis penyimpanan.
len0x084 BPanjang byte muatan yang ditulis ke dalam slot (0 <= len <= BufferSize).
flags0x0C4 BBit kontrol bingkai (lanjutan, status galat, atau pemberitahuan penghentian).
payload0x40BufferSizeWilayah muatan kontinu yang dibaca dan ditulis di tempat tanpa salinan perantara.

Pemanggil memilih di antara dua lapisan transpor tergantung pada struktur konkurensi:

  • StandardLink (Sesi SPSC 1:1): Memasangkan sesi klien dan server di atas slot permintaan dan tanggapan berdedikasi yang dilindungi oleh kunci urutan (SeqLock). Pada jalur cepat, penulis melakukan serialisasi langsung ke dalam slot yang dipetakan dan berkomit dengan rilis penyimpanan atomik; pembaca berputar sebentar di ruang pengguna sebelum beralih kembali ke primitif bangun OS (futex di Linux, __ulock_wait di macOS, WaitOnAddress di Windows). Perjalanan pulang pergi bingkai tunggal selesai dalam ~200 ns dengan 0 alokasi/op.
  • Advanced (Ring Pengiriman MPMC): Memultipleks M pemanggil Go serentak di seberang N utas pekerja C menggunakan ring penyerahan multi-produsen multi-konsumen bebas kunci, kumpulan slot bersama, dan tabel penyelesaian terindeks. Pemanggil memesan slot, mempublikasikan indeks slot ke ring, dan menunggu entri penyelesaian mereka sementara utas pekerja mengeluarkan dan menyelesaikan permintaan di luar urutan.

Kedua lapisan melacak keaktifan rekan secara terus-menerus. Setiap titik akhir memperbarui penanda waktu monotonik atomik melalui Heartbeat() dan memeriksa PeerAlive(timeout) saat menunggu. Jika proses pekerja mogok di tengah panggilan, rekan mendeteksi detak jantung basi atau PID mati secara segera, membatalkan penantian yang diblokir dengan ErrPeerGone, dan mendukung Takeover sesi atomik tanpa meninggalkan file memori-bersama yang macet.

2. RPC Alokasi-Nol dan Pegangan Pemeriksaan-Generasi (gosuda.org/hqq/bindgen)

Menulis pembingkaian memori-bersama dengan tangan untuk pustaka C dengan puluhan fungsi membosankan dan rentan terhadap galat. hqq-bindgen adalah kompiler skema yang membaca file Definisi Antarmuka (.idl) (atau mem-bootstrapping-nya secara langsung dari header C .h melalui hqq-bindgen init) dan mengeluarkan paket klien Go zero-cgo di samping kerangka server C11.

Kesalahan paling berbahaya dalam pengikatan C apa pun adalah mengekspos penunjuk C mentah (sqlite3*, sqlite3_stmt*, ZSTD_CCtx*) melintasi batas bahasa. Jika pemanggil meneruskan penunjuk setelah membebaskannya, meneruskan penunjuk milik sesi lain, atau merusak alamat bilangan bulat, pustaka C mendereferensikan memori yang tidak valid.

hqq-bindgen melarang penunjuk mentah di kawat. Sebagai gantinya, IDL memodelkan sumber daya C berstatus sebagai deklarasi handle bertipe:

1service sqlite3 {
2    handle Db;
3    handle Stmt @owned_by(Db);
4
5    fn open_v2(path: string, flags: i32) -> result<Db, i32>;
6    fn prepare_one(db: Db, sql: string) -> result<Stmt, i32>;
7    fn finalize(stmt: @consume Stmt) -> result<void, i32>;
8    fn close(db: @consume Db) -> result<void, i32>;
9}

Di kawat, setiap pegangan adalah bilangan bulat 64-bit yang mengemas indeks tabel 32-bit dan penghitung generasi non-nol 32-bit: (uint64_t)generation << 32 | index. Runtime C (c/src/hqq_rpc.c) memelihara tabel pegangan per sesi dengan tiga invarian:

  1. Validasi Generasi: Setiap pencarian memverifikasi bahwa slot tersebut terisi, generasi yang disimpan cocok dengan 32 bit atas dari pegangan, dan tag tipe pegangannya cocok dengan tipe parameter (Db vs. Stmt). Menggunakan kembali pegangan yang ditutup gagal seketika dengan HQQRPC_E_BAD_HANDLE sebelum memanggil ke dalam pustaka C.
  2. Kepemilikan Hierarkis (@owned_by): Karena Stmt dianotasikan dengan @owned_by(Db), menutup pegangan Db secara otomatis menelusuri semua pegangan anak Stmt aktif yang dimiliki oleh Db tersebut dan menjalankan destruktor mereka dalam urutan alokasi terbalik (sqlite3_finalize sebelum sqlite3_close_v2).
  3. Pembersihan Perobohan Sesi: Jika klien Go memutuskan sambungan atau mogok tanpa menutup pegangannya, destruktor sesi server C menyapu seluruh tabel pegangan sesi dalam urutan terbalik, menjamin nol alokasi C yang bocor di seberang pemanduan ulang klien.

Sebelum sesi memproses panggilan RPC pertamanya, klien dan server bertukar intisari antarmuka SHA-256 32-byte yang dihitung atas IDL yang dinormalkan (HQQ-BINDGEN-IDL-V1\n awalan). Jika klien Go dan pekerja C dikompilasi dari skema IDL yang tidak kompatibel, jabat tangan menolak koneksi pada saat bootstrap.

3. Eksekusi Biner Tanpa-Manipulasi (gosuda.org/hqq/secureexec)

Memindahkan kode C ke dalam biner pekerja memunculkan pertanyaan rantai pasokan: bagaimana proses Go mengetahui bahwa biner pekerja di disk tidak dimodifikasi atau diganti di antara verifikasi dan eksekusi?

Memanggil crypto/sha256 pada jalur file yang diikuti oleh os/exec.Command(path).Start() meninggalkan jendela Waktu-Pemeriksaan ke Waktu-Penggunaan (TOCTOU) di mana proses lain dapat menimpa atau menukar file sebelum kernel mengeksekusinya. gosuda.org/hqq/secureexec menutup jendela ini:

  • Di Linux, secureexec membuka biner, mengalirkan isinya ke dalam deskriptor file memori anonim yang dibuat melalui memfd_create(MFD_CLOEXEC | MFD_ALLOW_SEALING) sambil menghitung hash SHA-256-nya, memverifikasi hash terhadap intisari 32-byte yang disematkan atau manifes rilis yang ditandatangani Ed25519 (cmd/hqq-manifest), menerapkan fcntl(F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) sehingga tidak ada proses di sistem yang dapat mengubah halaman, dan memijah anak dari deskriptor yang disegel.
  • Di macOS dan Windows, secureexec mewujudkan biner yang diverifikasi ke dalam direktori tembolok yang dikunci izin yang diberi kunci berdasarkan intisari SHA-256 dan memverifikasi ukuran file serta integritas intisari sebelum diluncurkan.

4. Pengawasan Proses Garis-Hidup Induk (gosuda.org/hqq/launch)

Ketika aplikasi Go memijah proses pekerja C latar belakang, pekerja tersebut tidak boleh hidup lebih lama dari induknya sebagai proses yatim piatu, bahkan jika induk Go dihentikan dengan SIGKILL (kill -9) dan tidak dapat menjalankan penangan pembersihan defer.

gosuda.org/hqq/launch memberlakukan pengikatan masa pakai melalui pipa OS anonim yang diteruskan ke masukan standar pekerja dengan bendera --parent-stdin. Induk Go memegang ujung tulis dari pipa dan tidak pernah menulis ke dalamnya; pekerja C memantau ujung baca pada utas garis hidup berdedikasi. Jika proses Go keluar secara normal, panik, atau dibunuh oleh kernel OS (SIGKILL atau pembunuh OOM), kernel langsung menutup deskriptor tulis. Pekerja C menerima EOF pada pipa dalam mikrodetik, mengganggu panggilan aktif apa pun, menjalankan destruktor pegangan sesinya, melepaskan tautan file memori-bersama, dan keluar secara bersih.

Panduan Cepat: Memanggil Layanan yang Dihasilkan

Dengan hqq-bindgen dan launch, memanggil pekerja C terisolasi dari Go terlihat seperti klien Go bertipe biasa sambil menjaga CGO_ENABLED=0:

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"log"
 7	"time"
 8
 9	"gosuda.org/hqq/bindgen/rt"
10	"gosuda.org/hqq/launch"
11	"gosuda.org/hqq/secureexec"
12)
13
14func main() {
15	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
16	defer cancel()
17
18	hash, err := secureexec.ParseHash("7649b1b45dacd934cde8149f35f4fcc3937f6486fbac23ce976466719b3752bb")
19	if err != nil {
20		log.Fatal(err)
21	}
22
23	listener, err := launch.NewListenerName("demo")
24	if err != nil {
25		log.Fatal(err)
26	}
27
28	srv, err := launch.Start(ctx, launch.Config{
29		Binary:   "/path/to/verified-worker",
30		SHA256:   hash,
31		Listener: listener,
32		Args:     []string{listener},
33	})
34	if err != nil {
35		log.Fatal(err)
36	}
37	defer srv.Close()
38
39	fmt.Printf("worker running on shared-memory listener %s (opts=%+v)\n", listener, rt.SessionOptions{})
40}

Struktur Repositori

Setiap subsistem di gosuda.org/hqq diatur ke dalam paket-paket terfokus dengan batas-batas eksplisit:

 1gosuda.org/hqq
 2├── standard.go / ring.go      # StandardLink (SPSC) dan primitif ring memori-bersama tingkat rendah
 3├── advanced.go / slotpool.go  # Pengirim Advanced (MPMC), kumpulan slot, dan tabel penyelesaian
 4├── shm_*.go / wait_*.go       # mmap/CreateFileMapping khusus OS dan futex/ulock/WaitOnAddress
 5├── c/                         # Pustaka transpor C11 (hqq.c) dan runtime server RPC (hqq_rpc.c)
 6├── bindgen/                   # Parser IDL hqq-bindgen, verifikator (V1-V41), dan pembuat kode Go/C11
 7│   └── rt/                    # Runtime klien Go alokasi-nol untuk layanan yang dihasilkan
 8├── secureexec/                # Verifikasi manifes SHA-256 / Ed25519 dan eksekusi disegel-memfd
 9├── launch/                    # Pengawas proses pekerja dengan manajemen garis hidup --parent-stdin
10├── cmd/hqq-manifest/          # Alat CLI untuk menghasilkan dan menandatangani manifes biner rilis
11└── spec/                      # Spesifikasi kawat yang dapat dieksekusi dan vektor uji emas (BINDGEN.md)

Untuk melihat bagaimana HQQ mendukung penggantian produksi drop-in untuk dua pustaka cgo yang paling banyak digunakan dalam ekosistem Go, lihat paket pendamping yang dibangun di atas tumpukan ini:

  • gosuda.org/sqlite3: Driver database/sql drop-in yang kompatibel dengan github.com/mattn/go-sqlite3, menjalankan SQLite C asli 3.54.1 di atas memori bersama dengan prapengambilan batch terpipa.
  • gosuda.org/zstd: Pengganti drop-in untuk github.com/DataDog/zstd, menjalankan libzstd 1.5.7 C asli dalam pekerja memori-bersama terisolasi dengan konteks alokasi-nol dan pembautan massal.
  • Repositori GitHub: https://github.com/gosuda/HQQ