[PATCH v3 4/4] selftests: kmod: Add tests for merging same-name module load requests
From: Petr Pavlu
Date: Sun Oct 16 2022 - 08:31:37 EST
Add two tests to check that loading the same module multiple times in
parallel results only in one real attempt to initialize it.
Synchronization of the loads is done by waiting 1000 ms in the init
function of a sample module kmod_test_0014. The tests measure time
needed to perform all inserts to verify that the loads get merged by the
module loader and are not serialized.
* Case 0014 checks a situation when the load is successful. It should
result in one insert returning 0 and remaining inserts returning
EEXIST.
* Case 0015 checks a situation when the load is failing because the
module init function returns ENODEV. It should result in one insert
returning this error code and remaining inserts returning EBUSY.
The tests use a simple init_module program to load kmod_test_0014.ko. It
enables to obtain directly a return code from the finit_module syscall.
Signed-off-by: Petr Pavlu <petr.pavlu@xxxxxxxx>
---
tools/testing/selftests/kmod/.gitignore | 1 +
tools/testing/selftests/kmod/Makefile | 17 ++-
tools/testing/selftests/kmod/init_module.c | 49 ++++++
tools/testing/selftests/kmod/kmod.sh | 139 ++++++++++++++++++
.../selftests/kmod/kmod_test_0014/Makefile | 14 ++
.../kmod/kmod_test_0014/kmod_test_0014.c | 29 ++++
6 files changed, 244 insertions(+), 5 deletions(-)
create mode 100644 tools/testing/selftests/kmod/.gitignore
create mode 100644 tools/testing/selftests/kmod/init_module.c
create mode 100644 tools/testing/selftests/kmod/kmod_test_0014/Makefile
create mode 100644 tools/testing/selftests/kmod/kmod_test_0014/kmod_test_0014.c
diff --git a/tools/testing/selftests/kmod/.gitignore b/tools/testing/selftests/kmod/.gitignore
new file mode 100644
index 000000000000..ea3afcaccc79
--- /dev/null
+++ b/tools/testing/selftests/kmod/.gitignore
@@ -0,0 +1 @@
+init_module
diff --git a/tools/testing/selftests/kmod/Makefile b/tools/testing/selftests/kmod/Makefile
index 5b3e746a0bee..48aeaae76211 100644
--- a/tools/testing/selftests/kmod/Makefile
+++ b/tools/testing/selftests/kmod/Makefile
@@ -1,12 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-only
# Makefile for kmod loading selftests
-# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
-all:
-
TEST_PROGS := kmod.sh
+# compile but not part of 'make run_tests'
+TEST_GEN_PROGS_EXTENDED := init_module kmod_test_0014.ko
+
+# override lib.mk's default rule
+override define CLEAN
+ $(RM) $(TEST_GEN_PROGS_EXTENDED)
+ $(MAKE) -C kmod_test_0014 clean
+endef
+
include ../lib.mk
-# Nothing to clean up.
-clean:
+$(OUTPUT)/kmod_test_0014.ko: $(wildcard kmod_test_0014/Makefile kmod_test_0014/*.[ch])
+ $(MAKE) -C kmod_test_0014
+ cp kmod_test_0014/kmod_test_0014.ko $@
diff --git a/tools/testing/selftests/kmod/init_module.c b/tools/testing/selftests/kmod/init_module.c
new file mode 100644
index 000000000000..529abd0a8357
--- /dev/null
+++ b/tools/testing/selftests/kmod/init_module.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Simple program to insert a module, similar to insmod but tailored
+ * specifically for needs of module loader tests.
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <errno.h>
+
+int main(int argc, char *argv[])
+{
+ const char *filename, *args;
+ int fd, ret;
+
+ if (argc < 2 || argc > 3) {
+ fprintf(stderr, "usage: %s filename [args]\n", argv[0]);
+ return EINVAL;
+ }
+
+ filename = argv[1];
+ args = argc > 2 ? argv[2] : "";
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ ret = errno;
+ fprintf(stderr, "init_module: could not open file %s: %s\n",
+ filename, strerror(ret));
+ return ret;
+ }
+
+ ret = syscall(SYS_finit_module, fd, args, 0);
+ if (ret != 0) {
+ ret = errno;
+ fprintf(stderr, "init_module: could not load module %s: %s\n",
+ filename, strerror(ret));
+ }
+
+ close(fd);
+
+ return ret;
+}
diff --git a/tools/testing/selftests/kmod/kmod.sh b/tools/testing/selftests/kmod/kmod.sh
index afd42387e8b2..8836d7fe1a0a 100755
--- a/tools/testing/selftests/kmod/kmod.sh
+++ b/tools/testing/selftests/kmod/kmod.sh
@@ -65,6 +65,8 @@ ALL_TESTS="$ALL_TESTS 0010:1:1"
ALL_TESTS="$ALL_TESTS 0011:1:1"
ALL_TESTS="$ALL_TESTS 0012:1:1"
ALL_TESTS="$ALL_TESTS 0013:1:1"
+ALL_TESTS="$ALL_TESTS 0014:1:1"
+ALL_TESTS="$ALL_TESTS 0015:1:1"
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
@@ -171,6 +173,12 @@ errno_name_to_val()
echo -1;;
-ENOENT)
echo -2;;
+ -EBUSY)
+ echo -16;;
+ -EEXIST)
+ echo -17;;
+ -ENODEV)
+ echo -19;;
-EINVAL)
echo -22;;
-ERR_ANY)
@@ -190,6 +198,12 @@ errno_val_to_name()
echo -EPERM;;
-2)
echo -ENOENT;;
+ -16)
+ echo -EBUSY;;
+ -17)
+ echo -EEXIST;;
+ -19)
+ echo -ENODEV;;
-22)
echo -EINVAL;;
-123456)
@@ -504,6 +518,129 @@ kmod_test_0013()
"cat /sys/module/${DEFAULT_KMOD_DRIVER}/sections/.*text | head -n1"
}
+# Test that loading the same module multiple times in parallel results only in
+# one real attempt to initialize it. Synchronization of the loads is done by
+# waiting 1000 ms in the init function of a sample module kmod_test_0014. Tests
+# measure time needed to perform all inserts to verify that the loads get merged
+# by the module loader and are not serialized.
+#
+# * Case 0014 checks a situation when the load is successful. It should result
+# in one insert returning 0 and remaining inserts returning EEXIST.
+# * Case 0015 checks a situation when the load is failing because the module
+# init function returns ENODEV. It should result in one insert returning this
+# error code and remaining inserts returning EBUSY.
+kmod_check_parallel_loads()
+{
+ local test_name="$1"
+ local parallel_loads="$2"
+ local insmod_params="$3"
+ local max_time="$4"
+ local success_exp="$5"
+ local ebusy_exp="$6"
+ local eexist_exp="$7"
+ local enodev_exp="$8"
+ local other_exp="$9"
+
+ local EBUSY=$(errno_name_to_val -EBUSY)
+ local EEXIST=$(errno_name_to_val -EEXIST)
+ local ENODEV=$(errno_name_to_val -ENODEV)
+ local start end elapsed pids
+ local success_cnt=0 ebusy_cnt=0 eexist_cnt=0 enodev_cnt=0 other_cnt=0
+ local test_ok=true
+
+ # Do a dummy insert to get the file into the page cache.
+ ./init_module kmod_test_0014.ko "sleep_msecs=0 retval=0"
+ rmmod kmod_test_0014
+
+ # Run the parallel loads.
+ let start=$(date +%s%N)/1000000
+
+ for i in $(seq 0 $(($parallel_loads - 1))); do
+ ./init_module kmod_test_0014.ko "$insmod_params" &
+ pids[$i]=$!
+ done
+
+ for pid in ${pids[*]}; do
+ local rc
+ { wait $pid; let rc=-$?; } || true
+
+ case $rc in
+ 0)
+ let success_cnt=$success_cnt+1;;
+ $EBUSY)
+ let ebusy_cnt=$ebusy_cnt+1;;
+ $EEXIST)
+ let eexist_cnt=$eexist_cnt+1;;
+ $ENODEV)
+ let enodev_cnt=$enodev_cnt+1;;
+ *)
+ let other_cnt=$other_cnt+1;;
+ esac
+ done
+
+ let end=$(date +%s%N)/1000000
+ let elapsed=$end-$start
+
+ if (( $success_cnt > 0 )); then
+ rmmod kmod_test_0014
+ fi
+
+ # Check the results.
+ if (( $elapsed > $max_time )); then
+ echo "$test_name: FAIL, time to load the modules is within limit, test expects '<$max_time' ms - got '$elapsed' ms" >&2
+ test_ok=false
+ else
+ echo "$test_name: OK! - time to load the modules is within limit, test expects '<$max_time' ms - got '$elapsed' ms" >&2
+ fi
+ if (( $success_cnt != $success_exp )); then
+ echo "$test_name: FAIL, number of successful loads, test expects '$success_exp' - got '$success_cnt'" >&2
+ test_ok=false
+ else
+ echo "$test_name: OK! - number of successful loads, test expects '$success_exp' - got '$success_cnt'" >&2
+ fi
+ if (( $ebusy_cnt != $ebusy_exp )); then
+ echo "$test_name: FAIL, number of loads returning EBUSY, test expects '$ebusy_exp' - got '$ebusy_cnt'" >&2
+ test_ok=false
+ else
+ echo "$test_name: OK! - number of loads returning EBUSY, test expects '$ebusy_exp' - got '$ebusy_cnt'" >&2
+ fi
+ if (( $eexist_cnt != $eexist_exp )); then
+ echo "$test_name: FAIL, number of loads returning EEXIST, test expects '$eexist_exp' - got '$eexist_cnt'" >&2
+ test_ok=false
+ else
+ echo "$test_name: OK! - number of loads returning EEXIST, test expects '$eexist_exp' - got '$eexist_cnt'" >&2
+ fi
+ if (( $enodev_cnt != $enodev_exp )); then
+ echo "$test_name: FAIL, number of loads returning ENODEV, test expects '$enodev_exp' - got '$enodev_cnt'" >&2
+ test_ok=false
+ else
+ echo "$test_name: OK! - number of loads returning ENODEV, test expects '$enodev_exp' - got '$enodev_cnt'" >&2
+ fi
+ if (( $other_cnt != $other_exp )); then
+ echo "$test_name: FAIL, number of loads returning other values, test expects '$other_exp' - got '$other_cnt'" >&2
+ test_ok=false
+ else
+ echo "$test_name: OK! - number of loads returning other values, test expects '$other_exp' - got '$other_cnt'" >&2
+ fi
+
+ [ $test_ok = true ] || exit 1
+}
+
+kmod_test_0014()
+{
+ kmod_check_parallel_loads \
+ "${FUNCNAME[0]}" 4 "sleep_msecs=1000 retval=0" 3000 \
+ 1 0 3 0 0
+}
+
+kmod_test_0015()
+{
+ local ENODEV=$(errno_name_to_val -ENODEV)
+ kmod_check_parallel_loads \
+ "${FUNCNAME[0]}" 4 "sleep_msecs=1000 retval=$ENODEV" 3000 \
+ 0 3 0 1 0
+}
+
list_tests()
{
echo "Test ID list:"
@@ -525,6 +662,8 @@ list_tests()
echo "0011 x $(get_test_count 0011) - test completely disabling module autoloading"
echo "0012 x $(get_test_count 0012) - test /proc/modules address visibility under CAP_SYSLOG"
echo "0013 x $(get_test_count 0013) - test /sys/module/*/sections/* visibility under CAP_SYSLOG"
+ echo "0014 x $(get_test_count 0014) - test handling of parallel loads, success case"
+ echo "0015 x $(get_test_count 0015) - test handling of parallel loads, init returning error"
}
usage()
diff --git a/tools/testing/selftests/kmod/kmod_test_0014/Makefile b/tools/testing/selftests/kmod/kmod_test_0014/Makefile
new file mode 100644
index 000000000000..c90afe5a98ad
--- /dev/null
+++ b/tools/testing/selftests/kmod/kmod_test_0014/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+KMOD_TEST_0014_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= $(abspath $(KMOD_TEST_0014_DIR)/../../../../..)
+
+MODULES = kmod_test_0014.ko
+
+obj-m += kmod_test_0014.o
+
+all:
+ $(MAKE) -C $(KDIR) M=$(KMOD_TEST_0014_DIR) modules
+
+clean:
+ $(MAKE) -C $(KDIR) M=$(KMOD_TEST_0014_DIR) clean
diff --git a/tools/testing/selftests/kmod/kmod_test_0014/kmod_test_0014.c b/tools/testing/selftests/kmod/kmod_test_0014/kmod_test_0014.c
new file mode 100644
index 000000000000..2b2ebeefe689
--- /dev/null
+++ b/tools/testing/selftests/kmod/kmod_test_0014/kmod_test_0014.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+
+static unsigned int sleep_msecs = 1000;
+module_param(sleep_msecs, uint, 0644);
+MODULE_PARM_DESC(sleep_msecs, "init sleep in miliseconds, default 1000");
+
+static int retval;
+module_param(retval, int, 0644);
+MODULE_PARM_DESC(retval, "init return value, default 0");
+
+static int __init kmod_test_0014_init(void)
+{
+ msleep(sleep_msecs);
+ return retval;
+}
+
+static void __exit kmod_test_0014_exit(void)
+{
+}
+
+module_init(kmod_test_0014_init);
+module_exit(kmod_test_0014_exit);
+
+MODULE_AUTHOR("Petr Pavlu <petr.pavlu@xxxxxxxx>");
+MODULE_LICENSE("GPL");
--
2.35.3