We recently encountered a kernel crash on the zswapin path in our internal kernel, which went undetected because of a lack of test coverage for this path. Add a selftest to cover this code path, allocating more memories than the cgroup limit to trigger swapout/zswapout, then reading the pages back in memories several times. Also add a variant of this test that runs with zswap disabled, to verify swapin correctness as well. Suggested-by: Rik van Riel <riel@xxxxxxxxxxx> Signed-off-by: Nhat Pham <nphamcs@xxxxxxxxx> --- tools/testing/selftests/cgroup/test_zswap.c | 67 ++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 32ce975b21d1..86231c86dc89 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -60,17 +60,39 @@ static long get_zswpout(const char *cgroup) return cg_read_key_long(cgroup, "memory.stat", "zswpout "); } -static int allocate_bytes(const char *cgroup, void *arg) +static int allocate_bytes_and_read(const char *cgroup, void *arg, bool read) { size_t size = (size_t)arg; char *mem = (char *)malloc(size); + int ret = 0; if (!mem) return -1; for (int i = 0; i < size; i += 4095) mem[i] = 'a'; + + if (read) { + /* cycle through the allocated memory to (z)swap in and out pages */ + for (int t = 0; t < 5; t++) { + for (int i = 0; i < size; i += 4095) { + if (mem[i] != 'a') + ret = -1; + } + } + } + free(mem); - return 0; + return ret; +} + +static int allocate_bytes(const char *cgroup, void *arg) +{ + return allocate_bytes_and_read(cgroup, arg, false); +} + +static int read_bytes(const char *cgroup, void *arg) +{ + return allocate_bytes_and_read(cgroup, arg, true); } static char *setup_test_group_1M(const char *root, const char *name) @@ -133,6 +155,45 @@ static int test_zswap_usage(const char *root) return ret; } +/* Simple test to verify the (z)swapin code paths */ +static int test_zswapin_size(const char *root, char *zswap_size) +{ + int ret = KSFT_FAIL; + char *test_group; + + /* Set up */ + test_group = cg_name(root, "zswapin_test"); + if (!test_group) + goto out; + if (cg_create(test_group)) + goto out; + if (cg_write(test_group, "memory.max", "8M")) + goto out; + if (cg_write(test_group, "memory.zswap.max", zswap_size)) + goto out; + + /* Allocate and read more than memory.max to trigger (z)swap in */ + if (cg_run(test_group, read_bytes, (void *)MB(32))) + goto out; + + ret = KSFT_PASS; + +out: + cg_destroy(test_group); + free(test_group); + return ret; +} + +static int test_swapin(const char *root) +{ + return test_zswapin_size(root, "0"); +} + +static int test_zswapin_no_limit(const char *root) +{ + return test_zswapin_size(root, "max"); +} + /* * When trying to store a memcg page in zswap, if the memcg hits its memory * limit in zswap, writeback should affect only the zswapped pages of that @@ -309,6 +370,8 @@ struct zswap_test { const char *name; } tests[] = { T(test_zswap_usage), + T(test_swapin), + T(test_zswapin_no_limit), T(test_no_kmem_bypass), T(test_no_invasive_cgroup_shrink), }; -- 2.39.3