Full hardware memory barrier pada jalur yang sangat sering dieksekusi dapat membebankan biaya pada setiap operasi, meskipun koordinasi lintas thread hanya terjadi sesekali. Linux membarrier() menyediakan penempatan biaya yang berbeda: jalur koordinasi yang jarang dapat meminta event pengurutan pada sekumpulan thread tertentu, sementara jalur yang sering dapat cukup memakai pengurutan pada level compiler dalam desain yang sesuai.
Mekanisme ini bukan pengganti umum untuk atomic, lock, atau memory model suatu bahasa pemrograman. membarrier() adalah primitive sinkronisasi khusus Linux untuk desain yang correctness-nya sudah memiliki pasangan yang presisi antara jalur yang sering dan jalur koordinasi yang jarang.
Barrier menargetkan thread, bukan rentang memori
membarrier() tidak melakukan flush pada buffer tertentu dan tidak menempelkan pengurutan pada satu alamat. Command-nya menargetkan thread. Pada bentuk private expedited, targetnya adalah thread yang sedang berjalan dalam proses yang sama dengan caller.
Setelah MEMBARRIER_CMD_PRIVATE_EXPEDITED berhasil, caller mendapat jaminan bahwa thread sibling yang sedang berjalan telah melewati keadaan ketika akses memori userspace mereka diurutkan terhadap system call tersebut. Thread yang tidak sedang berjalan secara efektif sudah berada pada keadaan yang memenuhi kondisi itu.
Perbedaan ini penting. Primitive tersebut membentuk relasi pengurutan di sekitar eksekusi thread target; primitive ini tidak mengubah data race biasa menjadi sinkronisasi yang valid.
Gambaran ringkasnya:
thread A thread koordinasi
-------- -----------------
akses X
compiler barrier
akses Y perbarui state koordinasi
membarrier(PRIVATE_EXPEDITED)
lanjut setelah seluruh target
melewati keadaan pengurutan
yang diwajibkanAkses mana yang memperoleh relasi berguna tetap bergantung pada matching barrier, operasi atomic, dan constraint compiler dalam protokol tersebut.
Registrasi memisahkan setup dari eksekusi
Command private expedited memerlukan registrasi terlebih dahulu. Proses memeriksa dukungan command dengan MEMBARRIER_CMD_QUERY, lalu mendaftarkan mode expedited yang akan dipakai.
#define _GNU_SOURCE
#include <linux/membarrier.h>
#include <sys/syscall.h>
#include <unistd.h>
static int mb(int cmd)
{
return syscall(SYS_membarrier, cmd, 0, 0);
}
int setup_barrier(void)
{
int supported = mb(MEMBARRIER_CMD_QUERY);
if (supported < 0)
return -1;
if (!(supported & MEMBARRIER_CMD_PRIVATE_EXPEDITED) ||
!(supported & MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED))
return -1;
return mb(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED);
}Registrasi mencatat intent proses untuk memakai command tersebut. Pemanggilan private expedited tanpa registrasi yang diwajibkan gagal dengan EPERM.
Hasil query berupa bit mask, sehingga software sebaiknya menguji bit command spesifik yang dibutuhkan dan tidak menyimpulkan dukungan hanya dari versi kernel.
Expedited mengubah lokasi pembayaran latency
Varian expedited dirancang untuk selesai tanpa blocking, tetapi dapat menimbulkan overhead tambahan ketika memaksa thread target melewati keadaan yang diwajibkan. Memanggilnya pada setiap operasi biasanya justru menghilangkan tujuan desain tersebut.
Bentuk yang berguna bersifat asimetris:
operasi sangat sering: pengurutan lokal/compiler yang murah
koordinasi jarang: system call + kerja pengurutan lintas threadAsimetri seperti ini muncul pada mekanisme seperti implementasi userspace Read-Copy-Update dan garbage collector. Operasi read-side atau fast-side dapat terjadi sangat sering, sedangkan event reklamasi atau koordinasi terjadi jauh lebih jarang.
Trade-off-nya bukan “barrier versus tanpa barrier.” Biaya sinkronisasi dipindahkan. Sisi yang jarang menjadi lebih berat agar jalur yang sering, bila protokolnya memang dirancang demikian, dapat menghindari hardware barrier yang sebaliknya harus terus dieksekusi.
Pengurutan compiler tetap menjadi bagian protokol
CPU memory barrier dan compiler barrier menangani persoalan pengurutan yang berbeda. Compiler dapat mengubah urutan, menggabungkan, atau menghapus operasi ketika aturan bahasa dan compiler mengizinkannya. Mekanisme pengurutan hardware tidak dapat memperbaiki operasi source yang sudah diubah compiler menjadi urutan yang tidak sesuai.
Karena itu, protokol yang memakai membarrier() tetap membutuhkan constraint compiler yang tepat pada jalur yang berpartisipasi. Pada C level rendah, salah satu bentuknya dapat berupa compiler barrier:
asm volatile ("" : : : "memory");Statement tersebut tidak menghasilkan CPU fence. Fungsinya membatasi perpindahan operasi memori oleh compiler melintasi titik itu. Jaminan eksekusi lintas thread berasal dari operasi membarrier() yang berpasangan dan bagian protokol lainnya.
Language-level atomic dapat menyediakan semantik compiler yang diperlukan pada implementasi lain. Mencampur raw compiler barrier, akses C biasa, dan language atomic tanpa model yang terdefinisi dapat menghasilkan undefined behavior meskipun pengurutan CPU yang dibayangkan tampak masuk akal.
Command private dan global memiliki scope berbeda
MEMBARRIER_CMD_PRIVATE_EXPEDITED menargetkan thread sibling yang sedang berjalan dalam proses caller. MEMBARRIER_CMD_GLOBAL_EXPEDITED menargetkan thread yang sedang berjalan dari proses yang sebelumnya mendaftar sebagai penerima global expedited barrier.
Karena itu, bentuk global bukan sekadar private barrier yang lebih kuat. Model partisipasinya berbeda dan targetnya lebih luas. Sebuah proses dapat mengeluarkan global expedited command tanpa mendaftarkan dirinya sebagai penerima; registrasi menyatakan intent untuk menerima barrier semacam itu dari caller.
Untuk protokol internal runtime yang seluruh pesertanya adalah thread dari satu proses, private scope memberi batas yang lebih sempit. Koordinasi lintas proses memerlukan desain eksplisit berdasarkan semantik registrasi global, bukan asumsi bahwa setiap proses otomatis menjadi target.
Sync-core menambahkan sinkronisasi instruction stream
MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE memperluas jaminan private expedited. Ketika system call kembali, thread sibling yang sedang berjalan juga telah mengeksekusi core-serializing instruction.
Properti tersebut relevan bagi runtime khusus yang memodifikasi executable code dan memerlukan eksekusi instruksi untuk mengamati transisi yang terkoordinasi. Properti ini tidak diperlukan untuk sinkronisasi shared data biasa, dan dukungan arsitektur harus diperiksa sebelum dipakai.
Command sync-core mempunyai command registrasi sendiri:
MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE
MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_COREMenganggap pasangan ini sama dengan private expedited biasa akan menghilangkan batas semantik yang penting.
Integrasi rseq melayani kasus koordinasi lain
Linux juga mendefinisikan MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ. Tujuannya terkait restartable sequences. Command ini dapat memastikan critical section rseq yang sedang dieksekusi thread sibling dimulai ulang, dengan target CPU tertentu bila MEMBARRIER_CMD_FLAG_CPU digunakan.
Command ini bukan generic barrier yang lebih kuat. Ia ada untuk ABI userspace yang berbeda, tempat critical section per-CPU bergantung pada perilaku restart dengan bantuan kernel. Command ini juga memerlukan registrasi pasangannya.
Keluarga command yang terpisah memperlihatkan bahwa membarrier() merupakan interface untuk beberapa kontrak koordinasi eksplisit, bukan satu operasi fence universal.
Batas system call bukan seluruh argumen correctness
Pemanggilan yang berhasil membuktikan jaminan yang didokumentasikan untuk command dan target set tersebut. Hasil itu tidak membuktikan bahwa algoritma di sekelilingnya menggunakan jaminan tersebut dengan benar.
Protokol yang solid tetap harus menentukan:
- akses mana yang terjadi sebelum dan sesudah titik pengurutan compiler;
- thread mana yang memanggil system call;
- thread mana yang termasuk target set;
- operasi atomic atau sinkronisasi mana yang memublikasikan state;
- aturan lifetime apa yang mencegah data yang sudah direklamasi diakses kembali;
- tindakan apa yang diambil ketika command yang diperlukan tidak tersedia.
Detail tersebut sangat penting pada algoritma reklamasi. Pengurutan dapat menetapkan bahwa thread telah melewati titik yang diwajibkan, tetapi lifetime object adalah properti terpisah yang harus diturunkan algoritma dari event itu.
Portabilitas memerlukan fallback eksplisit
membarrier() khusus Linux, dan masing-masing command bergantung pada dukungan kernel serta arsitektur. Runtime yang memakainya sebagai optimasi sebaiknya melakukan support query saat inisialisasi dan memilih fallback yang terdefinisi bila bit yang diperlukan tidak tersedia.
Fallback dapat mengembalikan hardware barrier ke jalur yang sering, memilih strategi sinkronisasi lain, atau menonaktifkan mode optimasi. Pilihan tepat bergantung pada algoritma, tetapi melanjutkan eksekusi tanpa jaminan pengurutan yang diperlukan bukan fallback.
Karena karakter tersebut, membarrier() paling sesuai untuk sistem level rendah yang frekuensi sinkronisasinya diketahui, Linux merupakan target eksekusi yang didukung, dan pemisahan fast path serta jalur koordinasi dinyatakan secara eksplisit. Dalam kondisi itu, system call ini menyediakan cara presisi untuk memusatkan kerja koordinasi yang mahal pada sisi yang lebih jarang dieksekusi.