Hi Ashutosh,
> > * During resize, simply calculate the new size and call ftruncate on each
> > segment to adjust memory accordingly, no need to mmap/munmap or modify any
> > memory mapping.
> >
> >
> That's same as my understanding.
Great, thanks for confirming!
> I thought I had shared a test program upthread, but I don't find it now.
> Attached here. Can you please share your test program?
Sure, mine is attached here (it’s based on another test program you shared
before :-)
Regards,
Jack Ng
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#include <string.h>
#include <errno.h>
#define HUGE_PG_SZ ((size_t)(2048*1024))
unsigned long long MAX_SZ = (100 * ((size_t)(1024 * 1024) * (size_t)(1024 *
1024))); // 100 TB
int main(int argc, char *argv[]) {
int fd_1 = -1;
int fd_2 = -1;
size_t old_size = atoi(argv[1]) * HUGE_PG_SZ;
size_t new_size = atoi(argv[2]) * HUGE_PG_SZ;
// Probe for a valid address that can grow up to MAX_SZ
void *probe = mmap(NULL, MAX_SZ,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB,
-1, 0);
if (probe == MAP_FAILED) {
perror("mmap");
exit(1);
}
if (munmap(probe, MAX_SZ) == -1) {
perror("munmap");
exit(1);
}
/*
|----- Memory mapping (MAP_NORESERVE) #1 -----|----- Memory mapping
(MAP_NORESERVE) #2 -----|
|-- In-use region 1 --|-- Reserved region 1 --|-- In-use region 2 --|--
Reserved region 2 --|
*/
// Create anon mapping #1 that's backed by an anon file fd_1
fd_1 = memfd_create("test_hugepage_map1", MFD_HUGETLB | MFD_HUGE_2MB |
MFD_CLOEXEC);
if (fd_1 == -1) {
perror("memfd_create");
exit(1);
}
void *map1_addr = mmap(probe, MAX_SZ/2,
PROT_READ | PROT_WRITE,
MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, fd_1, 0);
if (map1_addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
// Create in-use region 1
if (ftruncate(fd_1, old_size) == -1) {
perror("ftruncate");
exit(1);
}
// The difference between ftruncate and fallocate is that ftruncate does not
actually
// allocate any memory yet (rather it's allocated on "first touch") but
fallocate
// does and also initializes the memory with zeros.
/*if (fallocate(fd_1, 0, 0, old_size) == -1) {
perror("fallocate");
exit(1);
}*/
// Optionally, we can protect the reserved region just to be safe
if (mprotect(map1_addr + old_size, MAX_SZ/2 - old_size, PROT_NONE) == -1) {
perror("mprotect");
exit(1);
}
// Create anon mapping #2 that's backed by an anon file fd_2
fd_2 = memfd_create("test_hugepage_map2", MFD_HUGETLB | MFD_HUGE_2MB |
MFD_CLOEXEC);
if (fd_2 == -1) {
perror("memfd_create");
exit(1);
}
void *map2_addr = mmap(probe + MAX_SZ/2, MAX_SZ/2,
PROT_READ | PROT_WRITE,
MAP_NORESERVE | MAP_SHARED | MAP_HUGETLB, fd_2, 0);
if (map2_addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
// Create in-use region 2
if (ftruncate(fd_2, old_size) == -1) {
perror("ftruncate");
exit(1);
}
/*if (fallocate(fd_2, 0, 0, old_size) == -1) {
perror("fallocate");
exit(1);
}*/
if (mprotect(map2_addr + old_size, MAX_SZ/2 - old_size, PROT_NONE) == -1) {
perror("mprotect");
exit(1);
}
unsigned char *in_use1 = (unsigned char*)map1_addr;
unsigned char *in_use2 = (unsigned char*)map2_addr;
printf("Press Enter to fill in-use segment 1 memory...\n");
getchar();
memset(map1_addr, 0xee, old_size);
printf("Press Enter to fill in-use segment 2 memory...\n");
getchar();
memset(map2_addr, 0xdd, old_size);
for (int i=0; i < old_size; i++) {
if (in_use1[i] != 0xee) {
printf("seg1 content changed!\n");
exit(1);
}
}
for (int i=0; i < old_size; i++) {
if (in_use2[i] != 0xdd) {
printf("seg2 content changed!\n");
exit(1);
}
}
printf("Press Enter to resize in-use segment 1 memory...\n");
getchar();
// "Unprotect" the old reserved region first
if (mprotect(map1_addr + old_size, MAX_SZ/2 - old_size, PROT_READ |
PROT_WRITE) == -1) {
perror("mprotect");
exit(1);
}
if (ftruncate(fd_1, new_size) == -1) {
perror("fd_1 ftruncate");
exit(1);
}
// Protect the new reserved region
if (mprotect(map1_addr + new_size, MAX_SZ/2 - new_size, PROT_NONE) == -1) {
perror("mprotect");
exit(1);
}
printf("Press Enter to resize in-use segment 2 memory...\n");
getchar();
if (mprotect(map2_addr + old_size, MAX_SZ/2 - old_size, PROT_READ |
PROT_WRITE) == -1) {
perror("mprotect");
exit(1);
}
if (ftruncate(fd_2, new_size) == -1) {
perror("fd_2 ftruncate");
exit(1);
}
if (mprotect(map2_addr + new_size, MAX_SZ/2 - new_size, PROT_NONE) == -1) {
perror("mprotect");
exit(1);
}
size_t check_size = (old_size <= new_size ? old_size : new_size);
for (int i=0; i < check_size; i++) {
if (in_use1[i] != 0xee) {
printf("new seg1 content changed!\n");
exit(1);
}
}
for (int i=0; i < check_size; i++) {
if (in_use2[i] != 0xdd) {
printf("new seg2 content changed!\n");
exit(1);
}
}
memset(map1_addr, 0xaa, new_size);
for (int i=0; i < new_size; i++) {
if (in_use1[i] != 0xaa) {
printf("new seg1 corrupted!\n");
exit(1);
}
}
memset(map2_addr, 0xbb, new_size);
for (int i=0; i < new_size; i++) {
if (in_use2[i] != 0xbb) {
printf("new seg2 corrupted!\n");
exit(1);
}
}
printf("Press Enter to shrink seg 1 to 0...\n");
getchar();
if (ftruncate(fd_1, 0) == -1) {
perror("ftruncate");
exit(1);
}
close(fd_1);
printf("Press Enter to shrink seg 2 to 0...\n");
getchar();
if (ftruncate(fd_2, 0) == -1) {
perror("ftruncate");
exit(1);
}
close(fd_2);
printf("Press Enter to unmap first mapping...\n");
getchar();
if (munmap(map1_addr, MAX_SZ/2) == -1) {
perror("munmap");
exit(1);
}
printf("Press Enter to unmap first mapping...\n");
getchar();
if (munmap(map2_addr, MAX_SZ/2) == -1) {
perror("munmap");
exit(1);
}
printf("Press Enter to termiante program...\n");
getchar();
return 0;
}