if (temp == val) return mid; if (temp < val)
low = mid + 1;
high = mid - 1;
} return low;
}
/* * Returns false if the requested remap region overlaps with an * existing mapping (e.g text, stack) else returns true.
*/ staticbool is_remap_region_valid(void *addr, unsignedlonglong size)
{ void *remap_addr = NULL; bool ret = true;
/* Use MAP_FIXED_NOREPLACE flag to ensure region is not mapped */
remap_addr = mmap(addr, size, PROT_READ | PROT_WRITE,
MAP_FIXED_NOREPLACE | MAP_ANONYMOUS | MAP_SHARED,
-1, 0);
if (remap_addr == MAP_FAILED) { if (errno == EEXIST)
ret = false;
} else {
munmap(remap_addr, size);
}
return ret;
}
/* Returns mmap_min_addr sysctl tunable from procfs */ staticunsignedlonglong get_mmap_min_addr(void)
{
FILE *fp; int n_matched; staticunsignedlonglong addr;
if (addr) return addr;
fp = fopen("/proc/sys/vm/mmap_min_addr", "r"); if (fp == NULL) {
ksft_print_msg("Failed to open /proc/sys/vm/mmap_min_addr: %s\n",
strerror(errno)); exit(KSFT_SKIP);
}
n_matched = fscanf(fp, "%llu", &addr); if (n_matched != 1) {
ksft_print_msg("Failed to read /proc/sys/vm/mmap_min_addr: %s\n",
strerror(errno));
fclose(fp); exit(KSFT_SKIP);
}
fclose(fp); return addr;
}
/* * Using /proc/self/maps, assert that the specified address range is contained * within a single mapping.
*/ staticbool is_range_mapped(FILE *maps_fp, unsignedlong start, unsignedlong end)
{ char *line = NULL;
size_t len = 0; bool success = false; unsignedlong first_val, second_val;
rewind(maps_fp);
while (getline(&line, &len, maps_fp) != -1) { if (sscanf(line, "%lx-%lx", &first_val, &second_val) != 2) {
ksft_exit_fail_msg("cannot parse /proc/self/maps\n"); break;
}
/* Check if [ptr, ptr + size) mapped in /proc/self/maps. */ staticbool is_ptr_mapped(FILE *maps_fp, void *ptr, unsignedlong size)
{ unsignedlong start = (unsignedlong)ptr; unsignedlong end = start + size;
return is_range_mapped(maps_fp, start, end);
}
/* * Returns the start address of the mapping on success, else returns * NULL on failure.
*/ staticvoid *get_source_mapping(struct config c)
{ unsignedlonglong addr = 0ULL; void *src_addr = NULL; unsignedlonglong mmap_min_addr;
mmap_min_addr = get_mmap_min_addr(); /* * For some tests, we need to not have any mappings below the * source mapping. Add some headroom to mmap_min_addr for this.
*/
mmap_min_addr += 10 * _4MB;
retry:
addr += c.src_alignment; if (addr < mmap_min_addr) goto retry;
src_addr = mmap((void *) addr, c.region_size, PROT_READ | PROT_WRITE,
MAP_FIXED_NOREPLACE | MAP_ANONYMOUS | MAP_SHARED,
-1, 0); if (src_addr == MAP_FAILED) { if (errno == EPERM || errno == EEXIST) goto retry; goto error;
} /* * Check that the address is aligned to the specified alignment. * Addresses which have alignments that are multiples of that * specified are not considered valid. For instance, 1GB address is * 2MB-aligned, however it will not be considered valid for a * requested alignment of 2MB. This is done to reduce coincidental * alignment in the tests.
*/ if (((unsignedlonglong) src_addr & (c.src_alignment - 1)) ||
!((unsignedlonglong) src_addr & c.src_alignment)) {
munmap(src_addr, c.region_size); goto retry;
}
/* * This test validates that merge is called when expanding a mapping. * Mapping containing three pages is created, middle page is unmapped * and then the mapping containing the first page is expanded so that * it fills the created hole. The two parts should merge creating * single mapping with three pages.
*/ staticvoid mremap_expand_merge(FILE *maps_fp, unsignedlong page_size)
{ char *test_name = "mremap expand merge"; bool success = false; char *remap, *start;
out: if (success)
ksft_test_result_pass("%s\n", test_name); else
ksft_test_result_fail("%s\n", test_name);
}
/* * Similar to mremap_expand_merge() except instead of removing the middle page, * we remove the last then attempt to remap offset from the second page. This * should result in the mapping being restored to its former state.
*/ staticvoid mremap_expand_merge_offset(FILE *maps_fp, unsignedlong page_size)
{
out: if (success)
ksft_test_result_pass("%s\n", test_name); else
ksft_test_result_fail("%s\n", test_name);
}
/* * Verify that an mremap within a range does not cause corruption * of unrelated part of range. * * Consider the following range which is 2MB aligned and is * a part of a larger 20MB range which is not shown. Each * character is 256KB below making the source and destination * 2MB each. The lower case letters are moved (s to d) and the * upper case letters are not moved. The below test verifies * that the upper case S letters are not corrupted by the * adjacent mremap. * * |DDDDddddSSSSssss|
*/ staticvoid mremap_move_within_range(unsignedint pattern_seed, char *rand_addr)
{ char *test_name = "mremap mremap move within range"; void *src, *dest; unsignedint i, success = 1;
/* Verify byte pattern after remapping */
srand(pattern_seed); for (i = 0; i < SIZE_MB(1); i++) { char c = (char) rand();
if (((char *)src)[i] != c) {
ksft_print_msg("Data at src at %d got corrupted due to unrelated mremap\n",
i);
ksft_print_msg("Expected: %#x\t Got: %#x\n", c & 0xff,
((char *) src)[i] & 0xff);
success = 0;
}
}
out: if (munmap(ptr, size) == -1)
perror("munmap");
if (success)
ksft_test_result_pass("%s\n", test_name); else
ksft_test_result_fail("%s\n", test_name);
}
staticbool is_multiple_vma_range_ok(unsignedint pattern_seed, char *ptr, unsignedlong page_size)
{ int i;
srand(pattern_seed); for (i = 0; i <= 10; i += 2) { int j; char *buf = &ptr[i * page_size];
size_t size = i == 4 ? 2 * page_size : page_size;
/* * Unmap so we end up with: * * 0 2 4 6 8 10 offset in buffer * |*| |*| |*| |*| |*| |*| * |*| |*| |*| |*| |*| |*|
*/ for (i = 1; i < 10; i += 2) { if (munmap(&ptr[i * page_size], page_size)) {
perror("munmap");
success = false; goto out_unmap;
}
}
/* * Shrink in-place across multiple VMAs and gaps so we end up with: * * 0 * |*| * |*|
*/ if (inplace)
res = mremap(ptr, size, page_size, 0); else
res = mremap(ptr, size, page_size, MREMAP_MAYMOVE | MREMAP_FIXED,
tgt_ptr);
/* * Now try to move the entire range which is invalid for multi VMA move. * * This will fail, and no VMA should be moved, as we check this ahead of * time.
*/
res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
err = errno; if (res != MAP_FAILED) {
fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n");
success = false; goto out_unmap;
} if (err != EFAULT) {
errno = err;
perror("mrmeap() unexpected error");
success = false; goto out_unmap;
} if (is_ptr_mapped(maps_fp, tgt_ptr, page_size)) {
fprintf(stderr, "Invalid uffd-armed VMA at start of multi range moved\n");
success = false; goto out_unmap;
}
/* * Now try to move a single VMA, this should succeed as not multi VMA * move.
*/
res = mremap(ptr, page_size, page_size,
MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr); if (res == MAP_FAILED) {
perror("mremap single invalid-multi VMA");
success = false; goto out_unmap;
}
/* * Unmap the VMA, and remap a non-uffd registered (therefore, multi VMA * move valid) VMA at the start of ptr range.
*/ if (munmap(tgt_ptr, page_size)) {
perror("munmap");
success = false; goto out_unmap;
}
res = mmap(ptr, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); if (res == MAP_FAILED) {
perror("mmap");
success = false; goto out_unmap;
}
/* * Now try to move the entire range, we should succeed in moving the * first VMA, but no others, and report a failure.
*/
res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
err = errno; if (res != MAP_FAILED) {
fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n");
success = false; goto out_unmap;
} if (err != EFAULT) {
errno = err;
perror("mrmeap() unexpected error");
success = false; goto out_unmap;
} if (!is_ptr_mapped(maps_fp, tgt_ptr, page_size)) {
fprintf(stderr, "Valid VMA not moved\n");
success = false; goto out_unmap;
}
/* * Unmap the VMA, and map valid VMA at start of ptr range, and replace * all existing multi-move invalid VMAs, except the last, with valid * multi-move VMAs.
*/ if (munmap(tgt_ptr, page_size)) {
perror("munmap");
success = false; goto out_unmap;
} if (munmap(ptr, size - 2 * page_size)) {
perror("munmap");
success = false; goto out_unmap;
} for (i = 0; i < 8; i += 2) {
res = mmap(&ptr[i * page_size], page_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); if (res == MAP_FAILED) {
perror("mmap");
success = false; goto out_unmap;
}
}
/* * Now try to move the entire range, we should succeed in moving all but * the last VMA, and report a failure.
*/
res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
err = errno; if (res != MAP_FAILED) {
fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n");
success = false; goto out_unmap;
} if (err != EFAULT) {
errno = err;
perror("mrmeap() unexpected error");
success = false; goto out_unmap;
}
for (i = 0; i < 10; i += 2) { bool is_mapped = is_ptr_mapped(maps_fp,
&tgt_ptr[i * page_size], page_size);
if (i < 8 && !is_mapped) {
fprintf(stderr, "Valid VMA not moved at %d\n", i);
success = false; goto out_unmap;
} elseif (i == 8 && is_mapped) {
fprintf(stderr, "Invalid VMA moved at %d\n", i);
success = false; goto out_unmap;
}
}
src_addr = get_source_mapping(c); if (!src_addr) {
ret = -1; goto out;
}
/* Set byte pattern for source block. */
memcpy(src_addr, rand_addr, threshold);
/* Mask to zero out lower bits of address for alignment */
align_mask = ~(c.dest_alignment - 1); /* Offset of destination address from the end of the source region */
offset = (c.overlapping) ? -c.dest_alignment : c.dest_alignment;
addr = (void *) (((unsignedlonglong) src_addr + c.region_size
+ offset) & align_mask);
/* Remap after the destination block preamble. */
addr += c.dest_preamble_size;
/* See comment in get_source_mapping() */ if (!((unsignedlonglong) addr & c.dest_alignment))
addr = (void *) ((unsignedlonglong) addr | c.dest_alignment);
/* Don't destroy existing mappings unless expected to overlap */ while (!is_remap_region_valid(addr, c.region_size) && !c.overlapping) { /* Check for unsigned overflow */ if (addr + c.dest_alignment < addr) {
ksft_print_msg("Couldn't find a valid region to remap to\n");
ret = -1; goto clean_up_src;
}
addr += c.dest_alignment;
}
if (c.dest_preamble_size) {
dest_preamble_addr = mmap((void *) addr - c.dest_preamble_size, c.dest_preamble_size,
PROT_READ | PROT_WRITE,
MAP_FIXED_NOREPLACE | MAP_ANONYMOUS | MAP_SHARED,
-1, 0); if (dest_preamble_addr == MAP_FAILED) {
ksft_print_msg("Failed to map dest preamble region: %s\n",
strerror(errno));
ret = -1; goto clean_up_src;
}
/* Set byte pattern for the dest preamble block. */
memcpy(dest_preamble_addr, rand_addr, c.dest_preamble_size);
}
if (dest_addr == MAP_FAILED) {
ksft_print_msg("mremap failed: %s\n", strerror(errno));
ret = -1; goto clean_up_dest_preamble;
}
/* * Verify byte pattern after remapping. Employ an algorithm with a * square root time complexity in threshold: divide the range into * chunks, if memcmp() returns non-zero, only then perform an * iteration in that chunk to find the mismatch index.
*/
num_chunks = get_sqrt(threshold); for (unsignedlong i = 0; i < num_chunks; ++i) {
size_t chunk_size = threshold / num_chunks; unsignedlong shift = i * chunk_size;
if (!memcmp(dest_addr + shift, rand_addr + shift, chunk_size)) continue;
/* brute force iteration only over mismatch segment */ for (t = shift; t < shift + chunk_size; ++t) { if (((char *) dest_addr)[t] != rand_addr[t]) {
ksft_print_msg("Data after remap doesn't match at offset %llu\n",
t);
ksft_print_msg("Expected: %#x\t Got: %#x\n", rand_addr[t] & 0xff,
((char *) dest_addr)[t] & 0xff);
ret = -1; goto clean_up_dest;
}
}
}
/* * if threshold is not divisible by num_chunks, then check the * last chunk
*/ for (t = num_chunks * (threshold / num_chunks); t < threshold; ++t) { if (((char *) dest_addr)[t] != rand_addr[t]) {
ksft_print_msg("Data after remap doesn't match at offset %llu\n",
t);
ksft_print_msg("Expected: %#x\t Got: %#x\n", rand_addr[t] & 0xff,
((char *) dest_addr)[t] & 0xff);
ret = -1; goto clean_up_dest;
}
}
/* Verify the dest preamble byte pattern after remapping */ if (!c.dest_preamble_size) goto no_preamble;
num_chunks = get_sqrt(c.dest_preamble_size);
for (unsignedlong i = 0; i < num_chunks; ++i) {
size_t chunk_size = c.dest_preamble_size / num_chunks; unsignedlong shift = i * chunk_size;
if (!memcmp(dest_preamble_addr + shift, rand_addr + shift,
chunk_size)) continue;
/* brute force iteration only over mismatched segment */ for (d = shift; d < shift + chunk_size; ++d) { if (((char *) dest_preamble_addr)[d] != rand_addr[d]) {
ksft_print_msg("Preamble data after remap doesn't match at offset %llu\n",
d);
ksft_print_msg("Expected: %#x\t Got: %#x\n", rand_addr[d] & 0xff,
((char *) dest_preamble_addr)[d] & 0xff);
ret = -1; goto clean_up_dest;
}
}
}
for (d = num_chunks * (c.dest_preamble_size / num_chunks); d < c.dest_preamble_size; ++d) { if (((char *) dest_preamble_addr)[d] != rand_addr[d]) {
ksft_print_msg("Preamble data after remap doesn't match at offset %llu\n",
d);
ksft_print_msg("Expected: %#x\t Got: %#x\n", rand_addr[d] & 0xff,
((char *) dest_preamble_addr)[d] & 0xff);
ret = -1; goto clean_up_dest;
}
}
/* * Since the destination address is specified using MREMAP_FIXED, subsequent * mremap will unmap any previous mapping at the address range specified by * dest_addr and region_size. This significantly affects the remap time of * subsequent tests. So we clean up mappings after each test.
*/
clean_up_dest:
munmap(dest_addr, c.region_size);
clean_up_dest_preamble: if (c.dest_preamble_size && dest_preamble_addr)
munmap(dest_preamble_addr, c.dest_preamble_size);
clean_up_src:
munmap(src_addr, c.region_size);
out: return ret;
}
/* * Verify that an mremap aligning down does not destroy * the beginning of the mapping just because the aligned * down address landed on a mapping that maybe does not exist.
*/ staticvoid mremap_move_1mb_from_start(unsignedint pattern_seed, char *rand_addr)
{ char *test_name = "mremap move 1mb from start at 1MB+256KB aligned src"; void *src = NULL, *dest = NULL; unsignedint i, success = 1;
/* Config to reuse get_source_mapping() to do an aligned mmap. */ struct config c = {
.src_alignment = SIZE_MB(1) + SIZE_KB(256),
.region_size = SIZE_MB(6)
};
/* Verify byte pattern after remapping */
srand(pattern_seed); for (i = 0; i < SIZE_MB(1); i++) { char c = (char) rand();
if (((char *)src)[i] != c) {
ksft_print_msg("Data at src at %d got corrupted due to unrelated mremap\n",
i);
ksft_print_msg("Expected: %#x\t Got: %#x\n", c & 0xff,
((char *) src)[i] & 0xff);
success = 0;
}
}
out: if (src && munmap(src, c.region_size) == -1)
perror("munmap src");
if (dest && munmap(dest, c.region_size) == -1)
perror("munmap dest");
if (success)
ksft_test_result_pass("%s\n", test_name); else
ksft_test_result_fail("%s\n", test_name);
}
staticvoid run_mremap_test_case(struct test test_case, int *failures, unsignedint threshold_mb, char *rand_addr)
{ longlong remap_time = remap_region(test_case.config, threshold_mb,
rand_addr);
if (remap_time < 0) { if (test_case.expect_failure)
ksft_test_result_xfail("%s\n\tExpected mremap failure\n",
test_case.name); else {
ksft_test_result_fail("%s\n", test_case.name);
*failures += 1;
}
} else { /* * Comparing mremap time is only applicable if entire region * was faulted in.
*/ if (threshold_mb == VALIDATION_NO_THRESHOLD ||
test_case.config.region_size <= threshold_mb * _1MB)
ksft_test_result_pass("%s\n\tmremap time: %12lldns\n",
test_case.name, remap_time); else
ksft_test_result_pass("%s\n", test_case.name);
}
}
staticvoid usage(constchar *cmd)
{
fprintf(stderr, "Usage: %s [[-t <threshold_mb>] [-p <pattern_seed>]]\n" "-t\t only validate threshold_mb of the remapped region\n" " \t if 0 is supplied no threshold is used; all tests\n" " \t are run and remapped regions validated fully.\n" " \t The default threshold used is 4MB.\n" "-p\t provide a seed to generate the random pattern for\n" " \t validating the remapped region.\n", cmd);
}
/* * set preallocated random array according to test configs; see the * functions for the logic of setting the size
*/ if (!threshold_mb)
rand_size = MAX(max_test_variable_region_size,
max_test_constant_region_size); else
rand_size = MAX(MIN(threshold_mb * _1MB,
max_test_variable_region_size),
max_test_constant_region_size);
rand_size = MAX(dest_preamble_size, rand_size);
if (run_perf_tests) {
ksft_print_msg("\n%s\n", "mremap HAVE_MOVE_PMD/PUD optimization time comparison for 1GB region:"); for (i = 0; i < ARRAY_SIZE(perf_test_cases); i++)
run_mremap_test_case(perf_test_cases[i], &failures,
threshold_mb,
rand_addr);
}
munmap(rand_addr, rand_size);
if (failures > 0)
ksft_exit_fail(); else
ksft_exit_pass();
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.