From: Wei Yang <richard.weiyang@gmail.com>
To: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Andrew Morton <akpm@linux-foundation.org>,
Vlastimil Babka <vbabka@suse.cz>, Jann Horn <jannh@google.com>,
"Liam R . Howlett" <Liam.Howlett@oracle.com>,
Suren Baghdasaryan <surenb@google.com>,
David Hildenbrand <david@redhat.com>,
Matthew Wilcox <willy@infradead.org>,
Rik van Riel <riel@surriel.com>,
linux-mm@kvack.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 3/3] tools/testing/selftests: assert that anon merge cases behave as expected
Date: Mon, 7 Apr 2025 02:54:56 +0000 [thread overview]
Message-ID: <20250407025455.67nhchndedotn57g@master> (raw)
In-Reply-To: <e9e016392b6ceb5c58a868439fd7039e31bfda18.1742245056.git.lorenzo.stoakes@oracle.com>
On Mon, Mar 17, 2025 at 09:15:05PM +0000, Lorenzo Stoakes wrote:
>Prior to the recently applied commit that permits this merge,
>mprotect()'ing a faulted VMA, adjacent to an unfaulted VMA, such that the
>two share characteristics would fail to merge due to what appear to be
>unintended consequences of commit 965f55dea0e3 ("mmap: avoid merging cloned
>VMAs").
>
>Now we have fixed this bug, assert that we can indeed merge anonymous VMAs
>this way.
>
>Also assert that forked source/target VMAs are equally
>rejected. Previously, all empty target anon merges with one VMA faulted and
>the other unfaulted would be rejected incorrectly, now we ensure that
>unforked merge, but forked do not.
>
>Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
>---
> tools/testing/selftests/mm/.gitignore | 1 +
> tools/testing/selftests/mm/Makefile | 1 +
> tools/testing/selftests/mm/merge.c | 454 ++++++++++++++++++++++
> tools/testing/selftests/mm/run_vmtests.sh | 2 +
> 4 files changed, 458 insertions(+)
> create mode 100644 tools/testing/selftests/mm/merge.c
>
>diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore
>index c5241b193db8..91db34941a14 100644
>--- a/tools/testing/selftests/mm/.gitignore
>+++ b/tools/testing/selftests/mm/.gitignore
>@@ -58,3 +58,4 @@ hugetlb_dio
> pkey_sighandler_tests_32
> pkey_sighandler_tests_64
> guard-regions
>+merge
>diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
>index 8270895039d1..ad4d6043a60f 100644
>--- a/tools/testing/selftests/mm/Makefile
>+++ b/tools/testing/selftests/mm/Makefile
>@@ -98,6 +98,7 @@ TEST_GEN_FILES += hugetlb_madv_vs_map
> TEST_GEN_FILES += hugetlb_dio
> TEST_GEN_FILES += droppable
> TEST_GEN_FILES += guard-regions
>+TEST_GEN_FILES += merge
>
> ifneq ($(ARCH),arm64)
> TEST_GEN_FILES += soft-dirty
>diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c
>new file mode 100644
>index 000000000000..9cc61bdbfba8
>--- /dev/null
>+++ b/tools/testing/selftests/mm/merge.c
>@@ -0,0 +1,454 @@
>+// SPDX-License-Identifier: GPL-2.0-or-later
>+
>+#define _GNU_SOURCE
>+#include "../kselftest_harness.h"
>+#include <stdio.h>
>+#include <stdlib.h>
>+#include <unistd.h>
>+#include <sys/mman.h>
>+#include <sys/wait.h>
>+#include "vm_util.h"
>+
>+FIXTURE(merge)
>+{
>+ unsigned int page_size;
>+ char *carveout;
>+ struct procmap_fd procmap;
>+};
>+
>+FIXTURE_SETUP(merge)
>+{
>+ self->page_size = psize();
>+ /* Carve out PROT_NONE region to map over. */
>+ self->carveout = mmap(NULL, 12 * self->page_size, PROT_NONE,
>+ MAP_ANON | MAP_PRIVATE, -1, 0);
>+ ASSERT_NE(self->carveout, MAP_FAILED);
>+ /* Setup PROCMAP_QUERY interface. */
>+ ASSERT_EQ(open_self_procmap(&self->procmap), 0);
>+}
>+
>+FIXTURE_TEARDOWN(merge)
>+{
>+ ASSERT_EQ(munmap(self->carveout, 12 * self->page_size), 0);
>+ ASSERT_EQ(close_procmap(&self->procmap), 0);
>+}
>+
>+TEST_F(merge, mprotect_unfaulted_left)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ char *ptr;
>+
>+ /*
>+ * Map 10 pages of R/W memory within. MAP_NORESERVE so we don't hit
>+ * merge failure due to lack of VM_ACCOUNT flag by mistake.
>+ *
>+ * |-----------------------|
>+ * | unfaulted |
>+ * |-----------------------|
>+ */
>+ ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+ /*
>+ * Now make the first 5 pages read-only, splitting the VMA:
>+ *
>+ * RO RW
>+ * |-----------|-----------|
>+ * | unfaulted | unfaulted |
>+ * |-----------|-----------|
>+ */
>+ ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0);
>+ /*
>+ * Fault in the first of the last 5 pages so it gets an anon_vma and
>+ * thus the whole VMA becomes 'faulted':
>+ *
>+ * RO RW
>+ * |-----------|-----------|
>+ * | unfaulted | faulted |
>+ * |-----------|-----------|
>+ */
>+ ptr[5 * page_size] = 'x';
>+ /*
>+ * Now mprotect() the RW region read-only, we should merge (though for
>+ * ~15 years we did not! :):
>+ *
>+ * RO
>+ * |-----------------------|
>+ * | faulted |
>+ * |-----------------------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0);
>+
>+ /* Assert that the merge succeeded using PROCMAP_QUERY. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size);
>+}
>+
>+TEST_F(merge, mprotect_unfaulted_right)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ char *ptr;
>+
>+ /*
>+ * |-----------------------|
>+ * | unfaulted |
>+ * |-----------------------|
>+ */
>+ ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+ /*
>+ * Now make the last 5 pages read-only, splitting the VMA:
>+ *
>+ * RW RO
>+ * |-----------|-----------|
>+ * | unfaulted | unfaulted |
>+ * |-----------|-----------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0);
>+ /*
>+ * Fault in the first of the first 5 pages so it gets an anon_vma and
>+ * thus the whole VMA becomes 'faulted':
>+ *
>+ * RW RO
>+ * |-----------|-----------|
>+ * | faulted | unfaulted |
>+ * |-----------|-----------|
>+ */
>+ ptr[0] = 'x';
>+ /*
>+ * Now mprotect() the RW region read-only, we should merge:
>+ *
>+ * RO
>+ * |-----------------------|
>+ * | faulted |
>+ * |-----------------------|
>+ */
>+ ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0);
>+
>+ /* Assert that the merge succeeded using PROCMAP_QUERY. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size);
>+}
>+
>+TEST_F(merge, mprotect_unfaulted_both)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ char *ptr;
>+
>+ /*
>+ * |-----------------------|
>+ * | unfaulted |
>+ * |-----------------------|
>+ */
>+ ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+ /*
>+ * Now make the first and last 3 pages read-only, splitting the VMA:
>+ *
>+ * RO RW RO
>+ * |-----------|-----------|-----------|
>+ * | unfaulted | unfaulted | unfaulted |
>+ * |-----------|-----------|-----------|
>+ */
>+ ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0);
>+ ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0);
>+ /*
>+ * Fault in the first of the middle 3 pages so it gets an anon_vma and
>+ * thus the whole VMA becomes 'faulted':
>+ *
>+ * RO RW RO
>+ * |-----------|-----------|-----------|
>+ * | unfaulted | faulted | unfaulted |
>+ * |-----------|-----------|-----------|
>+ */
>+ ptr[3 * page_size] = 'x';
>+ /*
>+ * Now mprotect() the RW region read-only, we should merge:
>+ *
>+ * RO
>+ * |-----------------------|
>+ * | faulted |
>+ * |-----------------------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0);
>+
>+ /* Assert that the merge succeeded using PROCMAP_QUERY. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size);
>+}
>+
>+TEST_F(merge, mprotect_faulted_left_unfaulted_right)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ char *ptr;
>+
>+ /*
>+ * |-----------------------|
>+ * | unfaulted |
>+ * |-----------------------|
>+ */
>+ ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+ /*
>+ * Now make the last 3 pages read-only, splitting the VMA:
>+ *
>+ * RW RO
>+ * |-----------------------|-----------|
>+ * | unfaulted | unfaulted |
>+ * |-----------------------|-----------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0);
>+ /*
>+ * Fault in the first of the first 6 pages so it gets an anon_vma and
>+ * thus the whole VMA becomes 'faulted':
>+ *
>+ * RW RO
>+ * |-----------------------|-----------|
>+ * | unfaulted | unfaulted |
^^^
According to your previous comment convention, the comment here describe the
result after ptr[0] = 'x'.
faulted is the correct description here?
>+ * |-----------------------|-----------|
>+ */
>+ ptr[0] = 'x';
>+ /*
>+ * Now make the first 3 pages read-only, splitting the VMA:
>+ *
>+ * RO RW RO
>+ * |-----------|-----------|-----------|
>+ * | faulted | faulted | unfaulted |
>+ * |-----------|-----------|-----------|
>+ */
>+ ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0);
>+ /*
>+ * Now mprotect() the RW region read-only, we should merge:
>+ *
>+ * RO
>+ * |-----------------------|
>+ * | faulted |
>+ * |-----------------------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0);
>+
>+ /* Assert that the merge succeeded using PROCMAP_QUERY. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size);
>+}
>+
>+TEST_F(merge, mprotect_unfaulted_left_faulted_right)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ char *ptr;
>+
>+ /*
>+ * |-----------------------|
>+ * | unfaulted |
>+ * |-----------------------|
>+ */
>+ ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+ /*
>+ * Now make the first 3 pages read-only, splitting the VMA:
>+ *
>+ * RO RW
>+ * |-----------|-----------------------|
>+ * | unfaulted | unfaulted |
>+ * |-----------|-----------------------|
>+ */
>+ ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0);
>+ /*
>+ * FAult in the first of the last 6 pages so it gets an anon_vma and
^
s/A/a/
>+ * thus the whole VMA becomes 'faulted':
>+ *
>+ * RO RW
>+ * |-----------|-----------------------|
>+ * | unfaulted | faulted |
>+ * |-----------|-----------------------|
>+ */
>+ ptr[3 * page_size] = 'x';
>+ /*
>+ * Now make the last 3 pages read-only, splitting the VMA:
>+ *
>+ * RO RW RO
>+ * |-----------|-----------|-----------|
>+ * | unfaulted | faulted | faulted |
>+ * |-----------|-----------|-----------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0);
>+ /*
>+ * Now mprotect() the RW region read-only, we should merge:
>+ *
>+ * RO
>+ * |-----------------------|
>+ * | faulted |
>+ * |-----------------------|
>+ */
>+ ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0);
>+
>+ /* Assert that the merge succeeded using PROCMAP_QUERY. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size);
>+}
>+
>+TEST_F(merge, forked_target_vma)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ pid_t pid;
>+ char *ptr, *ptr2;
>+ int i;
>+
>+ /*
>+ * |-----------|
>+ * | unfaulted |
>+ * |-----------|
>+ */
>+ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+
>+ /*
>+ * Fault in process.
>+ *
>+ * |-----------|
>+ * | faulted |
>+ * |-----------|
>+ */
>+ ptr[0] = 'x';
>+
>+ pid = fork();
>+ ASSERT_NE(pid, -1);
>+
>+ if (pid != 0) {
>+ wait(NULL);
>+ return;
>+ }
>+
>+ /* Child process below: */
>+
>+ /* Reopen for child. */
>+ ASSERT_EQ(close_procmap(&self->procmap), 0);
>+ ASSERT_EQ(open_self_procmap(&self->procmap), 0);
>+
>+ /* unCOWing everything does not cause the AVC to go away. */
^^^
Before ptr[i] = 'x', we have unCOWed pages in vma. What we are doing here is
COWing, right?
>+ for (i = 0; i < 5 * page_size; i += page_size)
>+ ptr[i] = 'x';
>+
>+ /*
>+ * Map in adjacent VMA in child.
>+ *
>+ * forked
>+ * |-----------|-----------|
>+ * | faulted | unfaulted |
>+ * |-----------|-----------|
>+ * ptr ptr2
>+ */
>+ ptr2 = mmap(&ptr[5 * page_size], 5 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
>+ ASSERT_NE(ptr2, MAP_FAILED);
>+
>+ /* Make sure not merged. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size);
>+}
>+
>+TEST_F(merge, forked_source_vma)
>+{
>+ unsigned int page_size = self->page_size;
>+ char *carveout = self->carveout;
>+ struct procmap_fd *procmap = &self->procmap;
>+ pid_t pid;
>+ char *ptr, *ptr2;
>+ int i;
>+
>+ /*
>+ * |............|-----------|
>+ * | <unmapped> | unfaulted |
>+ * |............|-----------|
I am not sure "unmapped" is correct here. The range has already been mapped by
FIXTURE_SETUP(merge).
>+ */
>+ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr, MAP_FAILED);
>+
>+ /*
>+ * Fault in process.
>+ *
>+ * |............||-----------|
>+ * | <unmapped> || faulted |
>+ * |............||-----------|
^
Extra line here?
>+ */
>+ ptr[0] = 'x';
>+
>+ pid = fork();
>+ ASSERT_NE(pid, -1);
>+
>+ if (pid != 0) {
>+ wait(NULL);
>+ return;
>+ }
>+
>+ /* Child process below: */
>+
>+ /* Reopen for child. */
>+ ASSERT_EQ(close_procmap(&self->procmap), 0);
>+ ASSERT_EQ(open_self_procmap(&self->procmap), 0);
>+
>+ /* unCOWing everything does not cause the AVC to go away. */
Same as above.
>+ for (i = 0; i < 5 * page_size; i += page_size)
>+ ptr[i] = 'x';
>+
>+ /*
>+ * Map in adjacent VMA in child, ptr2 before ptr, but incompatible.
>+ *
>+ * RWX forked RW
>+ * |-----------|-----------|
>+ * | unfaulted | faulted |
>+ * |-----------|-----------|
>+ * ptr2 ptr
>+ */
>+ ptr2 = mmap(&carveout[6 * page_size], 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC,
>+ MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
>+ ASSERT_NE(ptr2, MAP_FAILED);
>+
I think pt2 is after ptr. Do I miss something?
>+
>+ /* Make sure not merged. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr2));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size);
>+
>+ /*
>+ * Now mprotect forked region to RWX so it becomes the source for the
>+ * merge to unfaulted region:
>+ *
>+ * RWX forked RWX
>+ * |-----------|-----------|
>+ * | unfaulted | faulted |
>+ * |-----------|-----------|
>+ * ptr2 ptr
>+ */
>+ ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
>+ /* Again, make sure not merged. */
>+ ASSERT_TRUE(find_vma_procmap(procmap, ptr2));
>+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2);
>+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size);
>+}
>+
>+TEST_HARNESS_MAIN
>diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
>index 9aff33b10999..0b2f8bb91433 100755
>--- a/tools/testing/selftests/mm/run_vmtests.sh
>+++ b/tools/testing/selftests/mm/run_vmtests.sh
>@@ -421,6 +421,8 @@ CATEGORY="madv_guard" run_test ./guard-regions
> # MADV_POPULATE_READ and MADV_POPULATE_WRITE tests
> CATEGORY="madv_populate" run_test ./madv_populate
>
>+CATEGORY="vma_merge" run_test ./merge
>+
./run_vmtests.sh -h would show a list of categories.
How about add the new category "vma_merge" into the list.
> if [ -x ./memfd_secret ]
> then
> (echo 0 > /proc/sys/kernel/yama/ptrace_scope 2>&1) | tap_prefix
>--
>2.48.1
>
--
Wei Yang
Help you, Help me
next prev parent reply other threads:[~2025-04-07 2:55 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-17 21:15 [PATCH 0/3] fix incorrectly disallowed anonymous VMA merges Lorenzo Stoakes
2025-03-17 21:15 ` [PATCH 1/3] mm/vma: " Lorenzo Stoakes
2025-04-04 12:53 ` Wei Yang
2025-04-04 13:04 ` Lorenzo Stoakes
2025-04-04 23:32 ` Wei Yang
2025-04-07 10:24 ` Lorenzo Stoakes
2025-04-07 16:44 ` Lorenzo Stoakes
2025-03-17 21:15 ` [PATCH 2/3] tools/testing: add PROCMAP_QUERY helper functions in mm self tests Lorenzo Stoakes
2025-03-18 15:18 ` Lorenzo Stoakes
2025-04-07 2:42 ` Wei Yang
2025-03-17 21:15 ` [PATCH 3/3] tools/testing/selftests: assert that anon merge cases behave as expected Lorenzo Stoakes
2025-04-07 2:54 ` Wei Yang [this message]
2025-04-07 11:02 ` Lorenzo Stoakes
2025-04-07 12:09 ` Wei Yang
2025-03-20 13:33 ` [PATCH 0/3] fix incorrectly disallowed anonymous VMA merges Yeoreum Yun
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250407025455.67nhchndedotn57g@master \
--to=richard.weiyang@gmail.com \
--cc=Liam.Howlett@oracle.com \
--cc=akpm@linux-foundation.org \
--cc=david@redhat.com \
--cc=jannh@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=lorenzo.stoakes@oracle.com \
--cc=riel@surriel.com \
--cc=surenb@google.com \
--cc=vbabka@suse.cz \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.