File Index
Files and directories
Sign in
FILE

mrelease_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/mm/mrelease_test.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Copyright 2022 Google LLC
  4 */
  5#define _GNU_SOURCE
  6#include <errno.h>
  7#include <stdbool.h>
  8#include <stdio.h>
  9#include <stdlib.h>
 10#include <sys/syscall.h>
 11#include <sys/wait.h>
 12#include <unistd.h>
 13#include <asm-generic/unistd.h>
 14#include "vm_util.h"
 15#include "kselftest.h"
 16
 17#define MB(x) (x << 20)
 18#define MAX_SIZE_MB 1024
 19
 20static int alloc_noexit(unsigned long nr_pages, int pipefd)
 21{
 22	int ppid = getppid();
 23	int timeout = 10; /* 10sec timeout to get killed */
 24	unsigned long i;
 25	char *buf;
 26
 27	buf = (char *)mmap(NULL, nr_pages * psize(), PROT_READ | PROT_WRITE,
 28			   MAP_PRIVATE | MAP_ANON, 0, 0);
 29	if (buf == MAP_FAILED)
 30		ksft_exit_fail_msg("mmap failed, halting the test: %s\n", strerror(errno));
 31
 32	for (i = 0; i < nr_pages; i++)
 33		*((unsigned long *)(buf + (i * psize()))) = i;
 34
 35	/* Signal the parent that the child is ready */
 36	if (write(pipefd, "", 1) < 0)
 37		ksft_exit_fail_msg("write: %s\n", strerror(errno));
 38
 39	/* Wait to be killed (when reparenting happens) */
 40	while (getppid() == ppid && timeout > 0) {
 41		sleep(1);
 42		timeout--;
 43	}
 44
 45	munmap(buf, nr_pages * psize());
 46
 47	return (timeout > 0) ? KSFT_PASS : KSFT_FAIL;
 48}
 49
 50/* The process_mrelease calls in this test are expected to fail */
 51static void run_negative_tests(int pidfd)
 52{
 53	/* Test invalid flags. Expect to fail with EINVAL error code. */
 54	if (!syscall(__NR_process_mrelease, pidfd, (unsigned int)-1) ||
 55			errno != EINVAL) {
 56		ksft_exit_fail_msg("process_mrelease with wrong flags: %s\n", strerror(errno));
 57	}
 58	/*
 59	 * Test reaping while process is alive with no pending SIGKILL.
 60	 * Expect to fail with EINVAL error code.
 61	 */
 62	if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL)
 63		ksft_exit_fail_msg("process_mrelease on a live process: %s\n", strerror(errno));
 64}
 65
 66static int child_main(int pipefd[], size_t size)
 67{
 68	int res;
 69
 70	/* Allocate and fault-in memory and wait to be killed */
 71	close(pipefd[0]);
 72	res = alloc_noexit(MB(size) / psize(), pipefd[1]);
 73	close(pipefd[1]);
 74	return res;
 75}
 76
 77int main(void)
 78{
 79	int pipefd[2], pidfd;
 80	bool success, retry;
 81	size_t size;
 82	pid_t pid;
 83	char byte;
 84	int res;
 85
 86	ksft_print_header();
 87	ksft_set_plan(1);
 88
 89	/* Test a wrong pidfd */
 90	if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) {
 91		if (errno == ENOSYS) {
 92			ksft_test_result_skip("process_mrelease not implemented\n");
 93			ksft_finished();
 94		} else {
 95			ksft_exit_fail_msg("process_mrelease with wrong pidfd: %s",
 96					   strerror(errno));
 97		}
 98	}
 99
100	/* Start the test with 1MB child memory allocation */
101	size = 1;
102retry:
103	/*
104	 * Pipe for the child to signal when it's done allocating
105	 * memory
106	 */
107	if (pipe(pipefd))
108		ksft_exit_fail_msg("pipe: %s\n", strerror(errno));
109
110	pid = fork();
111	if (pid < 0) {
112		close(pipefd[0]);
113		close(pipefd[1]);
114		ksft_exit_fail_msg("fork: %s\n", strerror(errno));
115	}
116
117	if (pid == 0) {
118		/* Child main routine */
119		res = child_main(pipefd, size);
120		exit(res);
121	}
122
123	/*
124	 * Parent main routine:
125	 * Wait for the child to finish allocations, then kill and reap
126	 */
127	close(pipefd[1]);
128	/* Block until the child is ready */
129	res = read(pipefd[0], &byte, 1);
130	close(pipefd[0]);
131	if (res < 0) {
132		if (!kill(pid, SIGKILL))
133			waitpid(pid, NULL, 0);
134		ksft_exit_fail_msg("read: %s\n", strerror(errno));
135	}
136
137	pidfd = syscall(__NR_pidfd_open, pid, 0);
138	if (pidfd < 0) {
139		if (!kill(pid, SIGKILL))
140			waitpid(pid, NULL, 0);
141		ksft_exit_fail_msg("pidfd_open: %s\n", strerror(errno));
142	}
143
144	/* Run negative tests which require a live child */
145	run_negative_tests(pidfd);
146
147	if (kill(pid, SIGKILL))
148		ksft_exit_fail_msg("kill: %s\n", strerror(errno));
149
150	success = (syscall(__NR_process_mrelease, pidfd, 0) == 0);
151	if (!success) {
152		/*
153		 * If we failed to reap because the child exited too soon,
154		 * before we could call process_mrelease. Double child's memory
155		 * which causes it to spend more time on cleanup and increases
156		 * our chances of reaping its memory before it exits.
157		 * Retry until we succeed or reach MAX_SIZE_MB.
158		 */
159		if (errno == ESRCH) {
160			retry = (size <= MAX_SIZE_MB);
161		} else {
162			waitpid(pid, NULL, 0);
163			ksft_exit_fail_msg("process_mrelease: %s\n", strerror(errno));
164		}
165	}
166
167	/* Cleanup to prevent zombies */
168	if (waitpid(pid, NULL, 0) < 0)
169		ksft_exit_fail_msg("waitpid: %s\n", strerror(errno));
170
171	close(pidfd);
172
173	if (!success) {
174		if (retry) {
175			size *= 2;
176			goto retry;
177		}
178		ksft_exit_fail_msg("All process_mrelease attempts failed!\n");
179	}
180
181	ksft_test_result_pass("Success reaping a child with %zuMB of memory allocations\n",
182			      size);
183	ksft_finished();
184}
~/ Read-only file preview.