POSIX Signals - SIGBUS

SIGBUS (Bus Error) is a standard signal in POSIX/UNIX-like operating systems sent by the kernel to a process when that process attempts to access memory that the CPU or physical memory management unit (MMU) cannot physically address or align.

While SIGSEGV (Segmentation Fault) means "you accessed memory that doesn't belong to you or violates permissions," SIGBUS means "your memory address itself is physically invalid or the underlying storage backing it failed."

The Primary Causes of SIGBUS

Unaligned Memory Access (Hardware Architecture Alignment)

On CPU architectures that strictly enforce alignment (such as SPARC, ARM, or MIPS), primitive data types must be stored at memory addresses that are multiples of their size:

  • A 4-byte int32_t must begin at an address divisible by 4 (e.g., 0x1004).
  • An 8-byte int64_t must begin at an address divisible by 8 (e.g., 0x1008).

If a program casts an unaligned byte pointer and attempts a multibyte read/write, the hardware raises an alignment exception, which the OS kernel translates into SIGBUS.

// Causes SIGBUS on strict alignment architectures (SPARC, older ARM)
char bytes[8] = {0};
int32_t *ptr = (int32_t *)&bytes[1]; // Unaligned address (not divisible by 4)
int32_t val = *ptr;                   // Triggers SIGBUS

Note on x86/x86_64: Intel x86 processors transparently handle unaligned scalar accesses in hardware (at a minor performance penalty). However, unaligned accesses on x86_64 can still trigger SIGBUS when using certain SIMD/AVX instructions (e.g., movaps requiring 16-byte alignment).

Truncated Memory-Mapped Files (mmap)

This is the most common way to trigger SIGBUS on modern Linux x86_64 systems.

When you map a file into virtual memory via mmap(), the kernel creates virtual memory addresses backing that region. However, if another process (or a system call like ftruncate) shrinks the underlying file on disk while your process is still mapped to it, reading/writing beyond the new file size triggers a bus error:

int fd = open("file.txt", O_RDWR);
ftruncate(fd, 4096);

// Map 4096 bytes into virtual memory
char *map = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

// Another process (or system call) truncates the file to 0 bytes
ftruncate(fd, 0);

// SIGBUS! The virtual page exists, but no physical file blocks back it anymore.
char c = map[100];

POSIX Shared Memory Truncation (shm_open)

Similarly, if a process creates POSIX shared memory via shm_open() and maps it with mmap(), but forgets to call ftruncate(fd, size) first, the backing file descriptor has a length of 0. Attempting to access the mapped region will instantly raise a SIGBUS.

Non-Existent Physical Memory / Hardware Errors

SIGBUS can be generated directly by hardware memory controllers when:

  • Uncorrectable RAM Errors (ECC Faults): The CPU attempts to read a physical RAM page, but the memory controller detects uncorrectable multi-bit ECC corruption.
  • Bad Device I/O Mapping: A process accesses physical memory addresses mapped to hardware devices (/dev/mem or PCI BAR regions) that do not respond on the bus.

SIGBUS vs. SIGSEGV

Metric SIGSEGV (Segmentation Fault) SIGBUS (Bus Error)
Core Problem Virtual Memory / Permission Violation. Physical Memory / Alignment / Storage Violation.
MMU Cause Page table entry is missing or invalid (e.g., dereferencing NULL, writing to PROT_READ memory, accessing unallocated memory stack/heap). Address itself cannot be physically satisfied by the bus or underlying backing store.
Common Trigger int *p = NULL; *p = 10; Reading beyond truncated file bounds in mmap(), or unaligned memory access.

Inspecting SIGBUS in Signal Handlers

When catching SIGBUS with a signal handler set via sigaction(), you can register the handler with the SA_SIGINFO flag to receive a siginfo_t struct containing the exact faulting memory address and error code:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void bus_handler(int sig, siginfo_t *info, void *context) {
    printf("Caught SIGBUS!\n");
    printf("Faulting Address: %p\n", info->si_addr);

    if (info->si_code == BUS_ADRERR) {
        printf("Reason: Non-existent physical address / truncated mmap\n");
    } else if (info->si_code == BUS_ADRALN) {
        printf("Reason: Invalid address alignment\n");
    }

    _exit(1);
}

Common si_code Values for SIGBUS:

  • BUS_ADRALN: Invalid address alignment.
  • BUS_ADRERR: Non-existent physical address (e.g., file truncation in mmap).
  • BUS_OBJERR: Hardware/filesystem error while accessing memory page.
  • BUS_MCEERR_AO / BUS_MCEERR_AR: Machine Check Exception (ECC hardware RAM error).

Why call it BUS?

The name "Bus Error" is a historical artifact from the early hardware architectures of 1970s minicomputers (like the DEC PDP-11, where original UNIX was written) and early workstations (like Sun-1 and Sun-2).

On those early hardware architectures, memory and peripherals weren't accessed through modern integrated memory controllers. Instead, the CPU talked to RAM, ROM, and device controllers across a shared physical circuit board connection called the System Bus (such as the PDP-11 UNIBUS or VMEbus).

How the Physical Hardware Generated a "Bus Error"

When the CPU wanted to read or write memory, it placed a physical memory address onto the address bus and signaled a read/write line.

A physical Bus Error occurred in two primary hardware hardware failure modes:

Scenario A: Unmapped Physical Address (No Response / Timeout)

If the CPU placed address 0x7FFF0000 on the bus, but there was no physical RAM chip or hardware card installed at that address location:

  1. The CPU asserted the address lines and waited for a device to acknowledge the request (a signal called ACK or READY).
  2. After a fixed hardware clock cycle timeout (e.g., 100 nanoseconds), no device answered.
  3. The bus controller logic physically asserted a BUS ERROR interrupt line directly on the CPU pinout.

Scenario B: Misaligned Physical Bus Accesses

On processors like the Motorola 68000 (used in early Apple Macintosh and Sun Workstations):

  • The physical hardware memory bus was 16 bits wide (2 bytes) or 32 bits wide.
  • If software instructed the CPU to fetch a 16-bit word starting at an odd physical address (e.g., 0x1001), the CPU’s microcode could not physically break the single bus transaction across two aligned physical memory words without raising an exception.
  • The hardware trap handler caught this, generated a bus fault exception, and handed it to UNIX, which signaled SIGBUS.

Why the Name Persists Today

As operating systems evolved and virtual memory (MMU) became standard:

  • SIGSEGV was invented to handle software-level Virtual Memory page table violations (e.g., page not present, read-only protection fault).
  • SIGBUS was kept to handle hardware-level / physical addressing and device transport failures.

Even though modern x86_64 and ARM64 CPUs use complex interconnects (like Intel Ultra Path Interconnect, PCIe, or Apple Fabric) instead of a simple 1970s UNIBUS, POSIX retained the name SIGBUS for backward compatibility.

Whenever the Operating System encounters a memory failure where the virtual memory page mapping itself exists, but the underlying physical transport or backing hardware cannot fulfill the transaction, it maps that error to SIGBUS.