diff options
Diffstat (limited to 'mm/vmalloc.c')
| -rw-r--r-- | mm/vmalloc.c | 65 | 
1 files changed, 48 insertions, 17 deletions
diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 5016f19e166..56faf3163ee 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -1253,18 +1253,22 @@ EXPORT_SYMBOL_GPL(map_vm_area);  DEFINE_RWLOCK(vmlist_lock);  struct vm_struct *vmlist; -static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, +static void setup_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,  			      unsigned long flags, void *caller)  { -	struct vm_struct *tmp, **p; -  	vm->flags = flags;  	vm->addr = (void *)va->va_start;  	vm->size = va->va_end - va->va_start;  	vm->caller = caller;  	va->private = vm;  	va->flags |= VM_VM_AREA; +} + +static void insert_vmalloc_vmlist(struct vm_struct *vm) +{ +	struct vm_struct *tmp, **p; +	vm->flags &= ~VM_UNLIST;  	write_lock(&vmlist_lock);  	for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {  		if (tmp->addr >= vm->addr) @@ -1275,6 +1279,13 @@ static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,  	write_unlock(&vmlist_lock);  } +static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, +			      unsigned long flags, void *caller) +{ +	setup_vmalloc_vm(vm, va, flags, caller); +	insert_vmalloc_vmlist(vm); +} +  static struct vm_struct *__get_vm_area_node(unsigned long size,  		unsigned long align, unsigned long flags, unsigned long start,  		unsigned long end, int node, gfp_t gfp_mask, void *caller) @@ -1313,7 +1324,18 @@ static struct vm_struct *__get_vm_area_node(unsigned long size,  		return NULL;  	} -	insert_vmalloc_vm(area, va, flags, caller); +	/* +	 * When this function is called from __vmalloc_node_range, +	 * we do not add vm_struct to vmlist here to avoid +	 * accessing uninitialized members of vm_struct such as +	 * pages and nr_pages fields. They will be set later. +	 * To distinguish it from others, we use a VM_UNLIST flag. +	 */ +	if (flags & VM_UNLIST) +		setup_vmalloc_vm(area, va, flags, caller); +	else +		insert_vmalloc_vm(area, va, flags, caller); +  	return area;  } @@ -1381,17 +1403,20 @@ struct vm_struct *remove_vm_area(const void *addr)  	va = find_vmap_area((unsigned long)addr);  	if (va && va->flags & VM_VM_AREA) {  		struct vm_struct *vm = va->private; -		struct vm_struct *tmp, **p; -		/* -		 * remove from list and disallow access to this vm_struct -		 * before unmap. (address range confliction is maintained by -		 * vmap.) -		 */ -		write_lock(&vmlist_lock); -		for (p = &vmlist; (tmp = *p) != vm; p = &tmp->next) -			; -		*p = tmp->next; -		write_unlock(&vmlist_lock); + +		if (!(vm->flags & VM_UNLIST)) { +			struct vm_struct *tmp, **p; +			/* +			 * remove from list and disallow access to +			 * this vm_struct before unmap. (address range +			 * confliction is maintained by vmap.) +			 */ +			write_lock(&vmlist_lock); +			for (p = &vmlist; (tmp = *p) != vm; p = &tmp->next) +				; +			*p = tmp->next; +			write_unlock(&vmlist_lock); +		}  		vmap_debug_free_range(va->va_start, va->va_end);  		free_unmap_vmap_area(va); @@ -1602,8 +1627,8 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align,  	if (!size || (size >> PAGE_SHIFT) > totalram_pages)  		return NULL; -	area = __get_vm_area_node(size, align, VM_ALLOC, start, end, node, -				  gfp_mask, caller); +	area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST, +				  start, end, node, gfp_mask, caller);  	if (!area)  		return NULL; @@ -1611,6 +1636,12 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align,  	addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);  	/* +	 * In this function, newly allocated vm_struct is not added +	 * to vmlist at __get_vm_area_node(). so, it is added here. +	 */ +	insert_vmalloc_vmlist(area); + +	/*  	 * A ref_count = 3 is needed because the vm_struct and vmap_area  	 * structures allocated in the __get_vm_area_node() function contain  	 * references to the virtual address of the vmalloc'ed block.  |