diff options
Diffstat (limited to 'mm/memory.c')
| -rw-r--r-- | mm/memory.c | 112 | 
1 files changed, 78 insertions, 34 deletions
diff --git a/mm/memory.c b/mm/memory.c index cf6873e91c6..4126dd16778 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1971,6 +1971,15 @@ static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,  				ret = tmp;  				goto unwritable_page;  			} +			if (unlikely(!(tmp & VM_FAULT_LOCKED))) { +				lock_page(old_page); +				if (!old_page->mapping) { +					ret = 0; /* retry the fault */ +					unlock_page(old_page); +					goto unwritable_page; +				} +			} else +				VM_BUG_ON(!PageLocked(old_page));  			/*  			 * Since we dropped the lock we need to revalidate @@ -1980,9 +1989,11 @@ static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,  			 */  			page_table = pte_offset_map_lock(mm, pmd, address,  							 &ptl); -			page_cache_release(old_page); -			if (!pte_same(*page_table, orig_pte)) +			if (!pte_same(*page_table, orig_pte)) { +				unlock_page(old_page); +				page_cache_release(old_page);  				goto unlock; +			}  			page_mkwrite = 1;  		} @@ -2094,9 +2105,6 @@ gotten:  unlock:  	pte_unmap_unlock(page_table, ptl);  	if (dirty_page) { -		if (vma->vm_file) -			file_update_time(vma->vm_file); -  		/*  		 * Yes, Virginia, this is actually required to prevent a race  		 * with clear_page_dirty_for_io() from clearing the page dirty @@ -2105,16 +2113,41 @@ unlock:  		 *  		 * do_no_page is protected similarly.  		 */ -		wait_on_page_locked(dirty_page); -		set_page_dirty_balance(dirty_page, page_mkwrite); +		if (!page_mkwrite) { +			wait_on_page_locked(dirty_page); +			set_page_dirty_balance(dirty_page, page_mkwrite); +		}  		put_page(dirty_page); +		if (page_mkwrite) { +			struct address_space *mapping = dirty_page->mapping; + +			set_page_dirty(dirty_page); +			unlock_page(dirty_page); +			page_cache_release(dirty_page); +			if (mapping)	{ +				/* +				 * Some device drivers do not set page.mapping +				 * but still dirty their pages +				 */ +				balance_dirty_pages_ratelimited(mapping); +			} +		} + +		/* file_update_time outside page_lock */ +		if (vma->vm_file) +			file_update_time(vma->vm_file);  	}  	return ret;  oom_free_new:  	page_cache_release(new_page);  oom: -	if (old_page) +	if (old_page) { +		if (page_mkwrite) { +			unlock_page(old_page); +			page_cache_release(old_page); +		}  		page_cache_release(old_page); +	}  	return VM_FAULT_OOM;  unwritable_page: @@ -2458,8 +2491,7 @@ static int do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma,  	if (mem_cgroup_try_charge_swapin(mm, page, GFP_KERNEL, &ptr)) {  		ret = VM_FAULT_OOM; -		unlock_page(page); -		goto out; +		goto out_page;  	}  	/* @@ -2521,6 +2553,7 @@ out:  out_nomap:  	mem_cgroup_cancel_charge_swapin(ptr);  	pte_unmap_unlock(page_table, ptl); +out_page:  	unlock_page(page);  	page_cache_release(page);  	return ret; @@ -2664,27 +2697,22 @@ static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma,  				int tmp;  				unlock_page(page); -				vmf.flags |= FAULT_FLAG_MKWRITE; +				vmf.flags = FAULT_FLAG_WRITE|FAULT_FLAG_MKWRITE;  				tmp = vma->vm_ops->page_mkwrite(vma, &vmf);  				if (unlikely(tmp &  					  (VM_FAULT_ERROR | VM_FAULT_NOPAGE))) {  					ret = tmp; -					anon = 1; /* no anon but release vmf.page */ -					goto out_unlocked; -				} -				lock_page(page); -				/* -				 * XXX: this is not quite right (racy vs -				 * invalidate) to unlock and relock the page -				 * like this, however a better fix requires -				 * reworking page_mkwrite locking API, which -				 * is better done later. -				 */ -				if (!page->mapping) { -					ret = 0; -					anon = 1; /* no anon but release vmf.page */ -					goto out; +					goto unwritable_page;  				} +				if (unlikely(!(tmp & VM_FAULT_LOCKED))) { +					lock_page(page); +					if (!page->mapping) { +						ret = 0; /* retry the fault */ +						unlock_page(page); +						goto unwritable_page; +					} +				} else +					VM_BUG_ON(!PageLocked(page));  				page_mkwrite = 1;  			}  		} @@ -2736,19 +2764,35 @@ static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma,  	pte_unmap_unlock(page_table, ptl);  out: -	unlock_page(vmf.page); -out_unlocked: -	if (anon) -		page_cache_release(vmf.page); -	else if (dirty_page) { -		if (vma->vm_file) -			file_update_time(vma->vm_file); +	if (dirty_page) { +		struct address_space *mapping = page->mapping; -		set_page_dirty_balance(dirty_page, page_mkwrite); +		if (set_page_dirty(dirty_page)) +			page_mkwrite = 1; +		unlock_page(dirty_page);  		put_page(dirty_page); +		if (page_mkwrite && mapping) { +			/* +			 * Some device drivers do not set page.mapping but still +			 * dirty their pages +			 */ +			balance_dirty_pages_ratelimited(mapping); +		} + +		/* file_update_time outside page_lock */ +		if (vma->vm_file) +			file_update_time(vma->vm_file); +	} else { +		unlock_page(vmf.page); +		if (anon) +			page_cache_release(vmf.page);  	}  	return ret; + +unwritable_page: +	page_cache_release(page); +	return ret;  }  static int do_linear_fault(struct mm_struct *mm, struct vm_area_struct *vma,  |