diff options
Diffstat (limited to 'tools/testing/selftests/mm/mlock-random-test.c')
| -rw-r--r-- | tools/testing/selftests/mm/mlock-random-test.c | 294 | 
1 files changed, 294 insertions, 0 deletions
diff --git a/tools/testing/selftests/mm/mlock-random-test.c b/tools/testing/selftests/mm/mlock-random-test.c new file mode 100644 index 000000000000..782ea94dee2f --- /dev/null +++ b/tools/testing/selftests/mm/mlock-random-test.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * It tests the mlock/mlock2() when they are invoked + * on randomly memory region. + */ +#include <unistd.h> +#include <sys/resource.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <time.h> +#include "mlock2.h" + +#define CHUNK_UNIT (128 * 1024) +#define MLOCK_RLIMIT_SIZE (CHUNK_UNIT * 2) +#define MLOCK_WITHIN_LIMIT_SIZE CHUNK_UNIT +#define MLOCK_OUTOF_LIMIT_SIZE (CHUNK_UNIT * 3) + +#define TEST_LOOP 100 +#define PAGE_ALIGN(size, ps) (((size) + ((ps) - 1)) & ~((ps) - 1)) + +int set_cap_limits(rlim_t max) +{ +	struct rlimit new; +	cap_t cap = cap_init(); + +	new.rlim_cur = max; +	new.rlim_max = max; +	if (setrlimit(RLIMIT_MEMLOCK, &new)) { +		perror("setrlimit() returns error\n"); +		return -1; +	} + +	/* drop capabilities including CAP_IPC_LOCK */ +	if (cap_set_proc(cap)) { +		perror("cap_set_proc() returns error\n"); +		return -2; +	} + +	return 0; +} + +int get_proc_locked_vm_size(void) +{ +	FILE *f; +	int ret = -1; +	char line[1024] = {0}; +	unsigned long lock_size = 0; + +	f = fopen("/proc/self/status", "r"); +	if (!f) { +		perror("fopen"); +		return -1; +	} + +	while (fgets(line, 1024, f)) { +		if (strstr(line, "VmLck")) { +			ret = sscanf(line, "VmLck:\t%8lu kB", &lock_size); +			if (ret <= 0) { +				printf("sscanf() on VmLck error: %s: %d\n", +						line, ret); +				fclose(f); +				return -1; +			} +			fclose(f); +			return (int)(lock_size << 10); +		} +	} + +	perror("cannot parse VmLck in /proc/self/status\n"); +	fclose(f); +	return -1; +} + +/* + * Get the MMUPageSize of the memory region including input + * address from proc file. + * + * return value: on error case, 0 will be returned. + * Otherwise the page size(in bytes) is returned. + */ +int get_proc_page_size(unsigned long addr) +{ +	FILE *smaps; +	char *line; +	unsigned long mmupage_size = 0; +	size_t size; + +	smaps = seek_to_smaps_entry(addr); +	if (!smaps) { +		printf("Unable to parse /proc/self/smaps\n"); +		return 0; +	} + +	while (getline(&line, &size, smaps) > 0) { +		if (!strstr(line, "MMUPageSize")) { +			free(line); +			line = NULL; +			size = 0; +			continue; +		} + +		/* found the MMUPageSize of this section */ +		if (sscanf(line, "MMUPageSize:    %8lu kB", +					&mmupage_size) < 1) { +			printf("Unable to parse smaps entry for Size:%s\n", +					line); +			break; +		} + +	} +	free(line); +	if (smaps) +		fclose(smaps); +	return mmupage_size << 10; +} + +/* + * Test mlock/mlock2() on provided memory chunk. + * It expects the mlock/mlock2() to be successful (within rlimit) + * + * With allocated memory chunk [p, p + alloc_size), this + * test will choose start/len randomly to perform mlock/mlock2 + * [start, start +  len] memory range. The range is within range + * of the allocated chunk. + * + * The memory region size alloc_size is within the rlimit. + * So we always expect a success of mlock/mlock2. + * + * VmLck is assumed to be 0 before this test. + * + *    return value: 0 - success + *    else: failure + */ +int test_mlock_within_limit(char *p, int alloc_size) +{ +	int i; +	int ret = 0; +	int locked_vm_size = 0; +	struct rlimit cur; +	int page_size = 0; + +	getrlimit(RLIMIT_MEMLOCK, &cur); +	if (cur.rlim_cur < alloc_size) { +		printf("alloc_size[%d] < %u rlimit,lead to mlock failure\n", +				alloc_size, (unsigned int)cur.rlim_cur); +		return -1; +	} + +	srand(time(NULL)); +	for (i = 0; i < TEST_LOOP; i++) { +		/* +		 * - choose mlock/mlock2 randomly +		 * - choose lock_size randomly but lock_size < alloc_size +		 * - choose start_offset randomly but p+start_offset+lock_size +		 *   < p+alloc_size +		 */ +		int is_mlock = !!(rand() % 2); +		int lock_size = rand() % alloc_size; +		int start_offset = rand() % (alloc_size - lock_size); + +		if (is_mlock) +			ret = mlock(p + start_offset, lock_size); +		else +			ret = mlock2_(p + start_offset, lock_size, +				       MLOCK_ONFAULT); + +		if (ret) { +			printf("%s() failure at |%p(%d)| mlock:|%p(%d)|\n", +					is_mlock ? "mlock" : "mlock2", +					p, alloc_size, +					p + start_offset, lock_size); +			return ret; +		} +	} + +	/* +	 * Check VmLck left by the tests. +	 */ +	locked_vm_size = get_proc_locked_vm_size(); +	page_size = get_proc_page_size((unsigned long)p); +	if (page_size == 0) { +		printf("cannot get proc MMUPageSize\n"); +		return -1; +	} + +	if (locked_vm_size > PAGE_ALIGN(alloc_size, page_size) + page_size) { +		printf("test_mlock_within_limit() left VmLck:%d on %d chunk\n", +				locked_vm_size, alloc_size); +		return -1; +	} + +	return 0; +} + + +/* + * We expect the mlock/mlock2() to be fail (outof limitation) + * + * With allocated memory chunk [p, p + alloc_size), this + * test will randomly choose start/len and perform mlock/mlock2 + * on [start, start+len] range. + * + * The memory region size alloc_size is above the rlimit. + * And the len to be locked is higher than rlimit. + * So we always expect a failure of mlock/mlock2. + * No locked page number should be increased as a side effect. + * + *    return value: 0 - success + *    else: failure + */ +int test_mlock_outof_limit(char *p, int alloc_size) +{ +	int i; +	int ret = 0; +	int locked_vm_size = 0, old_locked_vm_size = 0; +	struct rlimit cur; + +	getrlimit(RLIMIT_MEMLOCK, &cur); +	if (cur.rlim_cur >= alloc_size) { +		printf("alloc_size[%d] >%u rlimit, violates test condition\n", +				alloc_size, (unsigned int)cur.rlim_cur); +		return -1; +	} + +	old_locked_vm_size = get_proc_locked_vm_size(); +	srand(time(NULL)); +	for (i = 0; i < TEST_LOOP; i++) { +		int is_mlock = !!(rand() % 2); +		int lock_size = (rand() % (alloc_size - cur.rlim_cur)) +			+ cur.rlim_cur; +		int start_offset = rand() % (alloc_size - lock_size); + +		if (is_mlock) +			ret = mlock(p + start_offset, lock_size); +		else +			ret = mlock2_(p + start_offset, lock_size, +					MLOCK_ONFAULT); +		if (ret == 0) { +			printf("%s() succeeds? on %p(%d) mlock%p(%d)\n", +					is_mlock ? "mlock" : "mlock2", +					p, alloc_size, +					p + start_offset, lock_size); +			return -1; +		} +	} + +	locked_vm_size = get_proc_locked_vm_size(); +	if (locked_vm_size != old_locked_vm_size) { +		printf("tests leads to new mlocked page: old[%d], new[%d]\n", +				old_locked_vm_size, +				locked_vm_size); +		return -1; +	} + +	return 0; +} + +int main(int argc, char **argv) +{ +	char *p = NULL; +	int ret = 0; + +	if (set_cap_limits(MLOCK_RLIMIT_SIZE)) +		return -1; + +	p = malloc(MLOCK_WITHIN_LIMIT_SIZE); +	if (p == NULL) { +		perror("malloc() failure\n"); +		return -1; +	} +	ret = test_mlock_within_limit(p, MLOCK_WITHIN_LIMIT_SIZE); +	if (ret) +		return ret; +	munlock(p, MLOCK_WITHIN_LIMIT_SIZE); +	free(p); + + +	p = malloc(MLOCK_OUTOF_LIMIT_SIZE); +	if (p == NULL) { +		perror("malloc() failure\n"); +		return -1; +	} +	ret = test_mlock_outof_limit(p, MLOCK_OUTOF_LIMIT_SIZE); +	if (ret) +		return ret; +	munlock(p, MLOCK_OUTOF_LIMIT_SIZE); +	free(p); + +	return 0; +}  |