Accessing a memory-mapped region can result in two different signals, depending on the nature of the fault. Both kill the process by default (unless caught). Understanding which signal fires — and when — is important for writing robust programs and for debugging segfaults.
| Signal | Cause | Example Scenario |
| SIGSEGV | Accessing memory in a way that violates the page’s protection bits, or accessing an unmapped address entirely | Writing to a PROT_READ-only mapping; reading from an unmapped region; NULL pointer dereference |
| SIGBUS | Accessing a page in a file-backed mapping for which no corresponding file content exists (mapping extends beyond end of file) | File is 3 pages, mapping is 4 pages, and you access the 4th page |
SIGSEGV is delivered whenever the processor generates a protection fault or the kernel determines an access is illegal. In the context of mmap():
PROT_READ only)Scenario 2: Reading from a no-access mapping (
PROT_NONE)Scenario 3: Accessing an address outside any mapped region (e.g., after
munmap())Scenario 4: NULL pointer dereference (address 0 is typically unmapped)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
void sigsegv_handler(int sig) {
printf("Caught SIGSEGV (signal %d): tried to write to read-only mapping!\n", sig);
/* Real programs should not do complex work in a signal handler */
_exit(1);
}
int main(void) {
int fd;
struct stat sb;
char *addr;
signal(SIGSEGV, sigsegv_handler);
fd = open("/etc/hostname", O_RDONLY);
if (fd == -1) { perror("open"); exit(1); }
fstat(fd, &sb);
/* Map with PROT_READ only */
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
printf("File content: %.*s\n", (int)sb.st_size, addr);
/* This write violates PROT_READ — SIGSEGV is delivered */
addr[0] = 'X'; /* <-- triggers SIGSEGV */
munmap(addr, sb.st_size);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <signal.h>
void handler(int sig) {
printf("SIGSEGV: accessed unmapped region after munmap()\n");
_exit(1);
}
int main(void) {
signal(SIGSEGV, handler);
char *addr = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
addr[0] = 'A'; /* OK: mapping is valid */
printf("Wrote: %c\n", addr[0]);
munmap(addr, 4096); /* Remove the mapping */
/* Accessing addr now is illegal — SIGSEGV */
char c = addr[0]; /* <-- triggers SIGSEGV */
printf("Should not reach here: %c\n", c);
return 0;
}
SIGBUS is specific to file-backed mappings. It fires when you access a page that is within the mapped virtual address range but beyond the actual end of the file. The last page in the file is zero-padded up to the page boundary — but any whole pages after that generate SIGBUS.
| File Page 0 (valid data) |
File Page 1 (valid data) |
File Page 2 (partial: zero-padded to page boundary) |
Mapped Page 3 SIGBUS — no file content! |
| ← file size = 2.5 pages → | ← mapping extended beyond file → | ||
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <signal.h>
#include <unistd.h>
void sigbus_handler(int sig) {
printf("Caught SIGBUS (signal %d): "
"accessed beyond end of file in mapping!\n", sig);
_exit(1);
}
int main(void) {
int fd;
char *addr;
long ps = sysconf(_SC_PAGESIZE);
signal(SIGBUS, sigbus_handler);
/* Create a file that is only 100 bytes — much less than one page */
fd = open("small.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
write(fd, "Hello", 5); /* file size = 5 bytes */
/* Map TWO pages even though file is only 5 bytes */
addr = mmap(NULL, 2 * ps, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
/* Accessing within first page is fine (zero-padded past 5 bytes) */
printf("addr[0] = %c\n", addr[0]);
printf("addr[100] = %d (zero-padded)\n", addr[100]);
/* Accessing the second page (beyond end of file) = SIGBUS */
printf("About to access second page...\n");
char c = addr[ps]; /* <-- triggers SIGBUS */
printf("Should not reach: %c\n", c);
munmap(addr, 2 * ps);
return 0;
}
Real programs (like database engines) sometimes catch SIGBUS to detect when a file has been truncated under an active mapping:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
static sigjmp_buf jump_buf;
void sigbus_handler(int sig) {
siglongjmp(jump_buf, 1);
}
int main(void) {
int fd;
long ps = sysconf(_SC_PAGESIZE);
char *addr;
struct sigaction sa = { .sa_handler = sigbus_handler };
sigemptyset(&sa.sa_mask);
sigaction(SIGBUS, &sa, NULL);
fd = open("test.dat", O_RDWR | O_CREAT | O_TRUNC, 0644);
write(fd, "DATA", 4); /* 4-byte file */
addr = mmap(NULL, ps, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
if (sigsetjmp(jump_buf, 1) == 0) {
/* Attempt to read from second page — triggers SIGBUS */
char c = addr[ps - 1]; /* Near end of page, no file content */
printf("Read: %d\n", c);
} else {
printf("Recovered from SIGBUS — file shorter than mapping\n");
}
munmap(addr, ps);
return 0;
}
After a mapping is created, you can change its protection flags using mprotect():
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
/* Returns 0 on success, -1 on error */
/* Example: JIT compiler workflow */
/* Step 1: Allocate writable anonymous memory */
char *code = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
/* Step 2: Write machine code into it */
/* ... emit_instructions(code) ... */
/* Step 3: Make it executable (remove write, add exec) */
mprotect(code, 4096, PROT_READ | PROT_EXEC);
/* Step 4: Call the generated code */
/* ((void(*)(void))code)(); */
A: SIGSEGV means a protection violation — you accessed memory with the wrong permission (e.g., writing to a read-only page) or accessed an unmapped address entirely. SIGBUS is specific to file-backed mappings and means the page you accessed exists in the virtual address range but has no corresponding content in the file (the file is shorter than the mapping). Both kill the process by default.
A: Byte 4097 is in the second page, which is entirely beyond the file. The kernel delivers SIGBUS. However, bytes 4096–4096 (the last byte of the first page past the file’s 4096-byte content) are fine because the kernel zero-pads the last page up to the page boundary.
A: No. SIGBUS is specific to file-backed mappings. Anonymous mappings are always backed by physical RAM or swap, so there is always content for any page within the mapped range. Accessing out-of-range anonymous pages generates SIGSEGV, not SIGBUS.
A: If the first process accesses pages in the range 512 KB – 1 MB (pages that now have no corresponding file content), the kernel delivers SIGBUS. This is a real-world risk with mmap-based databases — you must coordinate access carefully, or catch SIGBUS to handle the truncation gracefully.
A: mprotect() changes the protection of an existing mapping. A classic use is in JIT (Just-In-Time) compilers: first allocate a PROT_READ|PROT_WRITE anonymous mapping to write machine code into, then call mprotect() to change it to PROT_READ|PROT_EXEC before executing it. This prevents accidentally writing to executable code, and satisfies the W^X (write XOR execute) security principle.
