From nobody Fri Jun 12 11:29:15 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 758CD3B19A1; Fri, 15 May 2026 08:49:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778834946; cv=none; b=rNOz9H4GehG2vi2FGPcAAeYJGHm2cN9ygRLg70AKdzK6K9rBkWnbsB48VDtUwFyWnVlO4aidZdXZvIJDflQnFIUQuHg5xUhVtsTE4GWQIxNDaMmTDSULPvi3MubR8eFX9FNS2LXpMKkWn5PxDXt+cPKRwII0XGcXQR/RQPL8mAE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778834946; c=relaxed/simple; bh=sHKXDPRz5CC+ddYd15V+FzHYq1MYmq/fvhP1ENudmq8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=I8Rxf/JzUfElXQkbV5Xqbmcik0Kh3absnDwZpD5BwDpBxkBaeY4HCGZuKNPac2jh06ZscwGfsJRu1WhcXOBZtmHxL1fTiCFO/ZwlxCGCfDmlJQDW7faTwdsmYmSFLFnsgJiJlF59ygTeUcrmYLpe79GkfZ60YHLqsgweLSE9Jlc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=YpJMGVom; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="YpJMGVom" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 1C7BD25E3; Fri, 15 May 2026 01:48:57 -0700 (PDT) Received: from a081061.blr.arm.com (a081061.arm.com [10.164.19.82]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 7B1C03F7B4; Fri, 15 May 2026 01:48:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1778834942; bh=sHKXDPRz5CC+ddYd15V+FzHYq1MYmq/fvhP1ENudmq8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YpJMGVomdnqInuJfFdSB9rL+Am5Ax1dJJlE4+CADOgh2t0XmMiHzzl4K/B6MtFJOs aHT/p7gZpgLwDjmsiBkW2L1zxg2HhvSUPYJu7X058VQ2HMELRcLXs0ViPTwJx+iYBu KFgR9KpTgipQG0Ub2HRowCy2T2pgUuu/7lEwibJM= From: Sarthak Sharma To: Andrew Morton , David Hildenbrand Cc: Jason Gunthorpe , John Hubbard , Peter Xu , Lorenzo Stoakes , "Liam R . Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko , Shuah Khan , linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Sarthak Sharma Subject: [PATCH 1/2] tools/mm: add a standalone GUP microbenchmark Date: Fri, 15 May 2026 14:18:39 +0530 Message-ID: <20260515084840.174652-2-sarthak.sharma@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515084840.174652-1-sarthak.sharma@arm.com> References: <20260515084840.174652-1-sarthak.sharma@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a command-line tool for benchmarking get_user_pages fast-path (GUP_FAST), pin_user_pages fast-path (PIN_FAST), and pin_user_pages longterm (PIN_LONGTERM) via the CONFIG_GUP_TEST debugfs interface. When invoked without arguments, gup_bench runs the same matrix of configurations as run_gup_matrix() in run_vmtests.sh: all three GUP commands across read/write, private/shared mappings, and a range of page counts, with THP on/off for regular mappings and hugetlb for huge page mappings. This tool is a mix of reused and new logic. The mapping/setup path comes from selftests/mm/gup_test.c, while the default benchmark matrix matches run_gup_matrix() in run_vmtests.sh. The standalone CLI and tools/mm integration are added here so tools/mm does not depend on kselftest. Add gup_bench to BUILD_TARGETS and INSTALL_TARGETS in tools/mm/Makefile, and ignore the resulting binary in tools/mm/.gitignore. While here, also add the missing thp_swap_allocator_test entry to .gitignore. Add tools/mm/gup_bench.c to the GUP entry in MAINTAINERS. Suggested-by: David Hildenbrand (Arm) Signed-off-by: Sarthak Sharma --- MAINTAINERS | 1 + tools/mm/.gitignore | 2 + tools/mm/Makefile | 6 +- tools/mm/gup_bench.c | 491 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 497 insertions(+), 3 deletions(-) create mode 100644 tools/mm/gup_bench.c diff --git a/MAINTAINERS b/MAINTAINERS index 98d0a7a1c689..c91165b9280e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16830,6 +16830,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/gi= t/akpm/mm F: mm/gup.c F: mm/gup_test.c F: mm/gup_test.h +F: tools/mm/gup_bench.c F: tools/testing/selftests/mm/gup_longterm.c F: tools/testing/selftests/mm/gup_test.c =20 diff --git a/tools/mm/.gitignore b/tools/mm/.gitignore index 922879f93fc8..154d740be02e 100644 --- a/tools/mm/.gitignore +++ b/tools/mm/.gitignore @@ -2,3 +2,5 @@ slabinfo page-types page_owner_sort +thp_swap_allocator_test +gup_bench diff --git a/tools/mm/Makefile b/tools/mm/Makefile index f5725b5c23aa..8e4db797a17a 100644 --- a/tools/mm/Makefile +++ b/tools/mm/Makefile @@ -3,13 +3,13 @@ # include ../scripts/Makefile.include =20 -BUILD_TARGETS=3Dpage-types slabinfo page_owner_sort thp_swap_allocator_test +BUILD_TARGETS=3Dpage-types slabinfo page_owner_sort thp_swap_allocator_tes= t gup_bench INSTALL_TARGETS =3D $(BUILD_TARGETS) thpmaps =20 LIB_DIR =3D ../lib/api LIBS =3D $(LIB_DIR)/libapi.a =20 -CFLAGS +=3D -Wall -Wextra -I../lib/ -pthread +CFLAGS +=3D -Wall -Wextra -I../lib/ -I../.. -pthread LDFLAGS +=3D $(LIBS) -pthread =20 all: $(BUILD_TARGETS) @@ -23,7 +23,7 @@ $(LIBS): $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) =20 clean: - $(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test + $(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test gup_ben= ch make -C $(LIB_DIR) clean =20 sbindir ?=3D /usr/sbin diff --git a/tools/mm/gup_bench.c b/tools/mm/gup_bench.c new file mode 100644 index 000000000000..2806ee0d7453 --- /dev/null +++ b/tools/mm/gup_bench.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microbenchmark for get_user_pages (GUP) kernel interfaces. + * + * Exercises GUP_FAST_BENCHMARK, PIN_FAST_BENCHMARK, and + * PIN_LONGTERM_BENCHMARK via the CONFIG_GUP_TEST debugfs interface. + * + * Example use: + * # Run the full matrix (all commands, access modes, page counts): + * ./gup_bench + * + * # Single run: pin_user_pages_fast, 512 pages, write access, hugetlb: + * ./gup_bench -a -n 512 -w -H + * + * Requires CONFIG_GUP_TEST=3Dy and debugfs mounted at /sys/kernel/debug. + * Must be run as root. + */ + +#define __SANE_USERSPACE_TYPES__ // Use ll64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MB (1UL << 20) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +/* Just the flags we need, copied from the kernel internals. */ +#define FOLL_WRITE 0x01 /* check pte is writable */ + +#define GUP_TEST_FILE "/sys/kernel/debug/gup_test" + +/* + * Local HugeTLB setup helpers for gup_bench. + * + * These helpers were copied from tools/testing/selftests/mm/ and adjusted= to + * remove the ksft formatting. Keep this copy local so tools/mm does not + * depend on ksft output behavior. + */ + +static unsigned int psize(void) +{ + static unsigned int __page_size; + + if (!__page_size) + __page_size =3D sysconf(_SC_PAGESIZE); + return __page_size; +} + +static unsigned long default_huge_page_size(void) +{ + FILE *f =3D fopen("/proc/meminfo", "r"); + unsigned long hpage_size =3D 0; + char buf[256]; + + if (!f) + return 0; + while (fgets(buf, sizeof(buf), f)) { + if (sscanf(buf, "Hugepagesize: %lu kB", &hpage_size) =3D=3D 1) + break; + } + fclose(f); + hpage_size <<=3D 10; + return hpage_size; +} + +static void hugetlb_sysfs_path(char *buf, size_t buflen, + unsigned long size, const char *attr) +{ + snprintf(buf, buflen, "/sys/kernel/mm/hugepages/hugepages-%lukB/%s", + size / 1024, attr); +} + +static unsigned long hugetlb_read_num(const char *path) +{ + char buf[32]; + FILE *f =3D fopen(path, "r"); + unsigned long val =3D 0; + + if (!f) + return 0; + if (fgets(buf, sizeof(buf), f)) + val =3D strtoul(buf, NULL, 10); + fclose(f); + return val; +} + +static void hugetlb_write_num(const char *path, unsigned long num) +{ + FILE *f =3D fopen(path, "w"); + + if (!f) + return; + fprintf(f, "%lu\n", num); + fclose(f); +} + +static unsigned long hugetlb_nr_pages(unsigned long size) +{ + char path[PATH_MAX]; + + hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages"); + return hugetlb_read_num(path); +} + +static void hugetlb_set_nr_pages(unsigned long size, unsigned long nr) +{ + char path[PATH_MAX]; + + hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages"); + hugetlb_write_num(path, nr); +} + +static unsigned long hugetlb_free_pages(unsigned long size) +{ + char path[PATH_MAX]; + + hugetlb_sysfs_path(path, sizeof(path), size, "free_hugepages"); + return hugetlb_read_num(path); +} + +/* Saved pool size to restore on exit */ +static unsigned long hugetlb_saved_nr; +static unsigned long hugetlb_saved_size; + +static void hugetlb_restore_atexit(void) +{ + if (hugetlb_saved_size) + hugetlb_set_nr_pages(hugetlb_saved_size, hugetlb_saved_nr); +} + +static bool __hugetlb_setup(unsigned long size, unsigned long nr) +{ + unsigned long free =3D hugetlb_free_pages(size); + unsigned long total =3D hugetlb_nr_pages(size); + + if (free >=3D nr) + return true; + + hugetlb_set_nr_pages(size, total + (nr - free)); + + return hugetlb_free_pages(size) >=3D nr; +} + +static bool hugetlb_setup_default(unsigned long nr) +{ + unsigned long hsize =3D default_huge_page_size(); + + if (!hsize) + return false; + + /* Save current pool so we can restore it on exit (only on first call) */ + if (!hugetlb_saved_size) { + hugetlb_saved_size =3D hsize; + hugetlb_saved_nr =3D hugetlb_nr_pages(hsize); + atexit(hugetlb_restore_atexit); + } + + return __hugetlb_setup(hsize, nr); +} + +static unsigned long cmd; +static const char *bench_label; +static int gup_fd, repeats =3D 1; +static unsigned long size =3D 128 * MB; +static atomic_int bench_error; +/* Serialize prints */ +static pthread_mutex_t print_mutex =3D PTHREAD_MUTEX_INITIALIZER; + +static const unsigned long bench_cmds[] =3D { + GUP_FAST_BENCHMARK, + PIN_FAST_BENCHMARK, + PIN_LONGTERM_BENCHMARK, +}; +static const int bench_thp_modes[] =3D { 1, 0 }; /* on, off */ +static const int bench_nr_pages_list[] =3D { 1, 512, 123, -1 }; + +static const char *cmd_to_str(unsigned long cmd) +{ + switch (cmd) { + case GUP_FAST_BENCHMARK: + return "GUP_FAST_BENCHMARK"; + case PIN_FAST_BENCHMARK: + return "PIN_FAST_BENCHMARK"; + case PIN_LONGTERM_BENCHMARK: + return "PIN_LONGTERM_BENCHMARK"; + } + return "Unknown command"; +} + +struct bench_run { + unsigned long cmd; + int thp; /* -1: default, 0: off, 1: on */ + bool hugetlb; + bool write; + bool shared; + int nr_pages; /* -1 means all pages (size / psize()) */ + unsigned long size; + char *file; + int nthreads; + unsigned int gup_flags; +}; + +void *gup_thread(void *data) +{ + struct gup_test gup =3D *(struct gup_test *)data; + int i, status; + + for (i =3D 0; i < repeats; i++) { + gup.size =3D size; + status =3D ioctl(gup_fd, cmd, &gup); + if (status) { + bench_error =3D 1; + break; + } + + pthread_mutex_lock(&print_mutex); + printf("%s time: get:%lld put:%lld us", + bench_label, gup.get_delta_usec, + gup.put_delta_usec); + if (gup.size !=3D size) + printf(", truncated (size: %lld)", gup.size); + printf("\n"); + pthread_mutex_unlock(&print_mutex); + } + + return NULL; +} + +static int run_bench(struct bench_run *run) +{ + struct gup_test gup =3D { 0 }; + int zero_fd, i, ret, started_threads =3D 0; + int flags =3D MAP_PRIVATE; + pthread_t *tid; + char label[128]; + char *p; + + /* Set globals consumed by gup_thread */ + cmd =3D run->cmd; + size =3D run->size; + bench_error =3D 0; + + if (run->hugetlb) { + unsigned long hp_size =3D default_huge_page_size(); + + if (!hp_size) { + fprintf(stderr, "Could not determine huge page size\n"); + return 1; + } + size =3D (size + hp_size - 1) & ~(hp_size - 1); + if (!hugetlb_setup_default(size / hp_size)) { + fprintf(stderr, "Not enough huge pages\n"); + return 1; + } + flags |=3D (MAP_HUGETLB | MAP_ANONYMOUS); + } + + if (run->shared) { + flags &=3D ~MAP_PRIVATE; + flags |=3D MAP_SHARED; + } + + gup.nr_pages_per_call =3D run->nr_pages < 0 ? size / psize() : + (unsigned long)run->nr_pages; + + gup.gup_flags =3D run->gup_flags; + if (run->write) + gup.gup_flags |=3D FOLL_WRITE; + + snprintf(label, sizeof(label), "%s (nr_pages=3D%-4u %s %s %s %s)", + cmd_to_str(run->cmd), + gup.nr_pages_per_call, + run->write ? "write" : "read", + run->shared ? "shared" : "private", + run->hugetlb ? "hugetlb=3Don" : "hugetlb=3Doff", + run->hugetlb ? "thp=3Doff" : + (run->thp =3D=3D 1 ? "thp=3Don" : + (run->thp =3D=3D 0 ? "thp=3Doff" : "thp=3Ddefault"))); + bench_label =3D label; + + zero_fd =3D open(run->file, O_RDWR); + if (zero_fd < 0) { + fprintf(stderr, "Unable to open %s: %s\n", run->file, strerror(errno)); + return 1; + } + + p =3D mmap(NULL, size, PROT_READ | PROT_WRITE, flags, zero_fd, 0); + close(zero_fd); + if (p =3D=3D MAP_FAILED) { + fprintf(stderr, "mmap: %s\n", strerror(errno)); + return 1; + } + gup.addr =3D (unsigned long)p; + + if (run->thp =3D=3D 1) + madvise(p, size, MADV_HUGEPAGE); + else if (run->thp =3D=3D 0) + madvise(p, size, MADV_NOHUGEPAGE); + + /* Fault them in here, from user space. */ + for (; (unsigned long)p < gup.addr + size; p +=3D psize()) + p[0] =3D 0; + + tid =3D malloc(sizeof(pthread_t) * run->nthreads); + assert(tid); + for (i =3D 0; i < run->nthreads; i++) { + ret =3D pthread_create(&tid[i], NULL, gup_thread, &gup); + if (ret) { + fprintf(stderr, "pthread_create failed: %s\n", strerror(ret)); + bench_error =3D 1; + break; + } + started_threads++; + } + for (i =3D 0; i < started_threads; i++) { + ret =3D pthread_join(tid[i], NULL); + if (ret) { + fprintf(stderr, "pthread_join failed: %s\n", strerror(ret)); + bench_error =3D 1; + } + } + + free(tid); + munmap((void *)gup.addr, size); + + return bench_error ? 1 : 0; +} + +static int run_matrix(void) +{ + unsigned int c, t, w, s, n; + int ret =3D 0; + + for (c =3D 0; c < ARRAY_SIZE(bench_cmds); c++) { + for (w =3D 0; w <=3D 1; w++) { + for (s =3D 0; s <=3D 1; s++) { + for (t =3D 0; t < ARRAY_SIZE(bench_thp_modes); t++) { + for (n =3D 0; n < ARRAY_SIZE(bench_nr_pages_list); n++) { + struct bench_run run =3D { + .cmd =3D bench_cmds[c], + .thp =3D bench_thp_modes[t], + .hugetlb =3D false, + .write =3D w, + .shared =3D s, + .nr_pages =3D bench_nr_pages_list[n], + .size =3D 128 * MB, + .file =3D "/dev/zero", + .nthreads =3D 1, + }; + ret |=3D run_bench(&run); + } + } + /* hugetlb: 256M to match run_gup_matrix() in run_vmtests.sh */ + for (n =3D 0; n < ARRAY_SIZE(bench_nr_pages_list); n++) { + struct bench_run run =3D { + .cmd =3D bench_cmds[c], + .thp =3D -1, + .hugetlb =3D true, + .write =3D w, + .shared =3D s, + .nr_pages =3D bench_nr_pages_list[n], + .size =3D 256 * MB, + .file =3D "/dev/zero", + .nthreads =3D 1, + }; + ret |=3D run_bench(&run); + } + } + } + } + return ret; +} + +int main(int argc, char **argv) +{ + struct bench_run run =3D { + .cmd =3D GUP_FAST_BENCHMARK, + .thp =3D -1, + .hugetlb =3D false, + .write =3D true, + .shared =3D false, + .nr_pages =3D 1, + .size =3D 128 * MB, + .file =3D "/dev/zero", + .nthreads =3D 1, + }; + int opt, result; + + while ((opt =3D getopt(argc, argv, "m:r:n:F:f:aj:tTLuwWSH")) !=3D -1) { + switch (opt) { + + /* Command selection */ + case 'u': + run.cmd =3D GUP_FAST_BENCHMARK; + break; + case 'a': + run.cmd =3D PIN_FAST_BENCHMARK; + break; + case 'L': + run.cmd =3D PIN_LONGTERM_BENCHMARK; + break; + + /* Memory type */ + case 'H': + run.hugetlb =3D true; + break; + case 't': + run.thp =3D 1; + break; + case 'T': + run.thp =3D 0; + break; + + /* Access mode */ + case 'w': + run.write =3D true; + break; + case 'W': + run.write =3D false; + break; + case 'S': + run.shared =3D true; + break; + + /* Mapping */ + case 'f': + run.file =3D optarg; + break; + + /* Sizing and iteration */ + case 'm': + run.size =3D atoi(optarg) * MB; + break; + case 'n': + run.nr_pages =3D atoi(optarg); + break; + case 'r': + repeats =3D atoi(optarg); + break; + case 'j': + run.nthreads =3D atoi(optarg); + break; + + /* Advanced */ + case 'F': + /* strtol, so you can pass flags in hex form */ + run.gup_flags =3D strtol(optarg, 0, 0); + break; + + default: + fprintf(stderr, "Wrong argument\n"); + exit(1); + } + } + + gup_fd =3D open(GUP_TEST_FILE, O_RDWR); + if (gup_fd =3D=3D -1) { + if (errno =3D=3D EACCES) { + fprintf(stderr, "Please run as root\n"); + } else if (errno =3D=3D ENOENT) { + if (opendir("/sys/kernel/debug") =3D=3D NULL) + fprintf(stderr, "Mount debugfs at /sys/kernel/debug\n"); + else + fprintf(stderr, "Check CONFIG_GUP_TEST in kernel config\n"); + } else { + fprintf(stderr, "Failed to open %s: %s\n", GUP_TEST_FILE, + strerror(errno)); + } + exit(1); + } + + result =3D (argc =3D=3D 1) ? run_matrix() : run_bench(&run); + close(gup_fd); + return result; +} --=20 2.39.5 From nobody Fri Jun 12 11:29:15 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id CD48340B6C3; Fri, 15 May 2026 08:49:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778834952; cv=none; b=YkthJyAjfRx1V03bXj6gK5AQtQFWnrPL/LzHQBu+PaH+hZiT7cHTdd1g8+FyHfAqlIDJcH7nE4LsfR9QA+X4x+P9XBmk534f9MgaZ8geE5PdA6BJwU7kDYtaK48RgNs+Z6xpxzvjUD+kNZhC7Xa6iwzcgT4ZlGwTKivmZw5L5aM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778834952; c=relaxed/simple; bh=oCtq2AZBnQJJnWw16nvThl6m2oj4VBTv7iHzzpyZ8iM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bjUGvAl5b+63EFRBTe5Q3cC5KO2N374fgPGpY2htA56JYiZs2nIyqFCzBzEYILiU6aoRlc3mCEwbOJlHdNEEyRVGbGOqwVkQuWvQjcAlAFaeOjhrlNlsngdG7A5ndZIy8S2dsG9XiuXul1aPyYdnB5XgTllRwKs4JHPcP5BwkOI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=GpCVi47f; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="GpCVi47f" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id AB31F26A4; Fri, 15 May 2026 01:49:04 -0700 (PDT) Received: from a081061.blr.arm.com (a081061.arm.com [10.164.19.82]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 154A83F7B4; Fri, 15 May 2026 01:49:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1778834949; bh=oCtq2AZBnQJJnWw16nvThl6m2oj4VBTv7iHzzpyZ8iM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GpCVi47fPt4AopC6BsmPj104ka1yJKkMcgsQqV/KO0PqkCnuIaGJ2K4zfRkhvR+m+ sqQXMVbSbUkiRVnwbUF47NyifwXBXEWlKRNgI4mndPVPwMZiHybsHKzY0KAQBaAYoj M0D+k21mKQ7qE1RGIMbIc4O2m4DMReGjQ3m8S4rs= From: Sarthak Sharma To: Andrew Morton , David Hildenbrand Cc: Jason Gunthorpe , John Hubbard , Peter Xu , Lorenzo Stoakes , "Liam R . Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko , Shuah Khan , linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Sarthak Sharma Subject: [PATCH 2/2] selftests/mm: rewrite gup_test as a standalone harness-based selftest Date: Fri, 15 May 2026 14:18:40 +0530 Message-ID: <20260515084840.174652-3-sarthak.sharma@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515084840.174652-1-sarthak.sharma@arm.com> References: <20260515084840.174652-1-sarthak.sharma@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Rewrite gup_test.c using kselftest_harness.h. The new test uses FIXTURE_VARIANT to cover seven configurations (private/shared, read/write, THP, hugetlb) and runs four test cases per variant (GUP_BASIC_TEST, PIN_BASIC_TEST, DUMP_USER_PAGES_TEST with get and pin), giving 28 TAP-reported cases in total without requiring any command-line arguments. Update run_vmtests.sh: remove run_gup_matrix() and the multiple flagged invocations of gup_test, replacing them with a single unconditional invocation. Benchmark functionality is handled by tools/mm/gup_bench introduced in the previous patch. Suggested-by: David Hildenbrand (Arm) Signed-off-by: Sarthak Sharma --- tools/testing/selftests/mm/gup_test.c | 404 ++++++++++------------ tools/testing/selftests/mm/run_vmtests.sh | 37 +- 2 files changed, 182 insertions(+), 259 deletions(-) diff --git a/tools/testing/selftests/mm/gup_test.c b/tools/testing/selftest= s/mm/gup_test.c index 3f841a96f870..c0e5e88d89ed 100644 --- a/tools/testing/selftests/mm/gup_test.c +++ b/tools/testing/selftests/mm/gup_test.c @@ -9,12 +9,11 @@ #include #include #include -#include -#include #include #include "kselftest.h" #include "vm_util.h" #include "hugepage_settings.h" +#include "kselftest_harness.h" =20 #define MB (1UL << 20) =20 @@ -23,253 +22,212 @@ =20 #define GUP_TEST_FILE "/sys/kernel/debug/gup_test" =20 -static unsigned long cmd =3D GUP_FAST_BENCHMARK; -static int gup_fd, repeats =3D 1; -static unsigned long size =3D 128 * MB; -/* Serialize prints */ -static pthread_mutex_t print_mutex =3D PTHREAD_MUTEX_INITIALIZER; +FIXTURE(gup_test) { + int gup_fd; + char *addr; + unsigned long size; +}; =20 -static char *cmd_to_str(unsigned long cmd) +FIXTURE_VARIANT(gup_test) { + bool thp; + bool hugetlb; + bool write; + bool shared; +}; + +FIXTURE_VARIANT_ADD(gup_test, private_write) { - switch (cmd) { - case GUP_FAST_BENCHMARK: - return "GUP_FAST_BENCHMARK"; - case PIN_FAST_BENCHMARK: - return "PIN_FAST_BENCHMARK"; - case PIN_LONGTERM_BENCHMARK: - return "PIN_LONGTERM_BENCHMARK"; - case GUP_BASIC_TEST: - return "GUP_BASIC_TEST"; - case PIN_BASIC_TEST: - return "PIN_BASIC_TEST"; - case DUMP_USER_PAGES_TEST: - return "DUMP_USER_PAGES_TEST"; - } - return "Unknown command"; -} + .thp =3D false, + .hugetlb =3D false, + .write =3D true, + .shared =3D false, +}; =20 -void *gup_thread(void *data) +FIXTURE_VARIANT_ADD(gup_test, private_readonly) { - struct gup_test gup =3D *(struct gup_test *)data; - int i, status; - - /* Only report timing information on the *_BENCHMARK commands: */ - if ((cmd =3D=3D PIN_FAST_BENCHMARK) || (cmd =3D=3D GUP_FAST_BENCHMARK) || - (cmd =3D=3D PIN_LONGTERM_BENCHMARK)) { - for (i =3D 0; i < repeats; i++) { - gup.size =3D size; - status =3D ioctl(gup_fd, cmd, &gup); - if (status) - break; - - pthread_mutex_lock(&print_mutex); - ksft_print_msg("%s: Time: get:%lld put:%lld us", - cmd_to_str(cmd), gup.get_delta_usec, - gup.put_delta_usec); - if (gup.size !=3D size) - ksft_print_msg(", truncated (size: %lld)", gup.size); - ksft_print_msg("\n"); - pthread_mutex_unlock(&print_mutex); - } - } else { - gup.size =3D size; - status =3D ioctl(gup_fd, cmd, &gup); - if (status) - goto return_; - - pthread_mutex_lock(&print_mutex); - ksft_print_msg("%s: done\n", cmd_to_str(cmd)); - if (gup.size !=3D size) - ksft_print_msg("Truncated (size: %lld)\n", gup.size); - pthread_mutex_unlock(&print_mutex); - } + .thp =3D false, + .hugetlb =3D false, + .write =3D false, + .shared =3D false, +}; =20 -return_: - ksft_test_result(!status, "ioctl status %d\n", status); - return NULL; -} +FIXTURE_VARIANT_ADD(gup_test, private_write_thp) +{ + .thp =3D true, + .hugetlb =3D false, + .write =3D true, + .shared =3D false, +}; =20 -int main(int argc, char **argv) +FIXTURE_VARIANT_ADD(gup_test, private_readonly_thp) { - struct gup_test gup =3D { 0 }; - int filed, i, opt, nr_pages =3D 1, thp =3D -1, write =3D 1, nthreads =3D = 1, ret; - int flags =3D MAP_PRIVATE; - char *file =3D "/dev/zero"; - bool hugetlb =3D false; - pthread_t *tid; - char *p; + .thp =3D true, + .hugetlb =3D false, + .write =3D false, + .shared =3D false, +}; =20 - while ((opt =3D getopt(argc, argv, "m:r:n:F:f:abcj:tTLUuwWSHpz")) !=3D -1= ) { - switch (opt) { - case 'a': - cmd =3D PIN_FAST_BENCHMARK; - break; - case 'b': - cmd =3D PIN_BASIC_TEST; - break; - case 'L': - cmd =3D PIN_LONGTERM_BENCHMARK; - break; - case 'c': - cmd =3D DUMP_USER_PAGES_TEST; - /* - * Dump page 0 (index 1). May be overridden later, by - * user's non-option arguments. - * - * .which_pages is zero-based, so that zero can mean "do - * nothing". - */ - gup.which_pages[0] =3D 1; - break; - case 'p': - /* works only with DUMP_USER_PAGES_TEST */ - gup.test_flags |=3D GUP_TEST_FLAG_DUMP_PAGES_USE_PIN; - break; - case 'F': - /* strtol, so you can pass flags in hex form */ - gup.gup_flags =3D strtol(optarg, 0, 0); - break; - case 'j': - nthreads =3D atoi(optarg); - break; - case 'm': - size =3D atoi(optarg) * MB; - break; - case 'r': - repeats =3D atoi(optarg); - break; - case 'n': - nr_pages =3D atoi(optarg); - if (nr_pages < 0) - nr_pages =3D size / psize(); - break; - case 't': - thp =3D 1; - break; - case 'T': - thp =3D 0; - break; - case 'U': - cmd =3D GUP_BASIC_TEST; - break; - case 'u': - cmd =3D GUP_FAST_BENCHMARK; - break; - case 'w': - write =3D 1; - break; - case 'W': - write =3D 0; - break; - case 'f': - file =3D optarg; - break; - case 'S': - flags &=3D ~MAP_PRIVATE; - flags |=3D MAP_SHARED; - break; - case 'H': - flags |=3D (MAP_HUGETLB | MAP_ANONYMOUS); - hugetlb =3D true; - break; - default: - ksft_exit_fail_msg("Wrong argument\n"); - } - } +FIXTURE_VARIANT_ADD(gup_test, private_write_hugetlb) +{ + .thp =3D false, + .hugetlb =3D true, + .write =3D true, + .shared =3D false, +}; =20 - if (optind < argc) { - int extra_arg_count =3D 0; - /* - * For example: - * - * ./gup_test -c 0 1 0x1001 - * - * ...to dump pages 0, 1, and 4097 - */ - - while ((optind < argc) && - (extra_arg_count < GUP_TEST_MAX_PAGES_TO_DUMP)) { - /* - * Do the 1-based indexing here, so that the user can - * use normal 0-based indexing on the command line. - */ - long page_index =3D strtol(argv[optind], 0, 0) + 1; - - gup.which_pages[extra_arg_count] =3D page_index; - extra_arg_count++; - optind++; - } - } +FIXTURE_VARIANT_ADD(gup_test, private_readonly_hugetlb) +{ + .thp =3D false, + .hugetlb =3D true, + .write =3D false, + .shared =3D false, +}; =20 - ksft_print_header(); +FIXTURE_VARIANT_ADD(gup_test, shared_write) +{ + .thp =3D false, + .hugetlb =3D false, + .write =3D true, + .shared =3D true, +}; + +FIXTURE_SETUP(gup_test) { + int mmap_flags =3D MAP_PRIVATE; + int zero_fd; + char *p; =20 - if (hugetlb) { + self->size =3D 128 * MB; + + /* Check for hugetlb */ + if (variant->hugetlb) { unsigned long hp_size =3D default_huge_page_size(); =20 if (!hp_size) - ksft_exit_skip("HugeTLB is unavailable\n"); + SKIP(return, "HugeTLB not available\n"); + + self->size =3D (self->size + hp_size - 1) & ~(hp_size - 1); + if (!hugetlb_setup_default(self->size / hp_size)) + SKIP(return, "Not enough huge pages\n"); =20 - size =3D (size + hp_size - 1) & ~(hp_size - 1); - if (!hugetlb_setup_default(size / hp_size)) - ksft_exit_skip("Not enough huge pages\n"); + mmap_flags |=3D (MAP_HUGETLB | MAP_ANONYMOUS); } =20 - ksft_set_plan(nthreads); + /* zero_fd has to be >=3D0. Already checked in main() */ + zero_fd =3D open("/dev/zero", O_RDWR); + ASSERT_GE(zero_fd, 0); =20 - filed =3D open(file, O_RDWR|O_CREAT, 0664); - if (filed < 0) - ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno)); + /* gup_fd has to be >=3D0. Already checked in main() */ + self->gup_fd =3D open(GUP_TEST_FILE, O_RDWR); + ASSERT_GE(self->gup_fd, 0); + + if (variant->shared) + mmap_flags =3D (mmap_flags & ~MAP_PRIVATE) | MAP_SHARED; + + self->addr =3D mmap(NULL, self->size, PROT_READ | PROT_WRITE, + mmap_flags, zero_fd, 0); + close(zero_fd); + ASSERT_NE(self->addr, MAP_FAILED); + + if (variant->thp) + madvise(self->addr, self->size, MADV_HUGEPAGE); + + for (p =3D self->addr; (unsigned long)p < (unsigned long)self->addr + + self->size; p +=3D psize()) + p[0] =3D 0; +} + +FIXTURE_TEARDOWN(gup_test) { + munmap(self->addr, self->size); + close(self->gup_fd); +} + +TEST_F(gup_test, get_user_pages) { + /* tests get_user_pages */ + struct gup_test gup =3D { 0 }; + + gup.addr =3D (unsigned long)self->addr; + gup.size =3D self->size; + gup.nr_pages_per_call =3D 1; =20 - gup.nr_pages_per_call =3D nr_pages; - if (write) + if (variant->write) gup.gup_flags |=3D FOLL_WRITE; =20 - gup_fd =3D open(GUP_TEST_FILE, O_RDWR); - if (gup_fd =3D=3D -1) { - switch (errno) { - case EACCES: - if (getuid()) - ksft_print_msg("Please run this test as root\n"); - break; - case ENOENT: - if (opendir("/sys/kernel/debug") =3D=3D NULL) - ksft_print_msg("mount debugfs at /sys/kernel/debug\n"); - ksft_print_msg("check if CONFIG_GUP_TEST is enabled in kernel config\n"= ); - break; - default: - ksft_print_msg("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno= )); - break; - } - ksft_test_result_skip("Please run this test as root\n"); - ksft_exit_pass(); - } + ASSERT_EQ(ioctl(self->gup_fd, GUP_BASIC_TEST, &gup), 0); +} =20 - p =3D mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0); - if (p =3D=3D MAP_FAILED) - ksft_exit_fail_msg("mmap: %s\n", strerror(errno)); - gup.addr =3D (unsigned long)p; +TEST_F(gup_test, pin_user_pages) { + /* tests pin_user_pages */ + struct gup_test gup =3D { 0 }; =20 - if (thp =3D=3D 1) - madvise(p, size, MADV_HUGEPAGE); - else if (thp =3D=3D 0) - madvise(p, size, MADV_NOHUGEPAGE); + gup.addr =3D (unsigned long)self->addr; + gup.size =3D self->size; + gup.nr_pages_per_call =3D 1; =20 - /* Fault them in here, from user space. */ - for (; (unsigned long)p < gup.addr + size; p +=3D psize()) - p[0] =3D 0; + if (variant->write) + gup.gup_flags |=3D FOLL_WRITE; + + ASSERT_EQ(ioctl(self->gup_fd, PIN_BASIC_TEST, &gup), 0); +} + +TEST_F(gup_test, dump_user_pages_with_get) { + /* tests DUMP_USER_PAGES_TEST using get_user_pages */ + struct gup_test gup =3D { 0 }; + + gup.addr =3D (unsigned long)self->addr; + gup.size =3D self->size; + gup.nr_pages_per_call =3D 1; + + if (variant->write) + gup.gup_flags |=3D FOLL_WRITE; + + gup.which_pages[0] =3D 1; + + ASSERT_EQ(ioctl(self->gup_fd, DUMP_USER_PAGES_TEST, &gup), 0); +} =20 - tid =3D malloc(sizeof(pthread_t) * nthreads); - assert(tid); - for (i =3D 0; i < nthreads; i++) { - ret =3D pthread_create(&tid[i], NULL, gup_thread, &gup); - assert(ret =3D=3D 0); +TEST_F(gup_test, dump_user_pages_with_pin) { + /* tests DUMP_USER_PAGES_TEST using pin_user_pages */ + struct gup_test gup =3D { 0 }; + + gup.addr =3D (unsigned long)self->addr; + gup.size =3D self->size; + gup.nr_pages_per_call =3D 1; + + if (variant->write) + gup.gup_flags |=3D FOLL_WRITE; + + gup.which_pages[0] =3D 1; + gup.test_flags |=3D GUP_TEST_FLAG_DUMP_PAGES_USE_PIN; + + ASSERT_EQ(ioctl(self->gup_fd, DUMP_USER_PAGES_TEST, &gup), 0); +} + +int main(int argc, char **argv) +{ + int fd; + char *file =3D "/dev/zero"; + + fd =3D open(file, O_RDWR); + if (fd < 0) { + ksft_print_header(); + ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno)); } - for (i =3D 0; i < nthreads; i++) { - ret =3D pthread_join(tid[i], NULL); - assert(ret =3D=3D 0); + close(fd); + + fd =3D open(GUP_TEST_FILE, O_RDWR); + if (fd =3D=3D -1) { + ksft_print_header(); + if (errno =3D=3D EACCES) + ksft_exit_skip("Please run this test as root\n"); + if (errno =3D=3D ENOENT) { + if (opendir("/sys/kernel/debug") =3D=3D NULL) + ksft_exit_skip("Mount debugfs at /sys/kernel/debug\n"); + else + ksft_exit_skip("Check CONFIG_GUP_TEST in kernel config\n"); + } + ksft_exit_skip("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno)= ); } + close(fd); =20 - free(tid); - - ksft_exit_pass(); + return test_harness_run(argc, argv); } diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/self= tests/mm/run_vmtests.sh index 043aa3ed2596..65a4ef0f3748 100755 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -130,30 +130,6 @@ test_selected() { fi } =20 -run_gup_matrix() { - # -t: thp=3Don, -T: thp=3Doff, -H: hugetlb=3Don - local hugetlb_mb=3D256 - - for huge in -t -T "-H -m $hugetlb_mb"; do - # -u: gup-fast, -U: gup-basic, -a: pin-fast, -b: pin-basic, -L: pi= n-longterm - for test_cmd in -u -U -a -b -L; do - # -w: write=3D1, -W: write=3D0 - for write in -w -W; do - # -S: shared - for share in -S " "; do - # -n: How many pages to fetch together? 512 is special - # because it's default thp size (or 2M on x86), 123 to - # just test partial gup when hit a huge in whatever fo= rm - for num in "-n 1" "-n 512" "-n 123" "-n -1"; do - CATEGORY=3D"gup_test" run_test ./gup_test \ - $huge $test_cmd $write $share $num - done - done - done - done - done -} - # filter 64bit architectures ARCH64STR=3D"arm64 mips64 parisc64 ppc64 ppc64le riscv64 s390x sparc64 x86= _64" if [ -z "$ARCH" ]; then @@ -239,18 +215,7 @@ fi =20 CATEGORY=3D"mmap" run_test ./map_fixed_noreplace =20 -if $RUN_ALL; then - run_gup_matrix -else - # get_user_pages_fast() benchmark - CATEGORY=3D"gup_test" run_test ./gup_test -u -n 1 - CATEGORY=3D"gup_test" run_test ./gup_test -u -n -1 - # pin_user_pages_fast() benchmark - CATEGORY=3D"gup_test" run_test ./gup_test -a -n 1 - CATEGORY=3D"gup_test" run_test ./gup_test -a -n -1 -fi -# Dump pages 0, 19, and 4096, using pin_user_pages: -CATEGORY=3D"gup_test" run_test ./gup_test -ct -F 0x1 0 19 0x1000 +CATEGORY=3D"gup_test" run_test ./gup_test CATEGORY=3D"gup_test" run_test ./gup_longterm =20 CATEGORY=3D"userfaultfd" run_test ./uffd-unit-tests --=20 2.39.5