Skip to content

Commit 85e3b86

Browse files
Christian Braunerhtejun
authored andcommitted
tests/cgroup: test cgroup.kill
Test that the new cgroup.kill feature works as intended. Link: https://lore.kernel.org/r/[email protected] Cc: Shakeel Butt <[email protected]> Cc: Tejun Heo <[email protected]> Cc: [email protected] Acked-by: Roman Gushchin <[email protected]> Signed-off-by: Christian Brauner <[email protected]> Signed-off-by: Tejun Heo <[email protected]>
1 parent 8075e4f commit 85e3b86

File tree

3 files changed

+301
-1
lines changed

3 files changed

+301
-1
lines changed

tools/testing/selftests/cgroup/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
test_memcontrol
33
test_core
44
test_freezer
5-
test_kmem
5+
test_kmem
6+
test_kill

tools/testing/selftests/cgroup/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ TEST_GEN_PROGS = test_memcontrol
99
TEST_GEN_PROGS += test_kmem
1010
TEST_GEN_PROGS += test_core
1111
TEST_GEN_PROGS += test_freezer
12+
TEST_GEN_PROGS += test_kill
1213

1314
include ../lib.mk
1415

1516
$(OUTPUT)/test_memcontrol: cgroup_util.c ../clone3/clone3_selftests.h
1617
$(OUTPUT)/test_kmem: cgroup_util.c ../clone3/clone3_selftests.h
1718
$(OUTPUT)/test_core: cgroup_util.c ../clone3/clone3_selftests.h
1819
$(OUTPUT)/test_freezer: cgroup_util.c ../clone3/clone3_selftests.h
20+
$(OUTPUT)/test_kill: cgroup_util.c ../clone3/clone3_selftests.h ../pidfd/pidfd.h
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
3+
#include <errno.h>
4+
#include <linux/limits.h>
5+
#include <stdbool.h>
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
#include <sys/types.h>
10+
#include <unistd.h>
11+
12+
#include "../kselftest.h"
13+
#include "../pidfd/pidfd.h"
14+
#include "cgroup_util.h"
15+
16+
/*
17+
* Kill the given cgroup and wait for the inotify signal.
18+
* If there are no events in 10 seconds, treat this as an error.
19+
* Then check that the cgroup is in the desired state.
20+
*/
21+
static int cg_kill_wait(const char *cgroup)
22+
{
23+
int fd, ret = -1;
24+
25+
fd = cg_prepare_for_wait(cgroup);
26+
if (fd < 0)
27+
return fd;
28+
29+
ret = cg_write(cgroup, "cgroup.kill", "1");
30+
if (ret)
31+
goto out;
32+
33+
ret = cg_wait_for(fd);
34+
if (ret)
35+
goto out;
36+
37+
out:
38+
close(fd);
39+
return ret;
40+
}
41+
42+
/*
43+
* A simple process running in a sleep loop until being
44+
* re-parented.
45+
*/
46+
static int child_fn(const char *cgroup, void *arg)
47+
{
48+
int ppid = getppid();
49+
50+
while (getppid() == ppid)
51+
usleep(1000);
52+
53+
return getppid() == ppid;
54+
}
55+
56+
static int test_cgkill_simple(const char *root)
57+
{
58+
pid_t pids[100];
59+
int ret = KSFT_FAIL;
60+
char *cgroup = NULL;
61+
int i;
62+
63+
cgroup = cg_name(root, "cg_test_simple");
64+
if (!cgroup)
65+
goto cleanup;
66+
67+
if (cg_create(cgroup))
68+
goto cleanup;
69+
70+
for (i = 0; i < 100; i++)
71+
pids[i] = cg_run_nowait(cgroup, child_fn, NULL);
72+
73+
if (cg_wait_for_proc_count(cgroup, 100))
74+
goto cleanup;
75+
76+
if (cg_read_strcmp(cgroup, "cgroup.events", "populated 1\n"))
77+
goto cleanup;
78+
79+
if (cg_kill_wait(cgroup))
80+
goto cleanup;
81+
82+
ret = KSFT_PASS;
83+
84+
cleanup:
85+
for (i = 0; i < 100; i++)
86+
wait_for_pid(pids[i]);
87+
88+
if (ret == KSFT_PASS &&
89+
cg_read_strcmp(cgroup, "cgroup.events", "populated 0\n"))
90+
ret = KSFT_FAIL;
91+
92+
if (cgroup)
93+
cg_destroy(cgroup);
94+
free(cgroup);
95+
return ret;
96+
}
97+
98+
/*
99+
* The test creates the following hierarchy:
100+
* A
101+
* / / \ \
102+
* B E I K
103+
* /\ |
104+
* C D F
105+
* |
106+
* G
107+
* |
108+
* H
109+
*
110+
* with a process in C, H and 3 processes in K.
111+
* Then it tries to kill the whole tree.
112+
*/
113+
static int test_cgkill_tree(const char *root)
114+
{
115+
pid_t pids[5];
116+
char *cgroup[10] = {0};
117+
int ret = KSFT_FAIL;
118+
int i;
119+
120+
cgroup[0] = cg_name(root, "cg_test_tree_A");
121+
if (!cgroup[0])
122+
goto cleanup;
123+
124+
cgroup[1] = cg_name(cgroup[0], "B");
125+
if (!cgroup[1])
126+
goto cleanup;
127+
128+
cgroup[2] = cg_name(cgroup[1], "C");
129+
if (!cgroup[2])
130+
goto cleanup;
131+
132+
cgroup[3] = cg_name(cgroup[1], "D");
133+
if (!cgroup[3])
134+
goto cleanup;
135+
136+
cgroup[4] = cg_name(cgroup[0], "E");
137+
if (!cgroup[4])
138+
goto cleanup;
139+
140+
cgroup[5] = cg_name(cgroup[4], "F");
141+
if (!cgroup[5])
142+
goto cleanup;
143+
144+
cgroup[6] = cg_name(cgroup[5], "G");
145+
if (!cgroup[6])
146+
goto cleanup;
147+
148+
cgroup[7] = cg_name(cgroup[6], "H");
149+
if (!cgroup[7])
150+
goto cleanup;
151+
152+
cgroup[8] = cg_name(cgroup[0], "I");
153+
if (!cgroup[8])
154+
goto cleanup;
155+
156+
cgroup[9] = cg_name(cgroup[0], "K");
157+
if (!cgroup[9])
158+
goto cleanup;
159+
160+
for (i = 0; i < 10; i++)
161+
if (cg_create(cgroup[i]))
162+
goto cleanup;
163+
164+
pids[0] = cg_run_nowait(cgroup[2], child_fn, NULL);
165+
pids[1] = cg_run_nowait(cgroup[7], child_fn, NULL);
166+
pids[2] = cg_run_nowait(cgroup[9], child_fn, NULL);
167+
pids[3] = cg_run_nowait(cgroup[9], child_fn, NULL);
168+
pids[4] = cg_run_nowait(cgroup[9], child_fn, NULL);
169+
170+
/*
171+
* Wait until all child processes will enter
172+
* corresponding cgroups.
173+
*/
174+
175+
if (cg_wait_for_proc_count(cgroup[2], 1) ||
176+
cg_wait_for_proc_count(cgroup[7], 1) ||
177+
cg_wait_for_proc_count(cgroup[9], 3))
178+
goto cleanup;
179+
180+
/*
181+
* Kill A and check that we get an empty notification.
182+
*/
183+
if (cg_kill_wait(cgroup[0]))
184+
goto cleanup;
185+
186+
ret = KSFT_PASS;
187+
188+
cleanup:
189+
for (i = 0; i < 5; i++)
190+
wait_for_pid(pids[i]);
191+
192+
if (ret == KSFT_PASS &&
193+
cg_read_strcmp(cgroup[0], "cgroup.events", "populated 0\n"))
194+
ret = KSFT_FAIL;
195+
196+
for (i = 9; i >= 0 && cgroup[i]; i--) {
197+
cg_destroy(cgroup[i]);
198+
free(cgroup[i]);
199+
}
200+
201+
return ret;
202+
}
203+
204+
static int forkbomb_fn(const char *cgroup, void *arg)
205+
{
206+
int ppid;
207+
208+
fork();
209+
fork();
210+
211+
ppid = getppid();
212+
213+
while (getppid() == ppid)
214+
usleep(1000);
215+
216+
return getppid() == ppid;
217+
}
218+
219+
/*
220+
* The test runs a fork bomb in a cgroup and tries to kill it.
221+
*/
222+
static int test_cgkill_forkbomb(const char *root)
223+
{
224+
int ret = KSFT_FAIL;
225+
char *cgroup = NULL;
226+
pid_t pid = -ESRCH;
227+
228+
cgroup = cg_name(root, "cg_forkbomb_test");
229+
if (!cgroup)
230+
goto cleanup;
231+
232+
if (cg_create(cgroup))
233+
goto cleanup;
234+
235+
pid = cg_run_nowait(cgroup, forkbomb_fn, NULL);
236+
if (pid < 0)
237+
goto cleanup;
238+
239+
usleep(100000);
240+
241+
if (cg_kill_wait(cgroup))
242+
goto cleanup;
243+
244+
if (cg_wait_for_proc_count(cgroup, 0))
245+
goto cleanup;
246+
247+
ret = KSFT_PASS;
248+
249+
cleanup:
250+
if (pid > 0)
251+
wait_for_pid(pid);
252+
253+
if (ret == KSFT_PASS &&
254+
cg_read_strcmp(cgroup, "cgroup.events", "populated 0\n"))
255+
ret = KSFT_FAIL;
256+
257+
if (cgroup)
258+
cg_destroy(cgroup);
259+
free(cgroup);
260+
return ret;
261+
}
262+
263+
#define T(x) { x, #x }
264+
struct cgkill_test {
265+
int (*fn)(const char *root);
266+
const char *name;
267+
} tests[] = {
268+
T(test_cgkill_simple),
269+
T(test_cgkill_tree),
270+
T(test_cgkill_forkbomb),
271+
};
272+
#undef T
273+
274+
int main(int argc, char *argv[])
275+
{
276+
char root[PATH_MAX];
277+
int i, ret = EXIT_SUCCESS;
278+
279+
if (cg_find_unified_root(root, sizeof(root)))
280+
ksft_exit_skip("cgroup v2 isn't mounted\n");
281+
for (i = 0; i < ARRAY_SIZE(tests); i++) {
282+
switch (tests[i].fn(root)) {
283+
case KSFT_PASS:
284+
ksft_test_result_pass("%s\n", tests[i].name);
285+
break;
286+
case KSFT_SKIP:
287+
ksft_test_result_skip("%s\n", tests[i].name);
288+
break;
289+
default:
290+
ret = EXIT_FAILURE;
291+
ksft_test_result_fail("%s\n", tests[i].name);
292+
break;
293+
}
294+
}
295+
296+
return ret;
297+
}

0 commit comments

Comments
 (0)