File Index
Files and directories
Sign in
FILE

virtual_address_range.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/mm/virtual_address_range.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright 2017, Anshuman Khandual, IBM Corp.
  4 *
  5 * Works on architectures which support 128TB virtual
  6 * address range and beyond.
  7 */
  8#include <stdio.h>
  9#include <stdlib.h>
 10#include <string.h>
 11#include <unistd.h>
 12#include <errno.h>
 13#include <sys/prctl.h>
 14#include <sys/mman.h>
 15#include <sys/time.h>
 16#include <fcntl.h>
 17
 18#include "vm_util.h"
 19#include "kselftest.h"
 20
 21/*
 22 * Maximum address range mapped with a single mmap()
 23 * call is little bit more than 1GB. Hence 1GB is
 24 * chosen as the single chunk size for address space
 25 * mapping.
 26 */
 27
 28#define SZ_1GB	(1024 * 1024 * 1024UL)
 29#define SZ_1TB	(1024 * 1024 * 1024 * 1024UL)
 30
 31#define MAP_CHUNK_SIZE	SZ_1GB
 32
 33/*
 34 * Address space till 128TB is mapped without any hint
 35 * and is enabled by default. Address space beyond 128TB
 36 * till 512TB is obtained by passing hint address as the
 37 * first argument into mmap() system call.
 38 *
 39 * The process heap address space is divided into two
 40 * different areas one below 128TB and one above 128TB
 41 * till it reaches 512TB. One with size 128TB and the
 42 * other being 384TB.
 43 *
 44 * On Arm64 the address space is 256TB and support for
 45 * high mappings up to 4PB virtual address space has
 46 * been added.
 47 *
 48 * On PowerPC64, the address space up to 128TB can be
 49 * mapped without a hint. Addresses beyond 128TB, up to
 50 * 4PB, can be mapped with a hint.
 51 *
 52 */
 53
 54#define NR_CHUNKS_128TB   ((128 * SZ_1TB) / MAP_CHUNK_SIZE) /* Number of chunks for 128TB */
 55#define NR_CHUNKS_256TB   (NR_CHUNKS_128TB * 2UL)
 56#define NR_CHUNKS_384TB   (NR_CHUNKS_128TB * 3UL)
 57#define NR_CHUNKS_3840TB  (NR_CHUNKS_128TB * 30UL)
 58#define NR_CHUNKS_3968TB  (NR_CHUNKS_128TB * 31UL)
 59
 60#define ADDR_MARK_128TB  (1UL << 47) /* First address beyond 128TB */
 61#define ADDR_MARK_256TB  (1UL << 48) /* First address beyond 256TB */
 62
 63#ifdef __aarch64__
 64#define HIGH_ADDR_MARK  ADDR_MARK_256TB
 65#define HIGH_ADDR_SHIFT 49
 66#define NR_CHUNKS_LOW   NR_CHUNKS_256TB
 67#define NR_CHUNKS_HIGH  NR_CHUNKS_3840TB
 68#elif defined(__PPC64__)
 69#define HIGH_ADDR_MARK  ADDR_MARK_128TB
 70#define HIGH_ADDR_SHIFT 48
 71#define NR_CHUNKS_LOW   NR_CHUNKS_128TB
 72#define NR_CHUNKS_HIGH  NR_CHUNKS_3968TB
 73#else
 74#define HIGH_ADDR_MARK  ADDR_MARK_128TB
 75#define HIGH_ADDR_SHIFT 48
 76#define NR_CHUNKS_LOW   NR_CHUNKS_128TB
 77#define NR_CHUNKS_HIGH  NR_CHUNKS_384TB
 78#endif
 79
 80static char *hint_addr(void)
 81{
 82	int bits = HIGH_ADDR_SHIFT + rand() % (63 - HIGH_ADDR_SHIFT);
 83
 84	return (char *) (1UL << bits);
 85}
 86
 87static void validate_addr(char *ptr, int high_addr)
 88{
 89	unsigned long addr = (unsigned long) ptr;
 90
 91	if (high_addr) {
 92		if (addr < HIGH_ADDR_MARK)
 93			ksft_exit_fail_msg("Bad address %lx\n", addr);
 94		return;
 95	}
 96
 97	if (addr > HIGH_ADDR_MARK)
 98		ksft_exit_fail_msg("Bad address %lx\n", addr);
 99}
