Mshare is a developing feature proposed by Anthony Yznaga and Khalid Aziz that enables sharing of PTEs across processes. The V3 patch set has been posted for review:
https://lore.kernel.org/linux-mm/20250820010415.699353-1-anthony.yznaga@orac...
This patch set adds selftests to exercise and demonstrate basic functionality of mshare.
The initial tests use open, ioctl, and mmap syscalls to establish a shared memory mapping between two processes and verify the expected behavior.
Additional tests are included to check interoperability with swap and Transparent Huge Pages.
Future work will extend coverage to other use cases such as integration with KVM and more advanced scenarios.
This series is intended to be applied on top of mshare V3, which is based on mm-new (2025-08-15).
-----------------
V1->V2: - Based on mshare V3, which based on mm-new as of 2025-08-15 - (Fix) For test cases in basic.c, Change to use a small chunk of memory(4k/8K for normal pages, 2M/4M for hugetlb pages), as to ensure these tests can run on any server or device. - (Fix) For test cases of hugetlb, swap and THP, add a tips to configure corresponding settings. - (Fix) Add memory to .gitignore file once it exists - (fix) Correct the Changelog of THP test case that mshare support THP only when user configure shmem_enabled as always
V1: https://lore.kernel.org/all/20250825145719.29455-1-linyongting@bytedance.com...
Yongting Lin (8): mshare: Add selftests mshare: selftests: Adding config fragments mshare: selftests: Add some helper functions for mshare filesystem mshare: selftests: Add test case shared memory mshare: selftests: Add test case ioctl unmap mshare: selftests: Add some helper functions for configuring and retrieving cgroup mshare: selftests: Add test case to demostrate the swapping of mshare memory mshare: selftests: Add test case to demostrate that mshare partly supports THP
tools/testing/selftests/mshare/.gitignore | 4 + tools/testing/selftests/mshare/Makefile | 7 + tools/testing/selftests/mshare/basic.c | 109 ++++++++++ tools/testing/selftests/mshare/config | 1 + tools/testing/selftests/mshare/memory.c | 89 ++++++++ tools/testing/selftests/mshare/util.c | 254 ++++++++++++++++++++++ 6 files changed, 464 insertions(+) create mode 100644 tools/testing/selftests/mshare/.gitignore create mode 100644 tools/testing/selftests/mshare/Makefile create mode 100644 tools/testing/selftests/mshare/basic.c create mode 100644 tools/testing/selftests/mshare/config create mode 100644 tools/testing/selftests/mshare/memory.c create mode 100644 tools/testing/selftests/mshare/util.c
This patch setup the selftests project for mshare and add a 'hello world' to project.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/.gitignore | 3 +++ tools/testing/selftests/mshare/Makefile | 7 +++++++ tools/testing/selftests/mshare/basic.c | 10 ++++++++++ 3 files changed, 20 insertions(+) create mode 100644 tools/testing/selftests/mshare/.gitignore create mode 100644 tools/testing/selftests/mshare/Makefile create mode 100644 tools/testing/selftests/mshare/basic.c
diff --git a/tools/testing/selftests/mshare/.gitignore b/tools/testing/selftests/mshare/.gitignore new file mode 100644 index 000000000000..406f31bd432c --- /dev/null +++ b/tools/testing/selftests/mshare/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +basic diff --git a/tools/testing/selftests/mshare/Makefile b/tools/testing/selftests/mshare/Makefile new file mode 100644 index 000000000000..651658d091c5 --- /dev/null +++ b/tools/testing/selftests/mshare/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS = $(KHDR_INCLUDES) -Wall -g -O2 + +TEST_GEN_PROGS := basic + +include ../lib.mk diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c new file mode 100644 index 000000000000..482af948878d --- /dev/null +++ b/tools/testing/selftests/mshare/basic.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "../kselftest_harness.h" + +TEST(basic) +{ + printf("Hello mshare\n"); +} + +TEST_HARNESS_MAIN
mshare test cases need pre-required kernel configs for the test to get pass.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/config | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/testing/selftests/mshare/config
diff --git a/tools/testing/selftests/mshare/config b/tools/testing/selftests/mshare/config new file mode 100644 index 000000000000..16fd9a3ca12a --- /dev/null +++ b/tools/testing/selftests/mshare/config @@ -0,0 +1 @@ +CONFIG_MSHARE=y
This test case aims to verify the basic functionalities of mshare.
Create a mshare file and use ioctl to create mapping for host mm with supportive flags, then create processes to map mshare file to their memory space, and eventually verify the correctiness of sharing memory.
To ensure these tests can run on any server or device with minimal memory usage, we follow the steps below:
1. The ftruncate size must be a multiple of the alignment size. 2. In the ioctl(MSHAREFS_CREATE_MAPPING) syscall, which determines the memory size occupied by an mshare instance, we use 4K/8K for normal pages and 2M/4M for hugetlb pages. 3. The size used in the mmap syscall must match the ftruncate size.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/basic.c | 82 +++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 35739b1133f7..54a132a8116c 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -3,9 +3,87 @@ #include "../kselftest_harness.h" #include "util.c"
-TEST(basic) +#define STRING "I am Msharefs" + +FIXTURE(basic) +{ + char filename[128]; + size_t align_size; +}; + +FIXTURE_VARIANT(basic) { + size_t allocate_size; + /* flags for ioctl */ + int map_flags; +}; + +FIXTURE_VARIANT_ADD(basic, ANON_4k) { + .allocate_size = KB(4), + .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, HUGETLB_2m) { + .allocate_size = MB(2), + .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, ANON_8k) { + .allocate_size = KB(8), + .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, HUGETLB_4m) { + .allocate_size = MB(4), + .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_SETUP(basic) +{ + int fd; + + self->align_size = mshare_get_info(); + + fd = create_mshare_file(self->filename, sizeof(self->filename)); + ftruncate(fd, self->align_size); + + if (variant->map_flags & MAP_HUGETLB) + ksft_print_msg("Tip: Please enable hugepages before running this test.\n" + "For example: sysctl -w vm.nr_hugepages=2\n"); + + ASSERT_EQ(mshare_ioctl_mapping(fd, variant->allocate_size, variant->map_flags), 0); + close(fd); +} + +FIXTURE_TEARDOWN(basic) +{ + ASSERT_EQ(unlink(self->filename), 0); +} + +TEST_F(basic, shared_mem) { - printf("Hello mshare\n"); + int fd; + void *addr; + pid_t pid = fork(); + + ASSERT_NE(pid, -1); + + fd = open(self->filename, O_RDWR, 0600); + ASSERT_NE(fd, -1); + + addr = mmap(NULL, self->align_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + ASSERT_NE(addr, MAP_FAILED); + + if (pid == 0) { + /* Child process write date the shared memory */ + memcpy(addr, STRING, sizeof(STRING)); + exit(0); + } + + ASSERT_NE(waitpid(pid, NULL, 0), -1); + + /* Parent process should retrieve the data from the shared memory */ + ASSERT_EQ(memcmp(addr, STRING, sizeof(STRING)), 0); }
TEST_HARNESS_MAIN
This test case aims to verify whether the guest VMA will vanish when corresponding VMA of host mm got ioctl unmap.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/basic.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 54a132a8116c..043d1f3e1e3e 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -86,4 +86,24 @@ TEST_F(basic, shared_mem) ASSERT_EQ(memcmp(addr, STRING, sizeof(STRING)), 0); }
+TEST_F_SIGNAL(basic, ioctl_unmap, SIGSEGV) +{ + char *addr; + int fd; + + fd = open(self->filename, O_RDWR, 0600); + addr = mmap(NULL, self->align_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + ASSERT_NE(addr, MAP_FAILED); + addr[0] = 'M'; + + /* munmap vma for host mm */ + mshare_ioctl_munmap(fd, variant->allocate_size); + /* + * Will generate SIGSEGV signal as ioctl has already cleaned + * shared page table + */ + addr[0] = 'D'; +} + TEST_HARNESS_MAIN
Before verify some complicated memory functionalities such as swap memory and THP, we need add some helper functions to configure and retrieve cgroups (specifically, memcg).
These helper functions consist: Create and destroy individual cgroup for test cases attach and dettach the test process to specified cgroup Read swap size and thp size from testing cgroup
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/util.c | 128 ++++++++++++++++++++++++++ 1 file changed, 128 insertions(+)
diff --git a/tools/testing/selftests/mshare/util.c b/tools/testing/selftests/mshare/util.c index a3c3896bd507..f1db4eb3cd8f 100644 --- a/tools/testing/selftests/mshare/util.c +++ b/tools/testing/selftests/mshare/util.c @@ -124,3 +124,131 @@ int mshare_ioctl_munmap(int fd, size_t size)
return ioctl(fd, MSHAREFS_UNMAP, &munmap); } + +/* + * Helper functions for cgroup + */ + +#define CGROUP_BASE "/sys/fs/cgroup/" +#define CGROUP_TEST "mshare-test-XXXXXX" + +bool is_cgroup_v2; + +__attribute__((constructor)) +void get_cgroup_version(void) +{ + if (access(CGROUP_BASE "cgroup.controllers", F_OK) == 0) + is_cgroup_v2 = true; +} + +int create_mshare_test_cgroup(char *cgroup, size_t len) +{ + if (is_cgroup_v2) + snprintf(cgroup, len, "%s/%s", CGROUP_BASE, CGROUP_TEST); + else + snprintf(cgroup, len, "%s/memory/%s", CGROUP_BASE, CGROUP_TEST); + + char *path = mkdtemp(cgroup); + + if (!path) { + perror("mkdtemp"); + return -1; + } + + return 0; +} + +int remove_cgroup(char *cgroup) +{ + return rmdir(cgroup); +} + +int write_data_to_cgroup(char *cgroup, char *file, char *data) +{ + char filename[128]; + int fd; + int ret; + + snprintf(filename, sizeof(filename), "%s/%s", cgroup, file); + fd = open(filename, O_RDWR); + + if (fd == -1) + return -1; + + ret = write(fd, data, strlen(data)); + close(fd); + + return ret; +} + +int attach_to_cgroup(char *cgroup) +{ + char pid_str[32]; + + snprintf(pid_str, sizeof(pid_str), "%d", getpid()); + return write_data_to_cgroup(cgroup, "cgroup.procs", pid_str); +} + +/* + * Simplely, just move the pid to root memcg as avoid + * complicated consideration. + */ +int dettach_from_cgroup(char *cgroup) +{ + char pid_str[32]; + char *root_memcg; + + if (is_cgroup_v2) + root_memcg = CGROUP_BASE; + else + root_memcg = CGROUP_BASE "memory"; + + snprintf(pid_str, sizeof(pid_str), "%d", getpid()); + return write_data_to_cgroup(root_memcg, "cgroup.procs", pid_str); +} + +size_t read_data_from_cgroup(char *cgroup, char *file, char *field) +{ + char filename[128]; + FILE *fp; + char line[80]; + size_t size = -1; + + snprintf(filename, sizeof(filename), "%s/%s", cgroup, file); + fp = fopen(filename, "r"); + if (!fp) { + perror("fopen"); + return -1; + } + + while (fgets(line, sizeof(line), fp)) { + if (!strncmp(line, field, strlen(field))) { + char *value = line + strlen(field) + 1; + + size = atol(value); + break; + } + } + + fclose(fp); + + return size; +} + +size_t read_swap_from_cgroup(char *cgroup) +{ + if (is_cgroup_v2) + return read_data_from_cgroup(cgroup, "memory.stat", "pswpout"); + else + return read_data_from_cgroup(cgroup, "memory.stat", "swap"); +} + +size_t read_huge_from_cgroup(char *cgroup) +{ + if (is_cgroup_v2) + return read_data_from_cgroup(cgroup, "memory.stat", "file_thp") + + read_data_from_cgroup(cgroup, "memory.stat", "anon_thp") + + read_data_from_cgroup(cgroup, "memory.stat", "shmem_thp"); + else + return read_data_from_cgroup(cgroup, "memory.stat", "rss_huge"); +}
This case is quit simple by using madvise(MADV_PAGEOUT), but for verifying the memory size of being swappd, we need to setup the memcg and attach test process to this memcg before perform the test.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/.gitignore | 1 + tools/testing/selftests/mshare/Makefile | 2 +- tools/testing/selftests/mshare/memory.c | 71 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/mshare/memory.c
diff --git a/tools/testing/selftests/mshare/.gitignore b/tools/testing/selftests/mshare/.gitignore index 406f31bd432c..116774fa2b82 100644 --- a/tools/testing/selftests/mshare/.gitignore +++ b/tools/testing/selftests/mshare/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only
basic +memory diff --git a/tools/testing/selftests/mshare/Makefile b/tools/testing/selftests/mshare/Makefile index 651658d091c5..b0418b8c30f2 100644 --- a/tools/testing/selftests/mshare/Makefile +++ b/tools/testing/selftests/mshare/Makefile @@ -2,6 +2,6 @@
CFLAGS = $(KHDR_INCLUDES) -Wall -g -O2
-TEST_GEN_PROGS := basic +TEST_GEN_PROGS := basic memory
include ../lib.mk diff --git a/tools/testing/selftests/mshare/memory.c b/tools/testing/selftests/mshare/memory.c new file mode 100644 index 000000000000..4bb0d22b9c03 --- /dev/null +++ b/tools/testing/selftests/mshare/memory.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/mman.h> + +#include "../kselftest_harness.h" +#include "util.c" + +FIXTURE(memory) +{ + char filename[128]; + int fd; + + char cgroup[128]; + + void *addr; + size_t align_size; +}; + +FIXTURE_SETUP(memory) +{ + ASSERT_NE(create_mshare_test_cgroup(self->cgroup, sizeof(self->cgroup)), -1); + + attach_to_cgroup(self->cgroup); + + self->align_size = mshare_get_info(); + self->fd = create_mshare_file(self->filename, sizeof(self->filename)); + ASSERT_NE(self->fd, -1); + ASSERT_NE(ftruncate(self->fd, self->align_size), -1); + + ASSERT_NE(mshare_ioctl_mapping(self->fd, MB(2), + MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED), + -1); + self->addr = mmap(NULL, self->align_size, PROT_READ | PROT_WRITE, + MAP_SHARED, self->fd, 0); + ASSERT_NE(self->addr, MAP_FAILED); +} + +FIXTURE_TEARDOWN(memory) +{ + ASSERT_NE(munmap(self->addr, self->align_size), -1); + close(self->fd); + + ASSERT_NE(unlink(self->filename), -1); + dettach_from_cgroup(self->cgroup); + + ASSERT_NE(remove_cgroup(self->cgroup), -1); +} + +TEST_F(memory, swap) +{ + size_t swap_size; + + /* fill physical memory */ + memset(self->addr, 0x01, MB(2)); + + /* force to reclaim the memory of mshare */ + ASSERT_NE(madvise(self->addr, MB(2), MADV_PAGEOUT), -1); + + swap_size = read_swap_from_cgroup(self->cgroup); + ASSERT_NE(swap_size, -1); + + /* convert to bytes */ + swap_size *= 4096; + + ksft_print_msg("Tip: Please configure swap space before running this test.\n"); + + /* allow an error of 10% */ + ASSERT_GT(swap_size, MB(2) * 9 / 10); +} + +TEST_HARNESS_MAIN
Currently, mshare doesn't support madvise(MADV_HUGEPAGE) to make pages become THP page.
Thus we need to set /sys/kernel/mm/transparent_hugepage/shmem_enabled to 'always', enabling mshare to acquire THP pages in a best effort way.
This case is quit simple that set up a mshare memory with 2MB size, than use memset to fill the physical memory and verify whether THP was allocated by reading memory.stat.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/memory.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/tools/testing/selftests/mshare/memory.c b/tools/testing/selftests/mshare/memory.c index 4bb0d22b9c03..5132210b1465 100644 --- a/tools/testing/selftests/mshare/memory.c +++ b/tools/testing/selftests/mshare/memory.c @@ -68,4 +68,22 @@ TEST_F(memory, swap) ASSERT_GT(swap_size, MB(2) * 9 / 10); }
+TEST_F(memory, thp) +{ + /* fill physical memory */ + memset(self->addr, 0x01, MB(2)); + + size_t huge = read_huge_from_cgroup(self->cgroup); + + /* + * mshare only allocate in the best effort way, and + * don't support madvise(MADV_HUGEPAGE) to change pages + * into THP or khuged to replace pages with THP. + */ + ksft_print_msg("Tip: Please enable transparent hugepages for shmem before running this test.\n" + "For example: echo always > /sys/kernel/mm/transparent_hugepage/shmem_enabled\n"); + + ASSERT_GE(huge, MB(2)); +} + TEST_HARNESS_MAIN
linux-kselftest-mirror@lists.linaro.org