diff options
Diffstat (limited to 'mm/vmalloc.c')
| -rw-r--r-- | mm/vmalloc.c | 196 | 
1 files changed, 97 insertions, 99 deletions
diff --git a/mm/vmalloc.c b/mm/vmalloc.c index f2481cb4e6b2..a5584384eabc 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -365,7 +365,7 @@ static struct vmap_area *alloc_vmap_area(unsigned long size,  	BUG_ON(offset_in_page(size));  	BUG_ON(!is_power_of_2(align)); -	might_sleep_if(gfpflags_allow_blocking(gfp_mask)); +	might_sleep();  	va = kmalloc_node(sizeof(struct vmap_area),  			gfp_mask & GFP_RECLAIM_MASK, node); @@ -601,6 +601,13 @@ static unsigned long lazy_max_pages(void)  static atomic_t vmap_lazy_nr = ATOMIC_INIT(0); +/* + * Serialize vmap purging.  There is no actual criticial section protected + * by this look, but we want to avoid concurrent calls for performance + * reasons and to make the pcpu_get_vm_areas more deterministic. + */ +static DEFINE_MUTEX(vmap_purge_lock); +  /* for per-CPU blocks */  static void purge_fragmented_blocks_allcpus(void); @@ -615,59 +622,40 @@ void set_iounmap_nonlazy(void)  /*   * Purges all lazily-freed vmap areas. - * - * If sync is 0 then don't purge if there is already a purge in progress. - * If force_flush is 1, then flush kernel TLBs between *start and *end even - * if we found no lazy vmap areas to unmap (callers can use this to optimise - * their own TLB flushing). - * Returns with *start = min(*start, lowest purged address) - *              *end = max(*end, highest purged address)   */ -static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end, -					int sync, int force_flush) +static bool __purge_vmap_area_lazy(unsigned long start, unsigned long end)  { -	static DEFINE_SPINLOCK(purge_lock);  	struct llist_node *valist;  	struct vmap_area *va;  	struct vmap_area *n_va; -	int nr = 0; +	bool do_free = false; -	/* -	 * If sync is 0 but force_flush is 1, we'll go sync anyway but callers -	 * should not expect such behaviour. This just simplifies locking for -	 * the case that isn't actually used at the moment anyway. -	 */ -	if (!sync && !force_flush) { -		if (!spin_trylock(&purge_lock)) -			return; -	} else -		spin_lock(&purge_lock); - -	if (sync) -		purge_fragmented_blocks_allcpus(); +	lockdep_assert_held(&vmap_purge_lock);  	valist = llist_del_all(&vmap_purge_list);  	llist_for_each_entry(va, valist, purge_list) { -		if (va->va_start < *start) -			*start = va->va_start; -		if (va->va_end > *end) -			*end = va->va_end; -		nr += (va->va_end - va->va_start) >> PAGE_SHIFT; +		if (va->va_start < start) +			start = va->va_start; +		if (va->va_end > end) +			end = va->va_end; +		do_free = true;  	} -	if (nr) -		atomic_sub(nr, &vmap_lazy_nr); +	if (!do_free) +		return false; -	if (nr || force_flush) -		flush_tlb_kernel_range(*start, *end); +	flush_tlb_kernel_range(start, end); -	if (nr) { -		spin_lock(&vmap_area_lock); -		llist_for_each_entry_safe(va, n_va, valist, purge_list) -			__free_vmap_area(va); -		spin_unlock(&vmap_area_lock); +	spin_lock(&vmap_area_lock); +	llist_for_each_entry_safe(va, n_va, valist, purge_list) { +		int nr = (va->va_end - va->va_start) >> PAGE_SHIFT; + +		__free_vmap_area(va); +		atomic_sub(nr, &vmap_lazy_nr); +		cond_resched_lock(&vmap_area_lock);  	} -	spin_unlock(&purge_lock); +	spin_unlock(&vmap_area_lock); +	return true;  }  /* @@ -676,9 +664,10 @@ static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end,   */  static void try_purge_vmap_area_lazy(void)  { -	unsigned long start = ULONG_MAX, end = 0; - -	__purge_vmap_area_lazy(&start, &end, 0, 0); +	if (mutex_trylock(&vmap_purge_lock)) { +		__purge_vmap_area_lazy(ULONG_MAX, 0); +		mutex_unlock(&vmap_purge_lock); +	}  }  /* @@ -686,9 +675,10 @@ static void try_purge_vmap_area_lazy(void)   */  static void purge_vmap_area_lazy(void)  { -	unsigned long start = ULONG_MAX, end = 0; - -	__purge_vmap_area_lazy(&start, &end, 1, 0); +	mutex_lock(&vmap_purge_lock); +	purge_fragmented_blocks_allcpus(); +	__purge_vmap_area_lazy(ULONG_MAX, 0); +	mutex_unlock(&vmap_purge_lock);  }  /* @@ -711,22 +701,13 @@ static void free_vmap_area_noflush(struct vmap_area *va)  }  /* - * Free and unmap a vmap area, caller ensuring flush_cache_vunmap had been - * called for the correct range previously. - */ -static void free_unmap_vmap_area_noflush(struct vmap_area *va) -{ -	unmap_vmap_area(va); -	free_vmap_area_noflush(va); -} - -/*   * Free and unmap a vmap area   */  static void free_unmap_vmap_area(struct vmap_area *va)  {  	flush_cache_vunmap(va->va_start, va->va_end); -	free_unmap_vmap_area_noflush(va); +	unmap_vmap_area(va); +	free_vmap_area_noflush(va);  }  static struct vmap_area *find_vmap_area(unsigned long addr) @@ -740,16 +721,6 @@ static struct vmap_area *find_vmap_area(unsigned long addr)  	return va;  } -static void free_unmap_vmap_area_addr(unsigned long addr) -{ -	struct vmap_area *va; - -	va = find_vmap_area(addr); -	BUG_ON(!va); -	free_unmap_vmap_area(va); -} - -  /*** Per cpu kva allocator ***/  /* @@ -1070,6 +1041,8 @@ void vm_unmap_aliases(void)  	if (unlikely(!vmap_initialized))  		return; +	might_sleep(); +  	for_each_possible_cpu(cpu) {  		struct vmap_block_queue *vbq = &per_cpu(vmap_block_queue, cpu);  		struct vmap_block *vb; @@ -1094,7 +1067,11 @@ void vm_unmap_aliases(void)  		rcu_read_unlock();  	} -	__purge_vmap_area_lazy(&start, &end, 1, flush); +	mutex_lock(&vmap_purge_lock); +	purge_fragmented_blocks_allcpus(); +	if (!__purge_vmap_area_lazy(start, end) && flush) +		flush_tlb_kernel_range(start, end); +	mutex_unlock(&vmap_purge_lock);  }  EXPORT_SYMBOL_GPL(vm_unmap_aliases); @@ -1107,7 +1084,9 @@ void vm_unmap_ram(const void *mem, unsigned int count)  {  	unsigned long size = (unsigned long)count << PAGE_SHIFT;  	unsigned long addr = (unsigned long)mem; +	struct vmap_area *va; +	might_sleep();  	BUG_ON(!addr);  	BUG_ON(addr < VMALLOC_START);  	BUG_ON(addr > VMALLOC_END); @@ -1116,10 +1095,14 @@ void vm_unmap_ram(const void *mem, unsigned int count)  	debug_check_no_locks_freed(mem, size);  	vmap_debug_free_range(addr, addr+size); -	if (likely(count <= VMAP_MAX_ALLOC)) +	if (likely(count <= VMAP_MAX_ALLOC)) {  		vb_free(mem, size); -	else -		free_unmap_vmap_area_addr(addr); +		return; +	} + +	va = find_vmap_area(addr); +	BUG_ON(!va); +	free_unmap_vmap_area(va);  }  EXPORT_SYMBOL(vm_unmap_ram); @@ -1455,6 +1438,8 @@ struct vm_struct *remove_vm_area(const void *addr)  {  	struct vmap_area *va; +	might_sleep(); +  	va = find_vmap_area((unsigned long)addr);  	if (va && va->flags & VM_VM_AREA) {  		struct vm_struct *vm = va->vm; @@ -1510,7 +1495,39 @@ static void __vunmap(const void *addr, int deallocate_pages)  	kfree(area);  	return;  } -  + +static inline void __vfree_deferred(const void *addr) +{ +	/* +	 * Use raw_cpu_ptr() because this can be called from preemptible +	 * context. Preemption is absolutely fine here, because the llist_add() +	 * implementation is lockless, so it works even if we are adding to +	 * nother cpu's list.  schedule_work() should be fine with this too. +	 */ +	struct vfree_deferred *p = raw_cpu_ptr(&vfree_deferred); + +	if (llist_add((struct llist_node *)addr, &p->list)) +		schedule_work(&p->wq); +} + +/** + *	vfree_atomic  -  release memory allocated by vmalloc() + *	@addr:		memory base address + * + *	This one is just like vfree() but can be called in any atomic context + *	except NMIs. + */ +void vfree_atomic(const void *addr) +{ +	BUG_ON(in_nmi()); + +	kmemleak_free(addr); + +	if (!addr) +		return; +	__vfree_deferred(addr); +} +  /**   *	vfree  -  release memory allocated by vmalloc()   *	@addr:		memory base address @@ -1533,11 +1550,9 @@ void vfree(const void *addr)  	if (!addr)  		return; -	if (unlikely(in_interrupt())) { -		struct vfree_deferred *p = this_cpu_ptr(&vfree_deferred); -		if (llist_add((struct llist_node *)addr, &p->list)) -			schedule_work(&p->wq); -	} else +	if (unlikely(in_interrupt())) +		__vfree_deferred(addr); +	else  		__vunmap(addr, 1);  }  EXPORT_SYMBOL(vfree); @@ -2574,32 +2589,13 @@ void pcpu_free_vm_areas(struct vm_struct **vms, int nr_vms)  static void *s_start(struct seq_file *m, loff_t *pos)  	__acquires(&vmap_area_lock)  { -	loff_t n = *pos; -	struct vmap_area *va; -  	spin_lock(&vmap_area_lock); -	va = list_first_entry(&vmap_area_list, typeof(*va), list); -	while (n > 0 && &va->list != &vmap_area_list) { -		n--; -		va = list_next_entry(va, list); -	} -	if (!n && &va->list != &vmap_area_list) -		return va; - -	return NULL; - +	return seq_list_start(&vmap_area_list, *pos);  }  static void *s_next(struct seq_file *m, void *p, loff_t *pos)  { -	struct vmap_area *va = p, *next; - -	++*pos; -	next = list_next_entry(va, list); -	if (&next->list != &vmap_area_list) -		return next; - -	return NULL; +	return seq_list_next(p, &vmap_area_list, pos);  }  static void s_stop(struct seq_file *m, void *p) @@ -2634,9 +2630,11 @@ static void show_numa_info(struct seq_file *m, struct vm_struct *v)  static int s_show(struct seq_file *m, void *p)  { -	struct vmap_area *va = p; +	struct vmap_area *va;  	struct vm_struct *v; +	va = list_entry(p, struct vmap_area, list); +  	/*  	 * s_show can encounter race with remove_vm_area, !VM_VM_AREA on  	 * behalf of vmap area is being tear down or vm_map_ram allocation.  |