diff options
Diffstat (limited to 'mm/memory.c')
| -rw-r--r-- | mm/memory.c | 54 | 
1 files changed, 45 insertions, 9 deletions
| diff --git a/mm/memory.c b/mm/memory.c index 2ed2267439d..71b161b73bb 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -2623,7 +2623,7 @@ static int do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma,  		unsigned int flags, pte_t orig_pte)  {  	spinlock_t *ptl; -	struct page *page; +	struct page *page, *swapcache = NULL;  	swp_entry_t entry;  	pte_t pte;  	struct mem_cgroup *ptr = NULL; @@ -2679,10 +2679,23 @@ static int do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma,  	lock_page(page);  	delayacct_clear_flag(DELAYACCT_PF_SWAPIN); -	page = ksm_might_need_to_copy(page, vma, address); -	if (!page) { -		ret = VM_FAULT_OOM; -		goto out; +	/* +	 * Make sure try_to_free_swap didn't release the swapcache +	 * from under us. The page pin isn't enough to prevent that. +	 */ +	if (unlikely(!PageSwapCache(page))) +		goto out_page; + +	if (ksm_might_need_to_copy(page, vma, address)) { +		swapcache = page; +		page = ksm_does_need_to_copy(page, vma, address); + +		if (unlikely(!page)) { +			ret = VM_FAULT_OOM; +			page = swapcache; +			swapcache = NULL; +			goto out_page; +		}  	}  	if (mem_cgroup_try_charge_swapin(mm, page, GFP_KERNEL, &ptr)) { @@ -2735,6 +2748,18 @@ static int do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma,  	if (vm_swap_full() || (vma->vm_flags & VM_LOCKED) || PageMlocked(page))  		try_to_free_swap(page);  	unlock_page(page); +	if (swapcache) { +		/* +		 * Hold the lock to avoid the swap entry to be reused +		 * until we take the PT lock for the pte_same() check +		 * (to avoid false positives from pte_same). For +		 * further safety release the lock after the swap_free +		 * so that the swap count won't change under a +		 * parallel locked swapcache. +		 */ +		unlock_page(swapcache); +		page_cache_release(swapcache); +	}  	if (flags & FAULT_FLAG_WRITE) {  		ret |= do_wp_page(mm, vma, address, page_table, pmd, ptl, pte); @@ -2756,15 +2781,17 @@ out_page:  	unlock_page(page);  out_release:  	page_cache_release(page); +	if (swapcache) { +		unlock_page(swapcache); +		page_cache_release(swapcache); +	}  	return ret;  }  /* - * This is like a special single-page "expand_downwards()", - * except we must first make sure that 'address-PAGE_SIZE' + * This is like a special single-page "expand_{down|up}wards()", + * except we must first make sure that 'address{-|+}PAGE_SIZE'   * doesn't hit another vma. - * - * The "find_vma()" will do the right thing even if we wrap   */  static inline int check_stack_guard_page(struct vm_area_struct *vma, unsigned long address)  { @@ -2783,6 +2810,15 @@ static inline int check_stack_guard_page(struct vm_area_struct *vma, unsigned lo  		expand_stack(vma, address - PAGE_SIZE);  	} +	if ((vma->vm_flags & VM_GROWSUP) && address + PAGE_SIZE == vma->vm_end) { +		struct vm_area_struct *next = vma->vm_next; + +		/* As VM_GROWSDOWN but s/below/above/ */ +		if (next && next->vm_start == address + PAGE_SIZE) +			return next->vm_flags & VM_GROWSUP ? 0 : -ENOMEM; + +		expand_upwards(vma, address + PAGE_SIZE); +	}  	return 0;  } | 
