From: Mihai Dontu <mdontu@xxxxxxxxxxxxxxx> 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@xxxxxxxxxxxxxxx> --- include/linux/mm.h | 1 + mm/memory.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) 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 -- 2.12.2