From patchwork Wed Jun 30 20:22:10 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Zhansaya Bagdauletkyzy X-Patchwork-Id: 12352811 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 97143C11F66 for ; Wed, 30 Jun 2021 20:22:16 +0000 (UTC) Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by mail.kernel.org (Postfix) with ESMTP id 335AC61456 for ; Wed, 30 Jun 2021 20:22:16 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 335AC61456 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=owner-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix) id A86E28D01C6; Wed, 30 Jun 2021 16:22:15 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id A5EA18D01A2; Wed, 30 Jun 2021 16:22:15 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 8D9298D01C6; Wed, 30 Jun 2021 16:22:15 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from forelay.hostedemail.com (smtprelay0173.hostedemail.com [216.40.44.173]) by kanga.kvack.org (Postfix) with ESMTP id 610D98D01A2 for ; Wed, 30 Jun 2021 16:22:15 -0400 (EDT) Received: from smtpin07.hostedemail.com (10.5.19.251.rfc1918.com [10.5.19.251]) by forelay01.hostedemail.com (Postfix) with ESMTP id 2B1F1180E97EC for ; Wed, 30 Jun 2021 20:22:15 +0000 (UTC) X-FDA: 78311512230.07.5196FCF Received: from mail-lf1-f52.google.com (mail-lf1-f52.google.com [209.85.167.52]) by imf18.hostedemail.com (Postfix) with ESMTP id D6A90400208D for ; Wed, 30 Jun 2021 20:22:14 +0000 (UTC) Received: by mail-lf1-f52.google.com with SMTP id d16so7453049lfn.3 for ; Wed, 30 Jun 2021 13:22:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to; bh=Bafw2Td/NqqAc0fHfkThw0BzlPCmSFgxlKxT67zoUP8=; b=de3d/q9MonRWnoi4b8qsmCb1aI6tR2ZDZbQhYxyX3o68Tm/Rb+QOYeQQPskCIulG87 Md3CZqWowXb5NLzUXsZcjALe6W+to4MDz5RnYYUypt2XPaJIH2ApBb7UQq2XQiQt4sIa JyGxtdTxqTyfATbWuMXDNt9VX5gQTtj1QeJjuI1BIetfK5bY6FkUD8dM8xBWQVXood7x cZ8aK8AChs+KP6fBd0eYYq/OhB5A14socDNjQWBbnfwGLB7b1Xi+vUUVJhBsF9fyv+7E 32ZPPUNEYB3jS3R5wKdQMufiyL7NgVULTKDBkaetlQLKgS1cN+5324GnHrc5oEVGOmW3 T5UQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=Bafw2Td/NqqAc0fHfkThw0BzlPCmSFgxlKxT67zoUP8=; b=L3hoDw4u509a42bc98YrZfXJN7s6bykVzjhDzXrNsBborOBgQNAbGAYmDJUWRVUIOn +oWrLCgDKWafjcVqeYcE2rnAKPZ+fHjZMa7oElCNaGVcETDjOCJXZg4OF0M98UB6VhBm Y3MpU6hdvKLgVu/QtCUNdUxxns9p1o9ErV3E5QAF8YpC0259YNe3eQ/7Ycplh/RTfhY8 dowF8AUTK2hHi/XhVvup51tVAgGfYdPRY7sNaPr405oZSYr110fwwlJ9IzMvWpuWfOk8 DcY1SUOVYWmStjiBupAnYxI6eGH575PrnsvApd0LLU9MoGiioMJqnkqXKQOHi1For05a bx1A== X-Gm-Message-State: AOAM531fjWlUa+B7Y7W3yWSV8PIu9g39snQNWbZvH935Py6nwfHsRdT8 hIYbbj9un4dsmhqEsgEAs6c= X-Google-Smtp-Source: ABdhPJzyqxBcfQ2UANOXmwB6vU7b3w1dayd+KHNBHyCMBsF0SCQo5uBtGyjE1EFtNfkA+MEgvEwRsA== X-Received: by 2002:a19:ac41:: with SMTP id r1mr2027338lfc.502.1625084533318; Wed, 30 Jun 2021 13:22:13 -0700 (PDT) Received: from asus ([37.151.208.206]) by smtp.gmail.com with ESMTPSA id m1sm2303312ljb.138.2021.06.30.13.22.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Jun 2021 13:22:12 -0700 (PDT) Date: Thu, 1 Jul 2021 02:22:10 +0600 From: Zhansaya Bagdauletkyzy To: shuah@kernel.org, akpm@linux-foundation.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-mm@kvack.org, tyhicks@linux.microsoft.com, pasha.tatashin@soleen.com Subject: [PATCH 1/1] selftests: vm: add KSM tests Message-ID: <257653e0e584f6b3c1fefb069792a76fc95a9c29.1625083828.git.zhansayabagdaulet@gmail.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: X-Rspamd-Server: rspam01 X-Rspamd-Queue-Id: D6A90400208D Authentication-Results: imf18.hostedemail.com; dkim=pass header.d=gmail.com header.s=20161025 header.b="de3d/q9M"; spf=pass (imf18.hostedemail.com: domain of zhansayabagdaulet@gmail.com designates 209.85.167.52 as permitted sender) smtp.mailfrom=zhansayabagdaulet@gmail.com; dmarc=pass (policy=none) header.from=gmail.com X-Stat-Signature: 645iwb4rg94sncof87tqxk6qu1m7ffac X-HE-Tag: 1625084534-922520 X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: Introduce selftests to validate the functionality of KSM. The function check_ksm_merge() tests the MADV_MERGEABLE advice of madvise syscall. The test is run on private anonymous pages that have read and write access. Since some KSM tunables are modified, their starting values are saved and restored after testing. At the start, run is set to 2 to ensure that only test pages will be merged (we assume that no applications make madvise syscalls in the background). If KSM config not enabled, then the test is skipped. Signed-off-by: Zhansaya Bagdauletkyzy --- tools/testing/selftests/vm/.gitignore | 1 + tools/testing/selftests/vm/Makefile | 1 + tools/testing/selftests/vm/ksm_tests.c | 289 ++++++++++++++++++++++ tools/testing/selftests/vm/run_vmtests.sh | 16 ++ 4 files changed, 307 insertions(+) create mode 100644 tools/testing/selftests/vm/ksm_tests.c diff --git a/tools/testing/selftests/vm/.gitignore b/tools/testing/selftests/vm/.gitignore index 1f651e85ed60..047b27dd7746 100644 --- a/tools/testing/selftests/vm/.gitignore +++ b/tools/testing/selftests/vm/.gitignore @@ -23,3 +23,4 @@ write_to_hugetlbfs hmm-tests local_config.* split_huge_page_test +ksm_tests diff --git a/tools/testing/selftests/vm/Makefile b/tools/testing/selftests/vm/Makefile index 73e1cc96d7c2..230d1af82f22 100644 --- a/tools/testing/selftests/vm/Makefile +++ b/tools/testing/selftests/vm/Makefile @@ -43,6 +43,7 @@ TEST_GEN_FILES += thuge-gen TEST_GEN_FILES += transhuge-stress TEST_GEN_FILES += userfaultfd TEST_GEN_FILES += split_huge_page_test +TEST_GEN_FILES += ksm_tests ifeq ($(MACHINE),x86_64) CAN_BUILD_I386 := $(shell ./../x86/check_cc.sh $(CC) ../x86/trivial_32bit_program.c -m32) diff --git a/tools/testing/selftests/vm/ksm_tests.c b/tools/testing/selftests/vm/ksm_tests.c new file mode 100644 index 000000000000..f283ba83adf5 --- /dev/null +++ b/tools/testing/selftests/vm/ksm_tests.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include "../kselftest.h" + +#define KSM_SYSFS_PATH "/sys/kernel/mm/ksm/" +#define KSM_SCAN_LIMIT_SEC_DEFAULT 120 +#define KSM_PAGE_COUNT_DEFAULT 10l +#define KSM_FP(s) (KSM_SYSFS_PATH s) +#define KSM_PROT_STR_DEFAULT "rw" + +struct ksm_sysfs { + unsigned long max_page_sharing; + unsigned long merge_across_nodes; + unsigned long pages_to_scan; + unsigned long run; + unsigned long sleep_millisecs; + unsigned long stable_node_chains_prune_millisecs; + unsigned long use_zero_pages; +}; + +static int ksm_write_sysfs(const char *file_path, unsigned long val) +{ + FILE *f = fopen(file_path, "w"); + + if (!f) { + fprintf(stderr, "f %s\n", file_path); + perror("fopen"); + return 1; + } + if (fprintf(f, "%lu", val) < 0) { + perror("fprintf"); + return 1; + } + fclose(f); + + return 0; +} + +static int ksm_read_sysfs(const char *file_path, unsigned long *val) +{ + FILE *f = fopen(file_path, "r"); + + if (!f) { + fprintf(stderr, "f %s\n", file_path); + perror("fopen"); + return 1; + } + if (fscanf(f, "%lu", val) != 1) { + perror("fscanf"); + return 1; + } + fclose(f); + + return 0; +} + +static int str_to_prot(char *prot_str) +{ + int prot = 0; + + if ((strchr(prot_str, 'r')) != NULL) + prot |= PROT_READ; + if ((strchr(prot_str, 'w')) != NULL) + prot |= PROT_WRITE; + if ((strchr(prot_str, 'x')) != NULL) + prot |= PROT_EXEC; + + return prot; +} + +static void print_help(void) +{ + printf("usage: ksm_tests [-h] [-a prot] [-p page_count] [-l timeout]\n"); + printf(" -a: specify the access protections of pages.\n" + " must be of the form [rwx].\n" + " Default: %s\n", KSM_PROT_STR_DEFAULT); + printf(" -p: specify the number of pages to test.\n" + " Default: %ld\n", KSM_PAGE_COUNT_DEFAULT); + printf(" -l: limit the maximum running time (in seconds) for a test.\n" + " Default: %d seconds\n", KSM_SCAN_LIMIT_SEC_DEFAULT); + + exit(0); +} + +static bool assert_ksm_pages_count(long dupl_page_count) +{ + unsigned long max_page_sharing, pages_sharing, pages_shared; + + if (ksm_read_sysfs(KSM_FP("pages_shared"), &pages_shared) || + ksm_read_sysfs(KSM_FP("pages_sharing"), &pages_sharing) || + ksm_read_sysfs(KSM_FP("max_page_sharing"), &max_page_sharing)) + return false; + + /* + * Since there must be at least 2 pages for merging and 1 page can be + * shared with the limited amount of pages (max_page_sharing), sometimes + * there are 'leftover' pages that cannot be merged. For example, if there + * are 11 pages with max_page_sharing = 10, then only 10 pages will be + * merged and the 11th page won't be affected. As a result, when the number + * of duplicate pages is divided by max_page_sharing and the remainder is 1, + * pages_shared and pages_sharing values will be equal between dupl_page_count + * and dupl_page_count - 1. + */ + if (dupl_page_count % max_page_sharing == 1 || + dupl_page_count % max_page_sharing == 0) { + if (pages_shared == dupl_page_count / max_page_sharing && + pages_sharing == pages_shared * (max_page_sharing - 1)) + return true; + else + return false; + } + + if (pages_shared == dupl_page_count / max_page_sharing + 1 && + pages_sharing == dupl_page_count - pages_shared) + return true; + + return false; +} + +static int ksm_save_def(struct ksm_sysfs *ksm_sysfs) +{ + if (ksm_read_sysfs(KSM_FP("max_page_sharing"), &ksm_sysfs->max_page_sharing) || + ksm_read_sysfs(KSM_FP("merge_across_nodes"), &ksm_sysfs->merge_across_nodes) || + ksm_read_sysfs(KSM_FP("sleep_millisecs"), &ksm_sysfs->sleep_millisecs) || + ksm_read_sysfs(KSM_FP("pages_to_scan"), &ksm_sysfs->pages_to_scan) || + ksm_read_sysfs(KSM_FP("run"), &ksm_sysfs->run) || + ksm_read_sysfs(KSM_FP("stable_node_chains_prune_millisecs"), + &ksm_sysfs->stable_node_chains_prune_millisecs) || + ksm_read_sysfs(KSM_FP("use_zero_pages"), &ksm_sysfs->use_zero_pages)) + return 1; + + return 0; +} + +static int ksm_restore(struct ksm_sysfs *ksm_sysfs) +{ + if (ksm_write_sysfs(KSM_FP("max_page_sharing"), ksm_sysfs->max_page_sharing) || + ksm_write_sysfs(KSM_FP("merge_across_nodes"), ksm_sysfs->merge_across_nodes) || + ksm_write_sysfs(KSM_FP("pages_to_scan"), ksm_sysfs->pages_to_scan) || + ksm_write_sysfs(KSM_FP("run"), ksm_sysfs->run) || + ksm_write_sysfs(KSM_FP("sleep_millisecs"), ksm_sysfs->sleep_millisecs) || + ksm_write_sysfs(KSM_FP("stable_node_chains_prune_millisecs"), + ksm_sysfs->stable_node_chains_prune_millisecs) || + ksm_write_sysfs(KSM_FP("use_zero_pages"), ksm_sysfs->use_zero_pages)) + return 1; + + return 0; +} + +static int check_ksm_merge(int mapping, int prot, long page_count, int timeout) +{ + int ret = KSFT_FAIL; + size_t page_size = sysconf(_SC_PAGESIZE); + unsigned long cur_scan, init_scan; + void *map_area; + struct timespec start_time, cur_time; + + printf("Testing KSM MADV_MERGEABLE with %ld identical pages\n", page_count); + + if (ksm_write_sysfs(KSM_FP("sleep_millisecs"), 0) || + ksm_write_sysfs(KSM_FP("pages_to_scan"), page_count)) + return ret; + + if (ksm_read_sysfs(KSM_FP("full_scans"), &init_scan)) + return ret; + + cur_scan = init_scan; + + map_area = mmap(NULL, page_size * page_count, PROT_WRITE, mapping, -1, 0); + if (!map_area) { + perror("mmap"); + return ret; + } + + memset(map_area, '*', page_size * page_count); + + if (mprotect(map_area, page_size * page_count, prot)) { + perror("mprotect"); + goto err_out; + } + + if (madvise(map_area, page_size * page_count, MADV_MERGEABLE)) { + perror("madvise"); + goto err_out; + } + + if (ksm_write_sysfs(KSM_FP("run"), 1)) + goto err_out; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) { + perror("clock_gettime"); + goto err_out; + } + + /* Since merging occurs only after 2 scans, make sure to get at least 2 full scans */ + while (cur_scan < init_scan + 2) { + if (ksm_read_sysfs(KSM_FP("full_scans"), &cur_scan)) + goto err_out; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &cur_time)) { + perror("clock_gettime"); + goto err_out; + } + + if ((cur_time.tv_sec - start_time.tv_sec) > timeout) { + printf("Scan time limit exceeded\n"); + goto err_out; + } + } + + /* verify that the right number of pages are merged */ + if (assert_ksm_pages_count(page_count)) { + printf("OK\n"); + munmap(map_area, page_size * page_count); + return KSFT_PASS; + } + +err_out: + printf("Not OK\n"); + munmap(map_area, page_size * page_count); + return KSFT_FAIL; +} + +int main(int argc, char *argv[]) +{ + int ret, opt; + struct ksm_sysfs ksm_sysfs_old; + long page_count = KSM_PAGE_COUNT_DEFAULT; + int ksm_scan_limit_sec = KSM_SCAN_LIMIT_SEC_DEFAULT; + int prot = 0; + + if (access(KSM_SYSFS_PATH, F_OK)) { + printf("Config KSM not enabled\n"); + return KSFT_SKIP; + } + + while ((opt = getopt(argc, argv, "ha:p:l:")) != -1) { + switch (opt) { + case 'a': + prot = str_to_prot(optarg); + break; + case 'p': + page_count = atol(optarg); + if (page_count <= 0) { + printf("The number of pages must be greater than 0\n"); + return KSFT_FAIL; + } + break; + case 'l': + ksm_scan_limit_sec = atoi(optarg); + if (ksm_scan_limit_sec <= 0) { + printf("Timeout value must be greater than 0\n"); + return KSFT_FAIL; + } + break; + case 'h': + print_help(); + break; + default: + return KSFT_FAIL; + } + } + + if (prot == 0) + prot = str_to_prot(KSM_PROT_STR_DEFAULT); + + if (ksm_save_def(&ksm_sysfs_old)) { + printf("Cannot save default tunables\n"); + return KSFT_FAIL; + } + + /* unmerge all pages if there are any */ + if (ksm_write_sysfs(KSM_FP("run"), 2)) + return KSFT_FAIL; + + ret = check_ksm_merge(MAP_PRIVATE | MAP_ANONYMOUS, prot, page_count, ksm_scan_limit_sec); + + if (ksm_restore(&ksm_sysfs_old)) { + printf("Cannot restore default tunables\n"); + return KSFT_FAIL; + } + + return ret; +} diff --git a/tools/testing/selftests/vm/run_vmtests.sh b/tools/testing/selftests/vm/run_vmtests.sh index e953f3cd9664..88772d8a6616 100755 --- a/tools/testing/selftests/vm/run_vmtests.sh +++ b/tools/testing/selftests/vm/run_vmtests.sh @@ -346,4 +346,20 @@ else exitcode=1 fi +echo "------------------------------------" +echo "running KSM tests" +echo "------------------------------------" +./ksm_tests +ret_val=$? + +if [ $ret_val -eq 0 ]; then + echo "[PASS]" +elif [ $ret_val -eq $ksft_skip ]; then + echo "[SKIP]" + exitcode=$ksft_skip +else + echo "[FAIL]" + exitcode=1 +fi + exit $exitcode