In this test case, the parent is allowed to alloc THP, but the child forked by it can't alloc THP. Signed-off-by: Yafang Shao --- .../selftests/bpf/prog_tests/thp_adjust.c | 59 +++++++++++++++++++ .../selftests/bpf/progs/test_thp_adjust.c | 39 ++++++++++++ 2 files changed, 98 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/thp_adjust.c b/tools/testing/selftests/bpf/prog_tests/thp_adjust.c index a4a34ee28301..bf367c6e6f52 100644 --- a/tools/testing/selftests/bpf/prog_tests/thp_adjust.c +++ b/tools/testing/selftests/bpf/prog_tests/thp_adjust.c @@ -1,5 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include + #include #include #include @@ -170,6 +176,57 @@ static void subtest_thp_policy(void) bpf_link__destroy(ops_link); } +/* + * In this test case, we clone the child process directly into the root cgroup. + * Consequently, the child process is not permitted to alloc THP. + */ +static void subtest_thp_fork(void) +{ + struct clone_args args = { + .flags = CLONE_INTO_CGROUP, + .exit_signal = SIGCHLD, + }; + struct bpf_link *ops_link; + int status, err; + pid_t pid; + + skel->bss->ppid = getpid(); + if (!ASSERT_GT(skel->bss->ppid, 0, "getpid")) + return; + args.cgroup = get_root_cgroup(); + if (!ASSERT_GE(args.cgroup, 0, "get_root_cgrp_fd")) + return; + + ops_link = bpf_map__attach_struct_ops(skel->maps.thp_fork_ops); + if (!ASSERT_OK_PTR(ops_link, "attach struct_ops")) + return; + + pid = syscall(__NR_clone3, &args, sizeof(args)); + if (!ASSERT_GE(pid, 0, "clone3")) + goto detach_ops; + + if (pid == 0) { + /* child */ + if (!ASSERT_NEQ(thp_alloc(), -1, "THP alloc")) + exit(EXIT_FAILURE); + thp_free(); + exit(EXIT_SUCCESS); + } + + err = waitpid(pid, &status, 0); + if (!ASSERT_EQ(err, pid, "waitpid")) + goto detach_ops; + ASSERT_EQ(skel->bss->fork_fail, 0, "fork_fail"); + ASSERT_GT(skel->bss->fork_succeed, 0, "fork_succeed"); + + if (!ASSERT_NEQ(thp_alloc(), -1, "THP alloc")) + goto detach_ops; + thp_free(); + ASSERT_GT(skel->bss->parent_succeed, 0, "parent_succeed"); +detach_ops: + bpf_link__destroy(ops_link); +} + static int thp_adjust_setup(void) { int err, cgrp_fd, cgrp_id, pmd_order; @@ -249,6 +306,8 @@ void test_thp_adjust(void) if (test__start_subtest("alloc_in_khugepaged")) subtest_thp_policy(); + if (test__start_subtest("khugepaged_fork")) + subtest_thp_fork(); thp_adjust_destroy(); } diff --git a/tools/testing/selftests/bpf/progs/test_thp_adjust.c b/tools/testing/selftests/bpf/progs/test_thp_adjust.c index 635915f31786..034086ce2f3d 100644 --- a/tools/testing/selftests/bpf/progs/test_thp_adjust.c +++ b/tools/testing/selftests/bpf/progs/test_thp_adjust.c @@ -6,6 +6,7 @@ char _license[] SEC("license") = "GPL"; +int ppid, fork_fail, fork_succeed, parent_succeed; int pf_alloc, pf_disallow, khugepaged_disallow; struct mm_struct *target_mm; int pmd_order, cgrp_id; @@ -74,3 +75,41 @@ SEC(".struct_ops.link") struct bpf_thp_ops khugepaged_ops = { .get_suggested_order = (void *)alloc_in_khugepaged, }; + + +SEC("struct_ops/get_suggested_order") +int BPF_PROG(thp_fork_test, struct mm_struct *mm, struct vm_area_struct *vma__nullable, + u64 vma_flags, enum tva_type tva_flags, int orders) +{ + struct task_struct *p = bpf_get_current_task_btf(); + struct mem_cgroup *memcg; + int suggested_orders = 0; + + /* Only works when CONFIG_MEMCG is enabled. */ + memcg = bpf_mm_get_mem_cgroup(mm); + if (!memcg) + return 0; + + /* The tasks under this specific cgroup are allowed to alloc THP */ + if (memcg->css.cgroup->kn->id == cgrp_id) + suggested_orders = orders; + + if (p->parent->pid == ppid) { + /* The child is forked into root cgrp, so it can't alloc THP */ + if (suggested_orders) + fork_fail++; + else + fork_succeed++; + } else if (p->pid == ppid) { + /* The parent can alloc THP */ + if (suggested_orders) + parent_succeed++; + } + bpf_put_mem_cgroup(memcg); + return suggested_orders; +} + +SEC(".struct_ops.link") +struct bpf_thp_ops thp_fork_ops = { + .get_suggested_order = (void *)thp_fork_test, +}; -- 2.47.3