diff mbox

[RFC,03/19] mm: Add vm_replace_page()

Message ID 20170616134348.17725-4-alazar@bitdefender.com (mailing list archive)
State New, archived
Headers show

Commit Message

Adalbert Lazăr June 16, 2017, 1:43 p.m. UTC
From: Mihai Dontu <mdontu@bitdefender.com>

This function is used to get two processes to share a page. It's inspired
by replace_page() from KSM.

Signed-off-by: Mihai Dontu <mdontu@bitdefender.com>
---
 include/linux/mm.h |  1 +
 mm/memory.c        | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)
diff mbox

Patch

diff --git a/include/linux/mm.h b/include/linux/mm.h
index b892e95d4929..9cd088ef9d0c 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2296,6 +2296,7 @@  int vm_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
 			unsigned long pfn, pgprot_t pgprot);
 int vm_insert_mixed(struct vm_area_struct *vma, unsigned long addr,
 			pfn_t pfn);
+int vm_replace_page(struct vm_area_struct *vma, struct page *page);
 int vm_iomap_memory(struct vm_area_struct *vma, phys_addr_t start, unsigned long len);
 
 
diff --git a/mm/memory.c b/mm/memory.c
index 2e65df1831d9..ae7716ffe6e9 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1776,6 +1776,75 @@  int vm_insert_mixed(struct vm_area_struct *vma, unsigned long addr,
 }
 EXPORT_SYMBOL(vm_insert_mixed);
 
+/**
+ * vm_replace_page - given a page-sized VMA, drop the currently
+ *                   referenced page and place the specified one
+ *                   in its stead
+ * @vma: the remote user VMA in which the replace takes place
+ * @page: the page with which we make the replacement
+ */
+int vm_replace_page(struct vm_area_struct *vma, struct page *page)
+{
+	unsigned long mmun_start;
+	unsigned long mmun_end;
+	struct mm_struct *mm = vma->vm_mm;
+	pmd_t *pmd;
+	struct page *old_page;
+	pte_t *ptep;
+	spinlock_t *ptl;
+
+	/* Make sure the area is page aligned */
+	if (vma->vm_start % PAGE_SIZE)
+		return -EINVAL;
+
+	/* Make sure the area is page-sized */
+	if ((vma->vm_end - vma->vm_start) != PAGE_SIZE)
+		return -EINVAL;
+
+	old_page = follow_page(vma, vma->vm_start, 0);
+	if (IS_ERR_OR_NULL(old_page))
+		return old_page ? PTR_ERR(old_page) : -ENOENT;
+
+	pmd = mm_find_pmd(mm, vma->vm_start);
+	if (!pmd)
+		return -ENOENT;
+
+	mmun_start = vma->vm_start;
+	mmun_end = mmun_start + PAGE_SIZE;
+	mmu_notifier_invalidate_range_start(mm, mmun_start, mmun_end);
+
+	ptep = pte_offset_map_lock(mm, pmd, vma->vm_start, &ptl);
+
+	get_page(page);
+	page_add_anon_rmap(page, vma, vma->vm_start, false);
+
+	flush_cache_page(vma, vma->vm_start, pte_pfn(*ptep));
+	ptep_clear_flush_notify(vma, vma->vm_start, ptep);
+
+	/*
+	 * TODO: Find why we can't do:
+	 *       set_pte_at_notify(mm, vma->vm_start, ptep,
+	 *                         mk_pte(page, vma->vm_page_prot))
+	 */
+	set_pte_at_notify(mm, vma->vm_start, ptep,
+			  mk_pte(page,
+				 __pgprot(_PAGE_PRESENT | _PAGE_RW |
+					  _PAGE_BIT_NX)));
+
+	/* Drop the old page */
+	page_remove_rmap(old_page, false);
+	if (!page_mapped(old_page))
+		try_to_free_swap(old_page);
+	put_page(old_page);
+
+	pte_unmap_unlock(ptep, ptl);
+
+	mmu_notifier_invalidate_range_end(mm, mmun_start, mmun_end);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(vm_replace_page);
+
 /*
  * maps a range of physical memory into the requested pages. the old
  * mappings are removed. any references to nonexistent pages results