100
101static void mark_range(char *ptr, size_t size)
102{
103	if (prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, size, "virtual_address_range") == -1) {
104		if (errno == EINVAL) {
105			/* Depends on CONFIG_ANON_VMA_NAME */
106			ksft_test_result_skip("prctl(PR_SET_VMA_ANON_NAME) not supported\n");
107			ksft_finished();
108		} else {
109			ksft_exit_fail_perror("prctl(PR_SET_VMA_ANON_NAME) failed\n");
110		}
111	}
112}
113
114static int is_marked_vma(const char *vma_name)
115{
116	return vma_name && !strcmp(vma_name, "[anon:virtual_address_range]\n");
117}
118
119static int validate_lower_address_hint(void)
120{
121	char *ptr;
122
123	ptr = mmap((void *) (1UL << 45), MAP_CHUNK_SIZE, PROT_READ |
124		   PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
125
126	if (ptr == MAP_FAILED)
127		return 0;
128
129	return 1;
130}
131
132static int validate_complete_va_space(void)
133{
134	unsigned long start_addr, end_addr, prev_end_addr;
135	char line[400];
136	char prot[6];
137	FILE *file;
138	int fd;
139
140	fd = open("va_dump", O_CREAT | O_WRONLY, 0600);
141	unlink("va_dump");
142	if (fd < 0) {
143		ksft_test_result_skip("cannot create or open dump file\n");
144		ksft_finished();
145	}
146
147	file = fopen("/proc/self/maps", "r");
148	if (file == NULL)
149		ksft_exit_fail_msg("cannot open /proc/self/maps\n");
150
151	prev_end_addr = 0;
152	while (fgets(line, sizeof(line), file)) {
153		const char *vma_name = NULL;
154		int vma_name_start = 0;
155		unsigned long hop;
156
157		if (sscanf(line, "%lx-%lx %4s %*s %*s %*s %n",
158			   &start_addr, &end_addr, prot, &vma_name_start) != 3)
159			ksft_exit_fail_msg("cannot parse /proc/self/maps\n");
160
161		if (vma_name_start)
162			vma_name = line + vma_name_start;
163
164		/* end of userspace mappings; ignore vsyscall mapping */
165		if (start_addr & (1UL << 63))
166			return 0;
167
168		/* /proc/self/maps must have gaps less than MAP_CHUNK_SIZE */
169		if (start_addr - prev_end_addr >= MAP_CHUNK_SIZE)
170			return 1;
171
172		prev_end_addr = end_addr;
173
174		if (prot[0] != 'r')
175			continue;
176
177		if (check_vmflag_io((void *)start_addr))
178			continue;
179
180		/*
181		 * Confirm whether MAP_CHUNK_SIZE chunk can be found or not.
182		 * If write succeeds, no need to check MAP_CHUNK_SIZE - 1
183		 * addresses after that. If the address was not held by this
184		 * process, write would fail with errno set to EFAULT.
185		 * Anyways, if write returns anything apart from 1, exit the
186		 * program since that would mean a bug in /proc/self/maps.
187		 */
188		hop = 0;
189		while (start_addr + hop < end_addr) {
190			if (write(fd, (void *)(start_addr + hop), 1) != 1)
191				return 1;
192			lseek(fd, 0, SEEK_SET);
193
194			if (is_marked_vma(vma_name))
195				munmap((char *)(start_addr + hop), MAP_CHUNK_SIZE);
196
197			hop += MAP_CHUNK_SIZE;
198		}
199	}
200	return 0;
201}
202
203int main(int argc, char *argv[])
204{
205	char *ptr[NR_CHUNKS_LOW];
206	char **hptr;
207	char *hint;
208	unsigned long i, lchunks, hchunks;
209
210	ksft_print_header();
211	ksft_set_plan(1);
212
213	for (i = 0; i < NR_CHUNKS_LOW; i++) {
214		ptr[i] = mmap(NULL, MAP_CHUNK_SIZE, PROT_READ,
215			      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
216
217		if (ptr[i] == MAP_FAILED) {
218			if (validate_lower_address_hint())
219				ksft_exit_fail_msg("mmap unexpectedly succeeded with hint\n");
220			break;
221		}
222
223		mark_range(ptr[i], MAP_CHUNK_SIZE);
224		validate_addr(ptr[i], 0);
225	}
226	lchunks = i;
227	hptr = (char **) calloc(NR_CHUNKS_HIGH, sizeof(char *));
228	if (hptr == NULL) {
229		ksft_test_result_skip("Memory constraint not fulfilled\n");
230		ksft_finished();
231	}
232
233	for (i = 0; i < NR_CHUNKS_HIGH; i++) {
234		hint = hint_addr();
235		hptr[i] = mmap(hint, MAP_CHUNK_SIZE, PROT_READ,
236			       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
237
238		if (hptr[i] == MAP_FAILED)
239			break;
240
241		mark_range(hptr[i], MAP_CHUNK_SIZE);
242		validate_addr(hptr[i], 1);
243	}
244	hchunks = i;
245	if (validate_complete_va_space()) {
246		ksft_test_result_fail("BUG in mmap() or /proc/self/maps\n");
247		ksft_finished();
248	}
249
250	for (i = 0; i < lchunks; i++)
251		munmap(ptr[i], MAP_CHUNK_SIZE);
252
253	for (i = 0; i < hchunks; i++)
254		munmap(hptr[i], MAP_CHUNK_SIZE);
255
256	free(hptr);
257
258	ksft_test_result_pass("Test\n");
259	ksft_finished();
260}
~/ Read-only file preview.