A full hardware memory barrier in a frequently executed path can impose a cost on every operation, even when cross-thread coordination happens only occasionally. Linux membarrier() supports a different placement of that cost: a rare coordination path can request an ordering event across a defined set of threads while a frequent path may need only compiler-level ordering.

This is not a general replacement for atomics, locks, or the memory model of a programming language. It is a Linux-specific synchronization primitive for designs whose correctness already has a precise pairing between a frequent path and an infrequent coordination path.

The barrier targets threads, not a memory range

membarrier() does not flush a selected buffer or attach ordering to one address. Its commands target threads. For the private expedited form, the target set is the running threads in the same process as the caller.

After a successful MEMBARRIER_CMD_PRIVATE_EXPEDITED, the caller is guaranteed that its running thread siblings have passed through a state in which their user-space memory accesses are ordered with respect to the system call. Threads that are not running are already in a state that satisfies the required condition.

That distinction matters. The primitive creates an ordering relationship around execution by targeted threads; it does not turn ordinary data races into valid synchronization.

A compact view is:

thread A                 coordination thread
--------                 -------------------
access X
compiler barrier
access Y                 update coordination state
                         membarrier(PRIVATE_EXPEDITED)
                         continue after all targets cross
                         the required ordering state

The exact accesses that become useful in a protocol depend on the protocol’s matching barriers, atomic operations, and compiler constraints.

Registration separates setup from execution

Private expedited commands require prior registration. A process first checks command support with MEMBARRIER_CMD_QUERY, then registers the expedited mode it intends to use.

#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);
}

Registration records the process’s intent to use that command. Invoking a private expedited command without the required registration fails with EPERM.

The query result is a bit mask, so software should test the specific command bits it requires rather than infer support from a kernel version.

Expedited changes where latency is paid

The expedited variants are designed to complete without blocking, but they can create extra system-wide or process-local overhead while forcing the target threads through the required state. Calling one for every operation would usually defeat the design goal.

The useful shape is asymmetric:

very frequent operation: cheap local/compiler ordering
rare coordination:       system call + cross-thread ordering work

This asymmetry appears in mechanisms such as userspace Read-Copy-Update implementations and garbage collectors. A read-side or fast-side operation can be extremely common, while a reclamation or coordination event occurs much less often.

The trade is therefore not “barrier versus no barrier.” It is a redistribution of synchronization work. The rare side becomes heavier so that a carefully designed frequent side can avoid a hardware barrier that would otherwise execute repeatedly.

Compiler ordering remains part of the protocol

A CPU memory barrier and a compiler barrier solve different ordering problems. The compiler can reorder, combine, or eliminate operations when the language and compiler rules permit it. A hardware ordering mechanism cannot retroactively repair source operations that the compiler already transformed into an unsuitable sequence.

Protocols using membarrier() therefore still need appropriate compiler constraints on the participating paths. In low-level C, that may include a compiler barrier such as:

asm volatile ("" : : : "memory");

That statement emits no CPU fence by itself. It constrains compiler movement of memory operations across the point. The cross-thread execution guarantee comes from the matching membarrier() operation and the rest of the protocol.

Language-level atomics may provide the required compiler semantics in other implementations. Mixing raw compiler barriers, plain C accesses, and language atomics without a defined model can introduce undefined behavior even when the intended CPU ordering looks plausible.

Private and global commands have different scopes

MEMBARRIER_CMD_PRIVATE_EXPEDITED targets running thread siblings in the caller’s process. MEMBARRIER_CMD_GLOBAL_EXPEDITED targets running threads belonging to processes that registered to receive global expedited barriers.

The global form is therefore not simply a stronger private barrier. It has a different participation model and a broader target set. A process can issue the global expedited command without itself registering as a recipient; registration expresses intent to receive such barriers from callers.

For an internal runtime protocol whose participants are threads of one process, private scope gives the protocol a tighter boundary. Cross-process coordination needs an explicit design around the global registration semantics rather than an assumption that every process is automatically targeted.

Sync-core adds instruction-stream synchronization

MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE extends the private expedited ordering guarantee. On return, running thread siblings have also executed a core-serializing instruction.

That property is relevant to specialized runtimes that modify executable code and need instruction execution to observe a coordinated transition. It is not required for ordinary shared-data synchronization, and architecture support must be queried before use.

The sync-core command has its own registration command:

MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE
MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE

Treating these as interchangeable with the ordinary private expedited pair would discard an important semantic boundary.

rseq integration serves another coordination case

Linux also defines MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ. Its purpose is tied to restartable sequences. It can ensure that currently executing rseq critical sections in sibling threads are restarted, with an optional CPU-specific target when MEMBARRIER_CMD_FLAG_CPU is used.

This command is not a generic stronger barrier. It exists for a distinct userspace ABI in which per-CPU critical sections rely on kernel-assisted restart behavior. It also requires its corresponding registration command.

The separate command families show that membarrier() is an interface for several explicit coordination contracts, not one universal fence operation.

The syscall boundary is not the entire correctness argument

A successful call proves the guarantee documented for that command and target set. It does not establish that the surrounding algorithm uses the guarantee correctly.

A sound protocol still has to specify:

  • which accesses occur before and after compiler ordering points;
  • which thread issues the system call;
  • which threads belong to the target set;
  • which atomic or synchronization operations publish state;
  • what lifetime rule prevents reclaimed data from being accessed later;
  • what happens when a required command is unavailable.

Those details are especially important for reclamation algorithms. Ordering can establish that threads crossed a required point, but object lifetime is a separate property that the algorithm must derive from that event.

Portability requires an explicit fallback

membarrier() is Linux-specific, and individual commands depend on kernel and architecture support. A runtime that uses it as an optimization should keep the support query close to initialization and choose a defined fallback when the required bits are absent.

A fallback may place a hardware barrier back on the frequent path, select another synchronization strategy, or disable the optimized mode. The appropriate choice depends on the algorithm, but silently proceeding without the required ordering guarantee is not a fallback.

This makes membarrier() most valuable in low-level systems where synchronization frequency is known, Linux is a supported execution target, and the fast/slow path split is explicit. In that setting, the syscall provides a precise way to concentrate expensive coordination work on the side that executes less often.