Re: [PATCH v6 5/6] tools/mm: make gup_bench a benchmark only tool
Mike Rapoport <[email protected]> Mon, 03 Aug 2026 12:00:01 +0300
| Newsgroups | org.kernel.vger.linux-kselftest,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kvack.linux-mm |
|---|---|
| Message-ID | <178574760162.1561566.7432721858074092727.b4-review@b4> |
> Remove the functional modes (GUP_BASIC_TEST, PIN_BASIC_TEST and > DUMP_USER_PAGES_TEST) from gup_bench. Drop kselftest dependency > and use normal diagnostics and exit statuses. > > When no arguments are supplied, run a single GUP_FAST_BENCHMARK > with existing default values. Let users select other configs > through command line options. Also validate numeric arguments > and reject positional arguments. > > Restore hugeTLB settings on failure and after every run. Also > handle failures without relying on assert() calls. > > Suggested-by: David Hildenbrand (Arm) <[email protected]> > Signed-off-by: Sarthak Sharma <[email protected]> ... > int main(int argc, char **argv) > { > struct gup_test gup = { 0 }; > - int filed, i, opt, nr_pages = 1, thp = -1, write = 1, nthreads = 1, ret; > + int filed, i, opt, nr_pages = 1, thp = -1, write = 1; > + int nthreads = 1, ret, started_threads = 0; > int flags = MAP_PRIVATE; > - char *file = "/dev/zero"; > - bool hugetlb = false; > + const char *file = "/dev/zero"; > + bool hugetlb = false, restore_hugetlb = false; > + unsigned long nr_pages_per_call; > pthread_t *tid; > char *p; > > - while ((opt = getopt(argc, argv, "m:r:n:F:f:abcj:tTLUuwWSHpz")) != -1) { > + while ((opt = getopt(argc, argv, "m:r:n:F:f:aj:tTLuwWSH")) != -1) { > switch (opt) { > case 'a': > cmd = PIN_FAST_BENCHMARK; > break; > - case 'b': > - cmd = PIN_BASIC_TEST; > - break; > case 'L': > cmd = PIN_LONGTERM_BENCHMARK; > break; > - case 'c': > - cmd = 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] = 1; > - break; > - case 'p': > - /* works only with DUMP_USER_PAGES_TEST */ > - gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN; > - break; > - case 'F': > - /* strtol, so you can pass flags in hex form */ > - gup.gup_flags = strtol(optarg, 0, 0); > + case 'F': { > + long val; > + > + val = parse_long_arg_base(optarg, "GUP flags", 0); > + if (val < 0 || val > UINT_MAX) { > + fprintf(stderr, "Invalid GUP flags '%s'\n", optarg); > + exit(1); > + } > + > + gup.gup_flags = val; > break; > - case 'j': > - nthreads = atoi(optarg); > + } > + case 'j': { > + long val; > + > + val = parse_positive_long_arg(optarg, "thread count"); > + if (val > INT_MAX || > + (size_t)val > SIZE_MAX / sizeof(pthread_t)) { > + fprintf(stderr, "Invalid thread count '%s'\n", optarg); > + exit(1); > + } > + nthreads = val; > break; > + } > case 'm': > - size = atoi(optarg) * MB; > + size = parse_positive_long_arg(optarg, "size"); > + if (size > ULONG_MAX / MB) { > + fprintf(stderr, "Invalid size '%s'\n", optarg); > + exit(1); > + } > + size *= MB; > break; > - case 'r': > - repeats = atoi(optarg); > + case 'r': { > + long val; > + > + val = parse_positive_long_arg(optarg, "repeat count"); > + if (val > INT_MAX) { > + fprintf(stderr, "Invalid repeat count '%s'\n", optarg); > + exit(1); > + } > + repeats = val; > break; > - case 'n': > - nr_pages = atoi(optarg); > - if (nr_pages < 0) > - nr_pages = size / getpagesize(); > + } > + case 'n': { > + long val; > + > + val = parse_long_arg(optarg, "page count"); It's better to name the numbers parsing after what they do: parse_flags() and parse_num(). > + if (val != -1 && (val < 1 || val > INT_MAX)) { And the limit checks seem wierd all over the place, like if we can loop infinitely, why do we care about INT_MAX? And what exact limit ULONG_MAX / MB or SIZE_MAX / sizeof(ptread_t) are supposed to express? > + fprintf(stderr, "Invalid page count '%s'\n", optarg); > + exit(1); > + } > + nr_pages = val; > break; ... > if (hugetlb) { > unsigned long hp_size = default_huge_page_size(); > > - if (!hp_size) > - ksft_exit_skip("HugeTLB is unavailable\n"); > + if (!hp_size) { > + fprintf(stderr, "Could not determine huge page size\n"); > + return 1; > + } > + > + if (size > ULONG_MAX - (hp_size - 1)) { > + fprintf(stderr, "HugeTLB mapping size is too large\n"); > + return 1; > + } > > size = (size + hp_size - 1) & ~(hp_size - 1); > - if (!hugetlb_setup_default(size / hp_size)) > - ksft_exit_skip("Not enough huge pages\n"); > + if (!hugetlb_setup_default(size / hp_size)) { > + fprintf(stderr, "Not enough huge pages\n"); > + hugetlb_restore_settings(); you don't need to explicitly call hugetlb_restore_settings(), _setup_defaults() sets up automatic restore on exit. > + return 1; > + } > + restore_hugetlb = true; > } ... > gup_fd = open(GUP_TEST_FILE, O_RDWR); > if (gup_fd == -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") == 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(); > + int err = errno; > + > + close(filed); > + if (err == EACCES) What was wrong with switch (errno) ? > + fprintf(stderr, "Please run as root\n"); Please add root check upfront and skip EACCES here > + else if (err == ENOENT) { > + DIR *debugfs = opendir("/sys/kernel/debug"); > + > + if (!debugfs) > + fprintf(stderr, "Mount debugfs at /sys/kernel/debug\n"); Just replace the prints, no need to refactor the logic there. > + else { > + closedir(debugfs); > + fprintf(stderr, "Check CONFIG_GUP_TEST in kernel config\n"); > + } > + } else > + fprintf(stderr, "Failed to open %s: %s\n", GUP_TEST_FILE, > + strerror(err)); > + if (restore_hugetlb) > + hugetlb_restore_settings(); > + return 1; > } > > p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0); > - if (p == MAP_FAILED) > - ksft_exit_fail_msg("mmap: %s\n", strerror(errno)); > + if (p == MAP_FAILED) { > + fprintf(stderr, "mmap: %s\n", strerror(errno)); > + close(filed); > + close(gup_fd); > + > + if (restore_hugetlb) > + hugetlb_restore_settings(); Use goto err_do_cleanup here and everywhere else. Piling cleanups in if (something_failed) is error prone and unmaintainable. > + return 1; > + } > + close(filed); > gup.addr = (unsigned long)p; > > if (thp == 1) ... > free(tid); > + munmap((void *)gup.addr, size); > + close(gup_fd); > + if (restore_hugetlb) > + hugetlb_restore_settings(); > > - ksft_exit_pass(); > + return bench_error ? 1 : 0; Using goto for cleanup gives you clean return 1 on error and return 0 on success. -- Sincerely yours, Mike.