The Linux man mmap
manual warns against changing the size of a file after it has been mapped:
The effect of changing the size of the underlying file of a mapping on the pages that correspond to added or removed regions of the file is unspecified.
Some argue that it's fine as long as you're not accessing the data beyond the end of a file boundary.
If you adopt the following sequence:
msync
the filemunmap
the fileftruncate
the filemmap
the file againwouldn't it be extremely costly (if you need to change the file size frequently, you would have to transfer data from HDD to RAM back and forth over and over again)?
So what is the correct way of dealing with changing the size of a mapped file?
i tried three minimal programs using your method msync → munmap → ftruncate → mmap
, munmap → ftruncate → mmap
and mremap
. Not using msync
was recommended in comments by Aykhan Hagverdili
msync → munmap → ftruncate → mmap
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define FILE_PATH "testfile"
#define INITIAL_SIZE 4096
#define EXTENDED_SIZE 8192
void handle_error(const char *msg) {
perror(msg);
_exit(1);
}
int main() {
// Create or open file
int fd = open(FILE_PATH, O_RDWR | O_CREAT, 0666);
if (fd == -1) handle_error("open");
// Set the initial file size
if(ftruncate(fd, INITIAL_SIZE) == -1) handle_error("ftruncate");
// Map the initial region
void *map = mmap(NULL, INITIAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) handle_error("mmap");
// Write data to the mapped region
strncpy((char *)map, "Hello,mmap!", INITIAL_SIZE);
// Synchronize changes to disk
if (msync(map, INITIAL_SIZE, MS_SYNC) == -1) handle_error("msync");
// Unmap the region
if (munmap(map, INITIAL_SIZE) == -1) handle_error("munmap");
// Extend the file size
if (ftruncate(fd, EXTENDED_SIZE) == -1) handle_error("munmap");
// Remap the file with the new size
map = mmap(NULL, EXTENDED_SIZE, PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);
if (map == MAP_FAILED) handle_error("mmap");
// Write data to the extended region
strncpy((char *)map + INITIAL_SIZE, "Extend region!", EXTENDED_SIZE - INITIAL_SIZE);
// Synchronize changes to disk
if (msync(map, EXTENDED_SIZE, MS_SYNC) == -1) handle_error("msync");
// cleanup
if (munmap(map, EXTENDED_SIZE) == -1) handle_error("munmap");
close(fd);
printf("\ntest completed sucessfully\n");
return 0;
}
the time for the program
$ time ./msync_mmap_test
test completed sucessfully
real 0m0,015s
user 0m0,003s
sys 0m0,000s
with mremap
#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd = open("testfile_mmap_test", O_RDWR | O_CREAT, 0666);
if(fd == -1){
perror("open");
return 1;
}
size_t size = 4096;
ftruncate(fd, size);
void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(map == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
//Access the file
sprintf((char *)map, "Hello, mmap!");
printf("File contents: %s\n", (char *)map);
size_t new_size = 8192;
ftruncate(fd, new_size);
// remap to new size
map = mremap(map, size, new_size, MREMAP_MAYMOVE);
if (map == MAP_FAILED) {
perror("mremap");
close(fd);
return 1;
}
//Use new region
sprintf((char *)map + 4096, "Extended region");
printf("File contents (extended): %s\n", (char *)map + 4096);
//clean up
munmap(map, new_size);
close(fd);
return 0;
}
the time for the mremap
$ time ./mmap_test
File contents: Hello, mmap!
File contents (extended): Extended region
real 0m0,003s
user 0m0,003s
sys 0m0,000s
with no_msync
#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define FILE_PATH "testfile_no_msync"
#define INITIAL_SIZE 4096
#define EXTENDED_SIZE 8192
void handle_error(const char *msg) {
perror(msg);
_exit(1);
}
int main() {
// Create or open the file
int fd = open(FILE_PATH, O_RDWR | O_CREAT, 0666);
if (fd == -1) handle_error("open");
// Set the initial file size
if (ftruncate(fd, INITIAL_SIZE) == -1) handle_error("ftruncate");
// Map the initial region
void *map = mmap(NULL, INITIAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) handle_error("mmap");
// Write data to the mapped region
strncpy((char *)map, "Hello, mmap!", INITIAL_SIZE);
// Unmap the region without msync
if (munmap(map, INITIAL_SIZE) == -1) handle_error("munmap");
// Extend the file size
if (ftruncate(fd, EXTENDED_SIZE) == -1) handle_error("ftruncate");
// Remap the file with the new size
map = mmap(NULL, EXTENDED_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) handle_error("mmap");
// Write data to the extended region
strncpy((char *)map + INITIAL_SIZE, "Extended region!", EXTENDED_SIZE - INITIAL_SIZE);
// Cleanup
if (munmap(map, EXTENDED_SIZE) == -1) handle_error("munmap");
close(fd);
printf("\nTest completed successfully without msync\n");
return 0;
}
the time for no msync
$ time ./no_msync
Test completed successfully without msync
real 0m0,003s
user 0m0,003s
sys 0m0,000s
the execution time for mremap
and without msync
was same. As per man msync
there is no guarantee that changes will be written back to the file before the memory is unmapped using munmap() or the program exits.
When working with critical data that must persist even in the event of a crash or power failure, the msync
should be used.
As per man mremap
mremap() system call is used to resize an existing memory mapping created with mmap(). It allows you to expand, shrink, or move the memory mapping to a new location while keeping the data in the memory region intact. It is good way to use for dynamically resizing memory-mapped regions to accommodate additional data and avoiding the overhead of unmapping and remapping.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With