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).
Yongting Lin (8): mshare: Add selftests mshare: selftests: Adding config fragment mshare: selftests: Add some helper function for mshare filesystem mshare: selftests: Add test case shared memory mshare: selftests: Add test case ioctl unmap mshare: selftests: Add some helper functions for reading and controlling cgroup mshare: selftests: Add test case to demostrate the swaping of mshare memory mshare: selftests: Add test case to demostrate that mshare doesn't support THP
tools/testing/selftests/mshare/.gitignore | 3 + tools/testing/selftests/mshare/Makefile | 7 + tools/testing/selftests/mshare/basic.c | 108 ++++++++++ tools/testing/selftests/mshare/config | 1 + tools/testing/selftests/mshare/memory.c | 82 +++++++ tools/testing/selftests/mshare/util.c | 251 ++++++++++++++++++++++ 6 files changed, 452 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 sets up 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
Before create basic test cases, we need to have some helper functions to help setup the tests.
These helper functions consist of: Mount and unmount the mshare filesystem Create a temporary file which be performed test on it later Map and unmap mshare region via the ioctl syscall
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/basic.c | 1 + tools/testing/selftests/mshare/util.c | 123 +++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 tools/testing/selftests/mshare/util.c
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 482af948878d..35739b1133f7 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0
#include "../kselftest_harness.h" +#include "util.c"
TEST(basic) { diff --git a/tools/testing/selftests/mshare/util.c b/tools/testing/selftests/mshare/util.c new file mode 100644 index 000000000000..75f6ff25aa2c --- /dev/null +++ b/tools/testing/selftests/mshare/util.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/msharefs.h> +#include <stdio.h> +#include <mntent.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +/* + * Helper functions for mounting msharefs + */ + +#define MOUNT_POINT "/sys/fs/mshare" +#define FS_TYPE "msharefs" + +bool is_msharefs_mounted(void) +{ + FILE *fp; + struct mntent *ent; + bool found = false; + + fp = setmntent("/proc/mounts", "r"); + if (!fp) { + perror("setmntent"); + exit(1); + } + + while ((ent = getmntent(fp)) != NULL) { + if (strcmp(ent->mnt_dir, MOUNT_POINT) == 0 && + strcmp(ent->mnt_type, FS_TYPE) == 0) { + found = true; + break; + } + } + + endmntent(fp); + return found; +} + +bool msharefs_premounted; + +__attribute__((constructor)) +void mount_sharefs(void) +{ + msharefs_premounted = is_msharefs_mounted(); + if (msharefs_premounted) + return; + + if (mount(FS_TYPE, MOUNT_POINT, FS_TYPE, 0, NULL) != 0) { + perror("mount"); + exit(1); + } +} + +__attribute__((destructor)) +void umount_sharefs(void) +{ + if (!msharefs_premounted && umount(MOUNT_POINT) != 0) { + perror("umount"); + exit(1); + } +} + +/* + * Helper functions for mshare files + */ + +#define MSHARE_INFO MOUNT_POINT "/mshare_info" +#define MSHARE_TEST MOUNT_POINT "/mshare-test-XXXXXX" + +size_t mshare_get_info(void) +{ + char req[128]; + size_t size; + int fd; + + fd = open(MSHARE_INFO, O_RDONLY); + if (fd == -1) + return -1; + + read(fd, req, sizeof(req)); + size = atoll(req); + close(fd); + + return size; +} + +int create_mshare_file(char *filename, size_t len) +{ + int fd; + + strncpy(filename, MSHARE_TEST, len - 1); + fd = mkstemp(filename); + + return fd; +} + + +int mshare_ioctl_mapping(int fd, size_t size, int flags) +{ + struct mshare_create mcreate; + + mcreate.region_offset = 0; + mcreate.size = size; + mcreate.offset = 0; + mcreate.prot = PROT_READ | PROT_WRITE; + mcreate.flags = flags; + mcreate.fd = -1; + + return ioctl(fd, MSHAREFS_CREATE_MAPPING, &mcreate); +} + +int mshare_ioctl_munmap(int fd, size_t size) +{ + struct mshare_unmap munmap; + + munmap.region_offset = 0; + munmap.size = size; + + return ioctl(fd, MSHAREFS_UNMAP, &munmap); +}
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 two processes to map mshare file to their memory spaces, and eventually verify the correctiness of sharing memory.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/basic.c | 81 +++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 35739b1133f7..2347d30adfee 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -3,9 +3,86 @@ #include "../kselftest_harness.h" #include "util.c"
-TEST(basic) +#define STRING "I am Msharefs" + +FIXTURE(basic) +{ + char filename[128]; + size_t align_size; + size_t allocate_size; +}; + +FIXTURE_VARIANT(basic) { + /* decide the time of real mapping size besed on align_size */ + size_t map_size_time; + /* flags for ioctl */ + int map_flags; +}; + +FIXTURE_VARIANT_ADD(basic, ANON_512G) { + .map_size_time = 1, + .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, HUGETLB_512G) { + .map_size_time = 1, + .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, ANON_1T) { + .map_size_time = 2, + .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, HUGETLB_1T) { + .map_size_time = 2, + .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_SETUP(basic) { - printf("Hello mshare\n"); + int fd; + + self->align_size = mshare_get_info(); + self->allocate_size = self->align_size * variant->map_size_time; + + fd = create_mshare_file(self->filename, sizeof(self->filename)); + ftruncate(fd, self->allocate_size); + + ASSERT_EQ(mshare_ioctl_mapping(fd, self->allocate_size, variant->map_flags), 0); + close(fd); +} + +FIXTURE_TEARDOWN(basic) +{ + ASSERT_EQ(unlink(self->filename), 0); +} + +TEST_F(basic, shared_mem) +{ + 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->allocate_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
Hi Yongting,
Thank you for doing this. This is a great start for testing mshare. I do have some comments below.
On 8/25/25 7:57 AM, Yongting Lin wrote:
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 two processes to map mshare file to their memory spaces, and eventually verify the correctiness of sharing memory.
Signed-off-by: Yongting Lin linyongting@bytedance.com
tools/testing/selftests/mshare/basic.c | 81 +++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 35739b1133f7..2347d30adfee 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -3,9 +3,86 @@ #include "../kselftest_harness.h" #include "util.c" -TEST(basic) +#define STRING "I am Msharefs"
+FIXTURE(basic) +{
- char filename[128];
- size_t align_size;
- size_t allocate_size;
+};
+FIXTURE_VARIANT(basic) {
- /* decide the time of real mapping size besed on align_size */
- size_t map_size_time;
- /* flags for ioctl */
- int map_flags;
+};
+FIXTURE_VARIANT_ADD(basic, ANON_512G) {
- .map_size_time = 1,
- .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED,
+};
+FIXTURE_VARIANT_ADD(basic, HUGETLB_512G) {
- .map_size_time = 1,
- .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED,
+};
+FIXTURE_VARIANT_ADD(basic, ANON_1T) {
- .map_size_time = 2,
- .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED,
+};
+FIXTURE_VARIANT_ADD(basic, HUGETLB_1T) {
- .map_size_time = 2,
- .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED,
+};
+FIXTURE_SETUP(basic) {
- printf("Hello mshare\n");
- int fd;
- self->align_size = mshare_get_info();
- self->allocate_size = self->align_size * variant->map_size_time;
- fd = create_mshare_file(self->filename, sizeof(self->filename));
- ftruncate(fd, self->allocate_size);
- ASSERT_EQ(mshare_ioctl_mapping(fd, self->allocate_size, variant->map_flags), 0);
The tests should differentiate between how much VA space is allocated to an mshare region (i.e with ftruncate()) and how much memory is allocated within an mshare region through the ioctl. While the bounds of an mshare region need to be aligned to 512 GB, the memory allocated within it does not. Right now the tests will try to map 512 GB or 1 TB of anon or hugetlb memory in an mshare region which will fail on smaller systems to due to insufficient memory. Better to allocate smaller amounts so the tests can run on more systems.
Anthony
- close(fd);
+}
+FIXTURE_TEARDOWN(basic) +{
- ASSERT_EQ(unlink(self->filename), 0);
+}
+TEST_F(basic, shared_mem) +{
- 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->allocate_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 process with guest mm will segfault when VMA of host mm is unmaped via ioctl(MSHAREFS_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 2347d30adfee..16d1f63c3ebe 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -85,4 +85,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->allocate_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, self->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 controlling the cgroup (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 75f6ff25aa2c..94fddaea2c56 100644 --- a/tools/testing/selftests/mshare/util.c +++ b/tools/testing/selftests/mshare/util.c @@ -121,3 +121,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 swaped, 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/Makefile | 2 +- tools/testing/selftests/mshare/memory.c | 71 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/mshare/memory.c
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..7754c0e33506 --- /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" + +#define GB(x) ((x) * (1UL << 30)) + +FIXTURE(memory) +{ + char filename[128]; + int fd; + + char cgroup[128]; + + void *addr; + size_t allocate_size; +}; + +FIXTURE_SETUP(memory) +{ + ASSERT_NE(create_mshare_test_cgroup(self->cgroup, sizeof(self->cgroup)), -1); + + attach_to_cgroup(self->cgroup); + + self->allocate_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->allocate_size), -1); + + ASSERT_NE(mshare_ioctl_mapping(self->fd, self->allocate_size, + MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED), + -1); + self->addr = mmap(NULL, self->allocate_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->allocate_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; + + /* touch 1G memory */ + memset(self->addr, 0x01, GB(1)); + + /* force to reclaim the memory of mshare */ + ASSERT_NE(madvise(self->addr, GB(1), MADV_PAGEOUT), -1); + + swap_size = read_swap_from_cgroup(self->cgroup); + ASSERT_NE(swap_size, -1); + + /* convert to bytes */ + swap_size *= 4096; + + /* allow an error of 10% */ + ASSERT_GT(swap_size, GB(1) * 9 / 10); +} + +TEST_HARNESS_MAIN
This case is quit simple by using madvise(MADV_HUGEPAGE), but for verifying the size of THP memory, we need to setup the memcg and attach test process to this memcg before perform the test.
Because mshare doesn't support THP feature, the size of THP memory should be 0 even though we use madivse.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/memory.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/tools/testing/selftests/mshare/memory.c b/tools/testing/selftests/mshare/memory.c index 7754c0e33506..2a415ce7bc01 100644 --- a/tools/testing/selftests/mshare/memory.c +++ b/tools/testing/selftests/mshare/memory.c @@ -68,4 +68,15 @@ TEST_F(memory, swap) ASSERT_GT(swap_size, GB(1) * 9 / 10); }
+TEST_F(memory, thp) +{ + ASSERT_NE(madvise(self->addr, self->allocate_size, MADV_HUGEPAGE), -1); + /* touch 1G */ + memset(self->addr, 0x01, GB(1)); + + size_t huge = read_huge_from_cgroup(self->cgroup); + /* mshare don't support THP now */ + ASSERT_EQ(huge, 0); +} + TEST_HARNESS_MAIN
On 8/25/25 7:57 AM, Yongting Lin wrote:
This case is quit simple by using madvise(MADV_HUGEPAGE), but for verifying the size of THP memory, we need to setup the memcg and attach test process to this memcg before perform the test.
Because mshare doesn't support THP feature, the size of THP memory should be 0 even though we use madivse.
There can be THPs in an mshare region if THP settings are set to "always", but otherwise madvise() won't work directly on the msharefs VMA. It will be necessary to implement an ioctl to apply madvise operations to memory in an mshare region.
Anthony
Signed-off-by: Yongting Lin linyongting@bytedance.com
tools/testing/selftests/mshare/memory.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/tools/testing/selftests/mshare/memory.c b/tools/testing/selftests/mshare/memory.c index 7754c0e33506..2a415ce7bc01 100644 --- a/tools/testing/selftests/mshare/memory.c +++ b/tools/testing/selftests/mshare/memory.c @@ -68,4 +68,15 @@ TEST_F(memory, swap) ASSERT_GT(swap_size, GB(1) * 9 / 10); } +TEST_F(memory, thp) +{
- ASSERT_NE(madvise(self->addr, self->allocate_size, MADV_HUGEPAGE), -1);
- /* touch 1G */
- memset(self->addr, 0x01, GB(1));
- size_t huge = read_huge_from_cgroup(self->cgroup);
- /* mshare don't support THP now */
- ASSERT_EQ(huge, 0);
+}
- TEST_HARNESS_MAIN
This patch sets up 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
Before create basic test cases, we need to have some helper functions to help setup the tests.
These helper functions consist of: Mount and unmount the mshare filesystem Create a temporary file which be performed test on it later Map and unmap mshare region via the ioctl syscall
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/basic.c | 1 + tools/testing/selftests/mshare/util.c | 123 +++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 tools/testing/selftests/mshare/util.c
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 482af948878d..35739b1133f7 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0
#include "../kselftest_harness.h" +#include "util.c"
TEST(basic) { diff --git a/tools/testing/selftests/mshare/util.c b/tools/testing/selftests/mshare/util.c new file mode 100644 index 000000000000..75f6ff25aa2c --- /dev/null +++ b/tools/testing/selftests/mshare/util.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/msharefs.h> +#include <stdio.h> +#include <mntent.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +/* + * Helper functions for mounting msharefs + */ + +#define MOUNT_POINT "/sys/fs/mshare" +#define FS_TYPE "msharefs" + +bool is_msharefs_mounted(void) +{ + FILE *fp; + struct mntent *ent; + bool found = false; + + fp = setmntent("/proc/mounts", "r"); + if (!fp) { + perror("setmntent"); + exit(1); + } + + while ((ent = getmntent(fp)) != NULL) { + if (strcmp(ent->mnt_dir, MOUNT_POINT) == 0 && + strcmp(ent->mnt_type, FS_TYPE) == 0) { + found = true; + break; + } + } + + endmntent(fp); + return found; +} + +bool msharefs_premounted; + +__attribute__((constructor)) +void mount_sharefs(void) +{ + msharefs_premounted = is_msharefs_mounted(); + if (msharefs_premounted) + return; + + if (mount(FS_TYPE, MOUNT_POINT, FS_TYPE, 0, NULL) != 0) { + perror("mount"); + exit(1); + } +} + +__attribute__((destructor)) +void umount_sharefs(void) +{ + if (!msharefs_premounted && umount(MOUNT_POINT) != 0) { + perror("umount"); + exit(1); + } +} + +/* + * Helper functions for mshare files + */ + +#define MSHARE_INFO MOUNT_POINT "/mshare_info" +#define MSHARE_TEST MOUNT_POINT "/mshare-test-XXXXXX" + +size_t mshare_get_info(void) +{ + char req[128]; + size_t size; + int fd; + + fd = open(MSHARE_INFO, O_RDONLY); + if (fd == -1) + return -1; + + read(fd, req, sizeof(req)); + size = atoll(req); + close(fd); + + return size; +} + +int create_mshare_file(char *filename, size_t len) +{ + int fd; + + strncpy(filename, MSHARE_TEST, len - 1); + fd = mkstemp(filename); + + return fd; +} + + +int mshare_ioctl_mapping(int fd, size_t size, int flags) +{ + struct mshare_create mcreate; + + mcreate.region_offset = 0; + mcreate.size = size; + mcreate.offset = 0; + mcreate.prot = PROT_READ | PROT_WRITE; + mcreate.flags = flags; + mcreate.fd = -1; + + return ioctl(fd, MSHAREFS_CREATE_MAPPING, &mcreate); +} + +int mshare_ioctl_munmap(int fd, size_t size) +{ + struct mshare_unmap munmap; + + munmap.region_offset = 0; + munmap.size = size; + + return ioctl(fd, MSHAREFS_UNMAP, &munmap); +}
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 two processes to map mshare file to their memory spaces, and eventually verify the correctiness of sharing memory.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/basic.c | 81 +++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/mshare/basic.c b/tools/testing/selftests/mshare/basic.c index 35739b1133f7..2347d30adfee 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -3,9 +3,86 @@ #include "../kselftest_harness.h" #include "util.c"
-TEST(basic) +#define STRING "I am Msharefs" + +FIXTURE(basic) +{ + char filename[128]; + size_t align_size; + size_t allocate_size; +}; + +FIXTURE_VARIANT(basic) { + /* decide the time of real mapping size besed on align_size */ + size_t map_size_time; + /* flags for ioctl */ + int map_flags; +}; + +FIXTURE_VARIANT_ADD(basic, ANON_512G) { + .map_size_time = 1, + .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, HUGETLB_512G) { + .map_size_time = 1, + .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, ANON_1T) { + .map_size_time = 2, + .map_flags = MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_VARIANT_ADD(basic, HUGETLB_1T) { + .map_size_time = 2, + .map_flags = MAP_ANONYMOUS | MAP_HUGETLB | MAP_SHARED | MAP_FIXED, +}; + +FIXTURE_SETUP(basic) { - printf("Hello mshare\n"); + int fd; + + self->align_size = mshare_get_info(); + self->allocate_size = self->align_size * variant->map_size_time; + + fd = create_mshare_file(self->filename, sizeof(self->filename)); + ftruncate(fd, self->allocate_size); + + ASSERT_EQ(mshare_ioctl_mapping(fd, self->allocate_size, variant->map_flags), 0); + close(fd); +} + +FIXTURE_TEARDOWN(basic) +{ + ASSERT_EQ(unlink(self->filename), 0); +} + +TEST_F(basic, shared_mem) +{ + 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->allocate_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 process with guest mm will segfault when VMA of host mm is unmaped via ioctl(MSHAREFS_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 2347d30adfee..16d1f63c3ebe 100644 --- a/tools/testing/selftests/mshare/basic.c +++ b/tools/testing/selftests/mshare/basic.c @@ -85,4 +85,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->allocate_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, self->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 controlling the cgroup (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 75f6ff25aa2c..94fddaea2c56 100644 --- a/tools/testing/selftests/mshare/util.c +++ b/tools/testing/selftests/mshare/util.c @@ -121,3 +121,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 swaped, 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/Makefile | 2 +- tools/testing/selftests/mshare/memory.c | 71 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/mshare/memory.c
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..7754c0e33506 --- /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" + +#define GB(x) ((x) * (1UL << 30)) + +FIXTURE(memory) +{ + char filename[128]; + int fd; + + char cgroup[128]; + + void *addr; + size_t allocate_size; +}; + +FIXTURE_SETUP(memory) +{ + ASSERT_NE(create_mshare_test_cgroup(self->cgroup, sizeof(self->cgroup)), -1); + + attach_to_cgroup(self->cgroup); + + self->allocate_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->allocate_size), -1); + + ASSERT_NE(mshare_ioctl_mapping(self->fd, self->allocate_size, + MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED), + -1); + self->addr = mmap(NULL, self->allocate_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->allocate_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; + + /* touch 1G memory */ + memset(self->addr, 0x01, GB(1)); + + /* force to reclaim the memory of mshare */ + ASSERT_NE(madvise(self->addr, GB(1), MADV_PAGEOUT), -1); + + swap_size = read_swap_from_cgroup(self->cgroup); + ASSERT_NE(swap_size, -1); + + /* convert to bytes */ + swap_size *= 4096; + + /* allow an error of 10% */ + ASSERT_GT(swap_size, GB(1) * 9 / 10); +} + +TEST_HARNESS_MAIN
This case is quit simple by using madvise(MADV_HUGEPAGE), but for verifying the size of THP memory, we need to setup the memcg and attach test process to this memcg before perform the test.
Because mshare doesn't support THP feature, the size of THP memory should be 0 even though we use madivse.
Signed-off-by: Yongting Lin linyongting@bytedance.com --- tools/testing/selftests/mshare/memory.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/tools/testing/selftests/mshare/memory.c b/tools/testing/selftests/mshare/memory.c index 7754c0e33506..2a415ce7bc01 100644 --- a/tools/testing/selftests/mshare/memory.c +++ b/tools/testing/selftests/mshare/memory.c @@ -68,4 +68,15 @@ TEST_F(memory, swap) ASSERT_GT(swap_size, GB(1) * 9 / 10); }
+TEST_F(memory, thp) +{ + ASSERT_NE(madvise(self->addr, self->allocate_size, MADV_HUGEPAGE), -1); + /* touch 1G */ + memset(self->addr, 0x01, GB(1)); + + size_t huge = read_huge_from_cgroup(self->cgroup); + /* mshare don't support THP now */ + ASSERT_EQ(huge, 0); +} + TEST_HARNESS_MAIN
Sorry for the mistake! I accidentally sent each individual patch twice, except for the cover letter. Please ignore the duplicated ones at the bottom.
Apologies again for the noise.
Yongting Lin.
On Mon, Aug 25, 2025 at 10:57 PM Yongting Lin linyongting@bytedance.com wrote:
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).
Yongting Lin (8): mshare: Add selftests mshare: selftests: Adding config fragment mshare: selftests: Add some helper function for mshare filesystem mshare: selftests: Add test case shared memory mshare: selftests: Add test case ioctl unmap mshare: selftests: Add some helper functions for reading and controlling cgroup mshare: selftests: Add test case to demostrate the swaping of mshare memory mshare: selftests: Add test case to demostrate that mshare doesn't support THP
tools/testing/selftests/mshare/.gitignore | 3 + tools/testing/selftests/mshare/Makefile | 7 + tools/testing/selftests/mshare/basic.c | 108 ++++++++++ tools/testing/selftests/mshare/config | 1 + tools/testing/selftests/mshare/memory.c | 82 +++++++ tools/testing/selftests/mshare/util.c | 251 ++++++++++++++++++++++ 6 files changed, 452 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
-- 2.20.1
linux-kselftest-mirror@lists.linaro.org