Skip to content

Commit 3377614

Browse files
surenbaghdasaryanakpm00
authored andcommitted
selftests: vm: add process_mrelease tests
Introduce process_mrelease syscall sanity tests which include tests which expect to fail: - process_mrelease with invalid pidfd and flags inputs - process_mrelease on a live process with no pending signals and valid process_mrelease usage which is expected to succeed. Because process_mrelease has to be used against a process with a pending SIGKILL, it's possible that the process exits before process_mrelease gets called. In such cases we retry the test with a victim that allocates twice more memory up to 1GB. This would require the victim process to spend more time during exit and process_mrelease has a better chance of catching the process before it exits and succeeding. On success the test reports the amount of memory the child had to allocate for reaping to succeed. Sample output: $ mrelease_test Success reaping a child with 1MB of memory allocations On failure the test reports the failure. Sample outputs: $ mrelease_test All process_mrelease attempts failed! $ mrelease_test process_mrelease: Invalid argument Link: https://lkml.kernel.org/r/20220518204316.13131-1-surenb@google.com Signed-off-by: Suren Baghdasaryan <surenb@google.com> Reviewed-by: Shuah Khan <skhan@linuxfoundation.org> Acked-by: Christian Brauner (Microsoft) <brauner@kernel.org> Reviewed-by: Muhammad Usama Anjum <usama.anjum@collabora.com> Cc: Michal Hocko <mhocko@suse.com> Cc: David Rientjes <rientjes@google.com> Cc: Matthew Wilcox (Oracle) <willy@infradead.org> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Roman Gushchin <guro@fb.com> Cc: Minchan Kim <minchan@kernel.org> Cc: "Kirill A . Shutemov" <kirill@shutemov.name> Cc: Andrea Arcangeli <aarcange@redhat.com> Cc: Christoph Hellwig <hch@infradead.org> Cc: Oleg Nesterov <oleg@redhat.com> Cc: David Hildenbrand <david@redhat.com> Cc: Jann Horn <jannh@google.com> Cc: Shakeel Butt <shakeelb@google.com> Cc: Peter Xu <peterx@redhat.com> Cc: John Hubbard <jhubbard@nvidia.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent 3f1509c commit 3377614

4 files changed

Lines changed: 204 additions & 0 deletions

File tree

tools/testing/selftests/vm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ map_populate
1010
thuge-gen
1111
compaction_test
1212
mlock2-tests
13+
mrelease_test
1314
mremap_dontunmap
1415
mremap_test
1516
on-fault-limit

tools/testing/selftests/vm/Makefile

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

tools/testing/selftests/vm/run_vmtests.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ run_test ./mlock-random-test
141141

142142
run_test ./mlock2-tests
143143

144+
run_test ./mrelease_test
145+
144146
run_test ./mremap_test
145147

146148
run_test ./thuge-gen

0 commit comments

Comments
 (0)