mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	locking/lockdep: Free lock classes that are no longer in use
Instead of leaving lock classes that are no longer in use in the lock_classes array, reuse entries from that array that are no longer in use. Maintain a linked list of free lock classes with list head 'free_lock_class'. Only add freed lock classes to the free_lock_classes list after a grace period to avoid that a lock_classes[] element would be reused while an RCU reader is accessing it. Since the lockdep selftests run in a context where sleeping is not allowed and since the selftests require that lock resetting/zapping works with debug_locks off, make the behavior of lockdep_free_key_range() and lockdep_reset_lock() depend on whether or not these are called from the context of the lockdep selftests. Thanks to Peter for having shown how to modify get_pending_free() such that that function does not have to sleep. Signed-off-by: Bart Van Assche <bvanassche@acm.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Johannes Berg <johannes@sipsolutions.net> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Waiman Long <longman@redhat.com> Cc: Will Deacon <will.deacon@arm.com> Cc: johannes.berg@intel.com Cc: tj@kernel.org Link: https://lkml.kernel.org/r/20190214230058.196511-12-bvanassche@acm.org Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
		
							parent
							
								
									29fc33fb72
								
							
						
					
					
						commit
						a0b0fd53e1
					
				
					 2 changed files with 354 additions and 51 deletions
				
			
		|  | @ -63,7 +63,8 @@ extern struct lock_class_key __lockdep_no_validate__; | ||||||
| #define LOCKSTAT_POINTS		4 | #define LOCKSTAT_POINTS		4 | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * The lock-class itself: |  * The lock-class itself. The order of the structure members matters. | ||||||
|  |  * reinit_class() zeroes the key member and all subsequent members. | ||||||
|  */ |  */ | ||||||
| struct lock_class { | struct lock_class { | ||||||
| 	/*
 | 	/*
 | ||||||
|  | @ -72,7 +73,9 @@ struct lock_class { | ||||||
| 	struct hlist_node		hash_entry; | 	struct hlist_node		hash_entry; | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * global list of all lock-classes: | 	 * Entry in all_lock_classes when in use. Entry in free_lock_classes | ||||||
|  | 	 * when not in use. Instances that are being freed are on one of the | ||||||
|  | 	 * zapped_classes lists. | ||||||
| 	 */ | 	 */ | ||||||
| 	struct list_head		lock_entry; | 	struct list_head		lock_entry; | ||||||
| 
 | 
 | ||||||
|  | @ -104,7 +107,7 @@ struct lock_class { | ||||||
| 	unsigned long			contention_point[LOCKSTAT_POINTS]; | 	unsigned long			contention_point[LOCKSTAT_POINTS]; | ||||||
| 	unsigned long			contending_point[LOCKSTAT_POINTS]; | 	unsigned long			contending_point[LOCKSTAT_POINTS]; | ||||||
| #endif | #endif | ||||||
| }; | } __no_randomize_layout; | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_LOCK_STAT | #ifdef CONFIG_LOCK_STAT | ||||||
| struct lock_time { | struct lock_time { | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| #include <linux/random.h> | #include <linux/random.h> | ||||||
| #include <linux/jhash.h> | #include <linux/jhash.h> | ||||||
| #include <linux/nmi.h> | #include <linux/nmi.h> | ||||||
|  | #include <linux/rcupdate.h> | ||||||
| 
 | 
 | ||||||
| #include <asm/sections.h> | #include <asm/sections.h> | ||||||
| 
 | 
 | ||||||
|  | @ -135,8 +136,8 @@ static struct lock_list list_entries[MAX_LOCKDEP_ENTRIES]; | ||||||
| /*
 | /*
 | ||||||
|  * All data structures here are protected by the global debug_lock. |  * All data structures here are protected by the global debug_lock. | ||||||
|  * |  * | ||||||
|  * Mutex key structs only get allocated, once during bootup, and never |  * nr_lock_classes is the number of elements of lock_classes[] that is | ||||||
|  * get freed - this significantly simplifies the debugging code. |  * in use. | ||||||
|  */ |  */ | ||||||
| unsigned long nr_lock_classes; | unsigned long nr_lock_classes; | ||||||
| #ifndef CONFIG_DEBUG_LOCKDEP | #ifndef CONFIG_DEBUG_LOCKDEP | ||||||
|  | @ -278,11 +279,39 @@ static inline void lock_release_holdtime(struct held_lock *hlock) | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * We keep a global list of all lock classes. The list only grows, |  * We keep a global list of all lock classes. The list is only accessed with | ||||||
|  * never shrinks. The list is only accessed with the lockdep |  * the lockdep spinlock lock held. free_lock_classes is a list with free | ||||||
|  * spinlock lock held. |  * elements. These elements are linked together by the lock_entry member in | ||||||
|  |  * struct lock_class. | ||||||
|  */ |  */ | ||||||
| LIST_HEAD(all_lock_classes); | LIST_HEAD(all_lock_classes); | ||||||
|  | static LIST_HEAD(free_lock_classes); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct pending_free - information about data structures about to be freed | ||||||
|  |  * @zapped: Head of a list with struct lock_class elements. | ||||||
|  |  */ | ||||||
|  | struct pending_free { | ||||||
|  | 	struct list_head zapped; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct delayed_free - data structures used for delayed freeing | ||||||
|  |  * | ||||||
|  |  * A data structure for delayed freeing of data structures that may be | ||||||
|  |  * accessed by RCU readers at the time these were freed. | ||||||
|  |  * | ||||||
|  |  * @rcu_head:  Used to schedule an RCU callback for freeing data structures. | ||||||
|  |  * @index:     Index of @pf to which freed data structures are added. | ||||||
|  |  * @scheduled: Whether or not an RCU callback has been scheduled. | ||||||
|  |  * @pf:        Array with information about data structures about to be freed. | ||||||
|  |  */ | ||||||
|  | static struct delayed_free { | ||||||
|  | 	struct rcu_head		rcu_head; | ||||||
|  | 	int			index; | ||||||
|  | 	int			scheduled; | ||||||
|  | 	struct pending_free	pf[2]; | ||||||
|  | } delayed_free; | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * The lockdep classes are in a hash-table as well, for fast lookup: |  * The lockdep classes are in a hash-table as well, for fast lookup: | ||||||
|  | @ -742,7 +771,8 @@ static bool assign_lock_key(struct lockdep_map *lock) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Initialize the lock_classes[] array elements. |  * Initialize the lock_classes[] array elements, the free_lock_classes list | ||||||
|  |  * and also the delayed_free structure. | ||||||
|  */ |  */ | ||||||
| static void init_data_structures_once(void) | static void init_data_structures_once(void) | ||||||
| { | { | ||||||
|  | @ -754,7 +784,12 @@ static void init_data_structures_once(void) | ||||||
| 
 | 
 | ||||||
| 	initialization_happened = true; | 	initialization_happened = true; | ||||||
| 
 | 
 | ||||||
|  | 	init_rcu_head(&delayed_free.rcu_head); | ||||||
|  | 	INIT_LIST_HEAD(&delayed_free.pf[0].zapped); | ||||||
|  | 	INIT_LIST_HEAD(&delayed_free.pf[1].zapped); | ||||||
|  | 
 | ||||||
| 	for (i = 0; i < ARRAY_SIZE(lock_classes); i++) { | 	for (i = 0; i < ARRAY_SIZE(lock_classes); i++) { | ||||||
|  | 		list_add_tail(&lock_classes[i].lock_entry, &free_lock_classes); | ||||||
| 		INIT_LIST_HEAD(&lock_classes[i].locks_after); | 		INIT_LIST_HEAD(&lock_classes[i].locks_after); | ||||||
| 		INIT_LIST_HEAD(&lock_classes[i].locks_before); | 		INIT_LIST_HEAD(&lock_classes[i].locks_before); | ||||||
| 	} | 	} | ||||||
|  | @ -802,11 +837,10 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force) | ||||||
| 
 | 
 | ||||||
| 	init_data_structures_once(); | 	init_data_structures_once(); | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/* Allocate a new lock class and add it to the hash. */ | ||||||
| 	 * Allocate a new key from the static array, and add it to | 	class = list_first_entry_or_null(&free_lock_classes, typeof(*class), | ||||||
| 	 * the hash: | 					 lock_entry); | ||||||
| 	 */ | 	if (!class) { | ||||||
| 	if (nr_lock_classes >= MAX_LOCKDEP_KEYS) { |  | ||||||
| 		if (!debug_locks_off_graph_unlock()) { | 		if (!debug_locks_off_graph_unlock()) { | ||||||
| 			return NULL; | 			return NULL; | ||||||
| 		} | 		} | ||||||
|  | @ -815,7 +849,7 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force) | ||||||
| 		dump_stack(); | 		dump_stack(); | ||||||
| 		return NULL; | 		return NULL; | ||||||
| 	} | 	} | ||||||
| 	class = lock_classes + nr_lock_classes++; | 	nr_lock_classes++; | ||||||
| 	debug_atomic_inc(nr_unused_locks); | 	debug_atomic_inc(nr_unused_locks); | ||||||
| 	class->key = key; | 	class->key = key; | ||||||
| 	class->name = lock->name; | 	class->name = lock->name; | ||||||
|  | @ -829,9 +863,10 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force) | ||||||
| 	 */ | 	 */ | ||||||
| 	hlist_add_head_rcu(&class->hash_entry, hash_head); | 	hlist_add_head_rcu(&class->hash_entry, hash_head); | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Add it to the global list of classes: | 	 * Remove the class from the free list and add it to the global list | ||||||
|  | 	 * of classes. | ||||||
| 	 */ | 	 */ | ||||||
| 	list_add_tail(&class->lock_entry, &all_lock_classes); | 	list_move_tail(&class->lock_entry, &all_lock_classes); | ||||||
| 
 | 
 | ||||||
| 	if (verbose(class)) { | 	if (verbose(class)) { | ||||||
| 		graph_unlock(); | 		graph_unlock(); | ||||||
|  | @ -1860,6 +1895,24 @@ check_prev_add(struct task_struct *curr, struct held_lock *prev, | ||||||
| 	struct lock_list this; | 	struct lock_list this; | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
|  | 	if (!hlock_class(prev)->key || !hlock_class(next)->key) { | ||||||
|  | 		/*
 | ||||||
|  | 		 * The warning statements below may trigger a use-after-free | ||||||
|  | 		 * of the class name. It is better to trigger a use-after free | ||||||
|  | 		 * and to have the class name most of the time instead of not | ||||||
|  | 		 * having the class name available. | ||||||
|  | 		 */ | ||||||
|  | 		WARN_ONCE(!debug_locks_silent && !hlock_class(prev)->key, | ||||||
|  | 			  "Detected use-after-free of lock class %px/%s\n", | ||||||
|  | 			  hlock_class(prev), | ||||||
|  | 			  hlock_class(prev)->name); | ||||||
|  | 		WARN_ONCE(!debug_locks_silent && !hlock_class(next)->key, | ||||||
|  | 			  "Detected use-after-free of lock class %px/%s\n", | ||||||
|  | 			  hlock_class(next), | ||||||
|  | 			  hlock_class(next)->name); | ||||||
|  | 		return 2; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Prove that the new <prev> -> <next> dependency would not | 	 * Prove that the new <prev> -> <next> dependency would not | ||||||
| 	 * create a circular dependency in the graph. (We do this by | 	 * create a circular dependency in the graph. (We do this by | ||||||
|  | @ -2242,19 +2295,16 @@ static inline int add_chain_cache(struct task_struct *curr, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Look up a dependency chain. |  * Look up a dependency chain. Must be called with either the graph lock or | ||||||
|  |  * the RCU read lock held. | ||||||
|  */ |  */ | ||||||
| static inline struct lock_chain *lookup_chain_cache(u64 chain_key) | static inline struct lock_chain *lookup_chain_cache(u64 chain_key) | ||||||
| { | { | ||||||
| 	struct hlist_head *hash_head = chainhashentry(chain_key); | 	struct hlist_head *hash_head = chainhashentry(chain_key); | ||||||
| 	struct lock_chain *chain; | 	struct lock_chain *chain; | ||||||
| 
 | 
 | ||||||
| 	/*
 |  | ||||||
| 	 * We can walk it lock-free, because entries only get added |  | ||||||
| 	 * to the hash: |  | ||||||
| 	 */ |  | ||||||
| 	hlist_for_each_entry_rcu(chain, hash_head, entry) { | 	hlist_for_each_entry_rcu(chain, hash_head, entry) { | ||||||
| 		if (chain->chain_key == chain_key) { | 		if (READ_ONCE(chain->chain_key) == chain_key) { | ||||||
| 			debug_atomic_inc(chain_lookup_hits); | 			debug_atomic_inc(chain_lookup_hits); | ||||||
| 			return chain; | 			return chain; | ||||||
| 		} | 		} | ||||||
|  | @ -3337,6 +3387,11 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass, | ||||||
| 	if (nest_lock && !__lock_is_held(nest_lock, -1)) | 	if (nest_lock && !__lock_is_held(nest_lock, -1)) | ||||||
| 		return print_lock_nested_lock_not_held(curr, hlock, ip); | 		return print_lock_nested_lock_not_held(curr, hlock, ip); | ||||||
| 
 | 
 | ||||||
|  | 	if (!debug_locks_silent) { | ||||||
|  | 		WARN_ON_ONCE(depth && !hlock_class(hlock - 1)->key); | ||||||
|  | 		WARN_ON_ONCE(!hlock_class(hlock)->key); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if (!validate_chain(curr, lock, hlock, chain_head, chain_key)) | 	if (!validate_chain(curr, lock, hlock, chain_head, chain_key)) | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
|  | @ -4131,14 +4186,92 @@ void lockdep_reset(void) | ||||||
| 	raw_local_irq_restore(flags); | 	raw_local_irq_restore(flags); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Remove a class from a lock chain. Must be called with the graph lock held. */ | ||||||
|  | static void remove_class_from_lock_chain(struct lock_chain *chain, | ||||||
|  | 					 struct lock_class *class) | ||||||
|  | { | ||||||
|  | #ifdef CONFIG_PROVE_LOCKING | ||||||
|  | 	struct lock_chain *new_chain; | ||||||
|  | 	u64 chain_key; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = chain->base; i < chain->base + chain->depth; i++) { | ||||||
|  | 		if (chain_hlocks[i] != class - lock_classes) | ||||||
|  | 			continue; | ||||||
|  | 		/* The code below leaks one chain_hlock[] entry. */ | ||||||
|  | 		if (--chain->depth > 0) | ||||||
|  | 			memmove(&chain_hlocks[i], &chain_hlocks[i + 1], | ||||||
|  | 				(chain->base + chain->depth - i) * | ||||||
|  | 				sizeof(chain_hlocks[0])); | ||||||
|  | 		/*
 | ||||||
|  | 		 * Each lock class occurs at most once in a lock chain so once | ||||||
|  | 		 * we found a match we can break out of this loop. | ||||||
|  | 		 */ | ||||||
|  | 		goto recalc; | ||||||
|  | 	} | ||||||
|  | 	/* Since the chain has not been modified, return. */ | ||||||
|  | 	return; | ||||||
|  | 
 | ||||||
|  | recalc: | ||||||
|  | 	chain_key = 0; | ||||||
|  | 	for (i = chain->base; i < chain->base + chain->depth; i++) | ||||||
|  | 		chain_key = iterate_chain_key(chain_key, chain_hlocks[i] + 1); | ||||||
|  | 	if (chain->depth && chain->chain_key == chain_key) | ||||||
|  | 		return; | ||||||
|  | 	/* Overwrite the chain key for concurrent RCU readers. */ | ||||||
|  | 	WRITE_ONCE(chain->chain_key, chain_key); | ||||||
|  | 	/*
 | ||||||
|  | 	 * Note: calling hlist_del_rcu() from inside a | ||||||
|  | 	 * hlist_for_each_entry_rcu() loop is safe. | ||||||
|  | 	 */ | ||||||
|  | 	hlist_del_rcu(&chain->entry); | ||||||
|  | 	if (chain->depth == 0) | ||||||
|  | 		return; | ||||||
|  | 	/*
 | ||||||
|  | 	 * If the modified lock chain matches an existing lock chain, drop | ||||||
|  | 	 * the modified lock chain. | ||||||
|  | 	 */ | ||||||
|  | 	if (lookup_chain_cache(chain_key)) | ||||||
|  | 		return; | ||||||
|  | 	if (WARN_ON_ONCE(nr_lock_chains >= MAX_LOCKDEP_CHAINS)) { | ||||||
|  | 		debug_locks_off(); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	/*
 | ||||||
|  | 	 * Leak *chain because it is not safe to reinsert it before an RCU | ||||||
|  | 	 * grace period has expired. | ||||||
|  | 	 */ | ||||||
|  | 	new_chain = lock_chains + nr_lock_chains++; | ||||||
|  | 	*new_chain = *chain; | ||||||
|  | 	hlist_add_head_rcu(&new_chain->entry, chainhashentry(chain_key)); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Must be called with the graph lock held. */ | ||||||
|  | static void remove_class_from_lock_chains(struct lock_class *class) | ||||||
|  | { | ||||||
|  | 	struct lock_chain *chain; | ||||||
|  | 	struct hlist_head *head; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < ARRAY_SIZE(chainhash_table); i++) { | ||||||
|  | 		head = chainhash_table + i; | ||||||
|  | 		hlist_for_each_entry_rcu(chain, head, entry) { | ||||||
|  | 			remove_class_from_lock_chain(chain, class); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Remove all references to a lock class. The caller must hold the graph lock. |  * Remove all references to a lock class. The caller must hold the graph lock. | ||||||
|  */ |  */ | ||||||
| static void zap_class(struct lock_class *class) | static void zap_class(struct pending_free *pf, struct lock_class *class) | ||||||
| { | { | ||||||
| 	struct lock_list *entry; | 	struct lock_list *entry; | ||||||
| 	int i; | 	int i; | ||||||
| 
 | 
 | ||||||
|  | 	WARN_ON_ONCE(!class->key); | ||||||
|  | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Remove all dependencies this lock is | 	 * Remove all dependencies this lock is | ||||||
| 	 * involved in: | 	 * involved in: | ||||||
|  | @ -4151,14 +4284,33 @@ static void zap_class(struct lock_class *class) | ||||||
| 		WRITE_ONCE(entry->class, NULL); | 		WRITE_ONCE(entry->class, NULL); | ||||||
| 		WRITE_ONCE(entry->links_to, NULL); | 		WRITE_ONCE(entry->links_to, NULL); | ||||||
| 	} | 	} | ||||||
| 	/*
 | 	if (list_empty(&class->locks_after) && | ||||||
| 	 * Unhash the class and remove it from the all_lock_classes list: | 	    list_empty(&class->locks_before)) { | ||||||
| 	 */ | 		list_move_tail(&class->lock_entry, &pf->zapped); | ||||||
| 	hlist_del_rcu(&class->hash_entry); | 		hlist_del_rcu(&class->hash_entry); | ||||||
| 	list_del(&class->lock_entry); | 		WRITE_ONCE(class->key, NULL); | ||||||
|  | 		WRITE_ONCE(class->name, NULL); | ||||||
|  | 		nr_lock_classes--; | ||||||
|  | 	} else { | ||||||
|  | 		WARN_ONCE(true, "%s() failed for class %s\n", __func__, | ||||||
|  | 			  class->name); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	RCU_INIT_POINTER(class->key, NULL); | 	remove_class_from_lock_chains(class); | ||||||
| 	RCU_INIT_POINTER(class->name, NULL); | } | ||||||
|  | 
 | ||||||
|  | static void reinit_class(struct lock_class *class) | ||||||
|  | { | ||||||
|  | 	void *const p = class; | ||||||
|  | 	const unsigned int offset = offsetof(struct lock_class, key); | ||||||
|  | 
 | ||||||
|  | 	WARN_ON_ONCE(!class->lock_entry.next); | ||||||
|  | 	WARN_ON_ONCE(!list_empty(&class->locks_after)); | ||||||
|  | 	WARN_ON_ONCE(!list_empty(&class->locks_before)); | ||||||
|  | 	memset(p + offset, 0, sizeof(*class) - offset); | ||||||
|  | 	WARN_ON_ONCE(!class->lock_entry.next); | ||||||
|  | 	WARN_ON_ONCE(!list_empty(&class->locks_after)); | ||||||
|  | 	WARN_ON_ONCE(!list_empty(&class->locks_before)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static inline int within(const void *addr, void *start, unsigned long size) | static inline int within(const void *addr, void *start, unsigned long size) | ||||||
|  | @ -4166,7 +4318,87 @@ static inline int within(const void *addr, void *start, unsigned long size) | ||||||
| 	return addr >= start && addr < start + size; | 	return addr >= start && addr < start + size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void __lockdep_free_key_range(void *start, unsigned long size) | static bool inside_selftest(void) | ||||||
|  | { | ||||||
|  | 	return current == lockdep_selftest_task_struct; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* The caller must hold the graph lock. */ | ||||||
|  | static struct pending_free *get_pending_free(void) | ||||||
|  | { | ||||||
|  | 	return delayed_free.pf + delayed_free.index; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void free_zapped_rcu(struct rcu_head *cb); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Schedule an RCU callback if no RCU callback is pending. Must be called with | ||||||
|  |  * the graph lock held. | ||||||
|  |  */ | ||||||
|  | static void call_rcu_zapped(struct pending_free *pf) | ||||||
|  | { | ||||||
|  | 	WARN_ON_ONCE(inside_selftest()); | ||||||
|  | 
 | ||||||
|  | 	if (list_empty(&pf->zapped)) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	if (delayed_free.scheduled) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	delayed_free.scheduled = true; | ||||||
|  | 
 | ||||||
|  | 	WARN_ON_ONCE(delayed_free.pf + delayed_free.index != pf); | ||||||
|  | 	delayed_free.index ^= 1; | ||||||
|  | 
 | ||||||
|  | 	call_rcu(&delayed_free.rcu_head, free_zapped_rcu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* The caller must hold the graph lock. May be called from RCU context. */ | ||||||
|  | static void __free_zapped_classes(struct pending_free *pf) | ||||||
|  | { | ||||||
|  | 	struct lock_class *class; | ||||||
|  | 
 | ||||||
|  | 	list_for_each_entry(class, &pf->zapped, lock_entry) | ||||||
|  | 		reinit_class(class); | ||||||
|  | 
 | ||||||
|  | 	list_splice_init(&pf->zapped, &free_lock_classes); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void free_zapped_rcu(struct rcu_head *ch) | ||||||
|  | { | ||||||
|  | 	struct pending_free *pf; | ||||||
|  | 	unsigned long flags; | ||||||
|  | 
 | ||||||
|  | 	if (WARN_ON_ONCE(ch != &delayed_free.rcu_head)) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	raw_local_irq_save(flags); | ||||||
|  | 	if (!graph_lock()) | ||||||
|  | 		goto out_irq; | ||||||
|  | 
 | ||||||
|  | 	/* closed head */ | ||||||
|  | 	pf = delayed_free.pf + (delayed_free.index ^ 1); | ||||||
|  | 	__free_zapped_classes(pf); | ||||||
|  | 	delayed_free.scheduled = false; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * If there's anything on the open list, close and start a new callback. | ||||||
|  | 	 */ | ||||||
|  | 	call_rcu_zapped(delayed_free.pf + delayed_free.index); | ||||||
|  | 
 | ||||||
|  | 	graph_unlock(); | ||||||
|  | out_irq: | ||||||
|  | 	raw_local_irq_restore(flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Remove all lock classes from the class hash table and from the | ||||||
|  |  * all_lock_classes list whose key or name is in the address range [start, | ||||||
|  |  * start + size). Move these lock classes to the zapped_classes list. Must | ||||||
|  |  * be called with the graph lock held. | ||||||
|  |  */ | ||||||
|  | static void __lockdep_free_key_range(struct pending_free *pf, void *start, | ||||||
|  | 				     unsigned long size) | ||||||
| { | { | ||||||
| 	struct lock_class *class; | 	struct lock_class *class; | ||||||
| 	struct hlist_head *head; | 	struct hlist_head *head; | ||||||
|  | @ -4179,7 +4411,7 @@ static void __lockdep_free_key_range(void *start, unsigned long size) | ||||||
| 			if (!within(class->key, start, size) && | 			if (!within(class->key, start, size) && | ||||||
| 			    !within(class->name, start, size)) | 			    !within(class->name, start, size)) | ||||||
| 				continue; | 				continue; | ||||||
| 			zap_class(class); | 			zap_class(pf, class); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -4192,8 +4424,9 @@ static void __lockdep_free_key_range(void *start, unsigned long size) | ||||||
|  * guaranteed nobody will look up these exact classes -- they're properly dead |  * guaranteed nobody will look up these exact classes -- they're properly dead | ||||||
|  * but still allocated. |  * but still allocated. | ||||||
|  */ |  */ | ||||||
| void lockdep_free_key_range(void *start, unsigned long size) | static void lockdep_free_key_range_reg(void *start, unsigned long size) | ||||||
| { | { | ||||||
|  | 	struct pending_free *pf; | ||||||
| 	unsigned long flags; | 	unsigned long flags; | ||||||
| 	int locked; | 	int locked; | ||||||
| 
 | 
 | ||||||
|  | @ -4201,9 +4434,15 @@ void lockdep_free_key_range(void *start, unsigned long size) | ||||||
| 
 | 
 | ||||||
| 	raw_local_irq_save(flags); | 	raw_local_irq_save(flags); | ||||||
| 	locked = graph_lock(); | 	locked = graph_lock(); | ||||||
| 	__lockdep_free_key_range(start, size); | 	if (!locked) | ||||||
| 	if (locked) | 		goto out_irq; | ||||||
| 		graph_unlock(); | 
 | ||||||
|  | 	pf = get_pending_free(); | ||||||
|  | 	__lockdep_free_key_range(pf, start, size); | ||||||
|  | 	call_rcu_zapped(pf); | ||||||
|  | 
 | ||||||
|  | 	graph_unlock(); | ||||||
|  | out_irq: | ||||||
| 	raw_local_irq_restore(flags); | 	raw_local_irq_restore(flags); | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
|  | @ -4211,12 +4450,35 @@ void lockdep_free_key_range(void *start, unsigned long size) | ||||||
| 	 * before continuing to free the memory they refer to. | 	 * before continuing to free the memory they refer to. | ||||||
| 	 */ | 	 */ | ||||||
| 	synchronize_rcu(); | 	synchronize_rcu(); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	/*
 | /*
 | ||||||
| 	 * XXX at this point we could return the resources to the pool; |  * Free all lockdep keys in the range [start, start+size). Does not sleep. | ||||||
| 	 * instead we leak them. We would need to change to bitmap allocators |  * Ignores debug_locks. Must only be used by the lockdep selftests. | ||||||
| 	 * instead of the linear allocators we have now. |  */ | ||||||
| 	 */ | static void lockdep_free_key_range_imm(void *start, unsigned long size) | ||||||
|  | { | ||||||
|  | 	struct pending_free *pf = delayed_free.pf; | ||||||
|  | 	unsigned long flags; | ||||||
|  | 
 | ||||||
|  | 	init_data_structures_once(); | ||||||
|  | 
 | ||||||
|  | 	raw_local_irq_save(flags); | ||||||
|  | 	arch_spin_lock(&lockdep_lock); | ||||||
|  | 	__lockdep_free_key_range(pf, start, size); | ||||||
|  | 	__free_zapped_classes(pf); | ||||||
|  | 	arch_spin_unlock(&lockdep_lock); | ||||||
|  | 	raw_local_irq_restore(flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void lockdep_free_key_range(void *start, unsigned long size) | ||||||
|  | { | ||||||
|  | 	init_data_structures_once(); | ||||||
|  | 
 | ||||||
|  | 	if (inside_selftest()) | ||||||
|  | 		lockdep_free_key_range_imm(start, size); | ||||||
|  | 	else | ||||||
|  | 		lockdep_free_key_range_reg(start, size); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  | @ -4242,7 +4504,8 @@ static bool lock_class_cache_is_registered(struct lockdep_map *lock) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* The caller must hold the graph lock. Does not sleep. */ | /* The caller must hold the graph lock. Does not sleep. */ | ||||||
| static void __lockdep_reset_lock(struct lockdep_map *lock) | static void __lockdep_reset_lock(struct pending_free *pf, | ||||||
|  | 				 struct lockdep_map *lock) | ||||||
| { | { | ||||||
| 	struct lock_class *class; | 	struct lock_class *class; | ||||||
| 	int j; | 	int j; | ||||||
|  | @ -4256,7 +4519,7 @@ static void __lockdep_reset_lock(struct lockdep_map *lock) | ||||||
| 		 */ | 		 */ | ||||||
| 		class = look_up_lock_class(lock, j); | 		class = look_up_lock_class(lock, j); | ||||||
| 		if (class) | 		if (class) | ||||||
| 			zap_class(class); | 			zap_class(pf, class); | ||||||
| 	} | 	} | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Debug check: in the end all mapped classes should | 	 * Debug check: in the end all mapped classes should | ||||||
|  | @ -4266,21 +4529,57 @@ static void __lockdep_reset_lock(struct lockdep_map *lock) | ||||||
| 		debug_locks_off(); | 		debug_locks_off(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void lockdep_reset_lock(struct lockdep_map *lock) | /*
 | ||||||
|  |  * Remove all information lockdep has about a lock if debug_locks == 1. Free | ||||||
|  |  * released data structures from RCU context. | ||||||
|  |  */ | ||||||
|  | static void lockdep_reset_lock_reg(struct lockdep_map *lock) | ||||||
| { | { | ||||||
|  | 	struct pending_free *pf; | ||||||
| 	unsigned long flags; | 	unsigned long flags; | ||||||
| 	int locked; | 	int locked; | ||||||
| 
 | 
 | ||||||
| 	init_data_structures_once(); |  | ||||||
| 
 |  | ||||||
| 	raw_local_irq_save(flags); | 	raw_local_irq_save(flags); | ||||||
| 	locked = graph_lock(); | 	locked = graph_lock(); | ||||||
| 	__lockdep_reset_lock(lock); | 	if (!locked) | ||||||
| 	if (locked) | 		goto out_irq; | ||||||
| 		graph_unlock(); | 
 | ||||||
|  | 	pf = get_pending_free(); | ||||||
|  | 	__lockdep_reset_lock(pf, lock); | ||||||
|  | 	call_rcu_zapped(pf); | ||||||
|  | 
 | ||||||
|  | 	graph_unlock(); | ||||||
|  | out_irq: | ||||||
| 	raw_local_irq_restore(flags); | 	raw_local_irq_restore(flags); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * Reset a lock. Does not sleep. Ignores debug_locks. Must only be used by the | ||||||
|  |  * lockdep selftests. | ||||||
|  |  */ | ||||||
|  | static void lockdep_reset_lock_imm(struct lockdep_map *lock) | ||||||
|  | { | ||||||
|  | 	struct pending_free *pf = delayed_free.pf; | ||||||
|  | 	unsigned long flags; | ||||||
|  | 
 | ||||||
|  | 	raw_local_irq_save(flags); | ||||||
|  | 	arch_spin_lock(&lockdep_lock); | ||||||
|  | 	__lockdep_reset_lock(pf, lock); | ||||||
|  | 	__free_zapped_classes(pf); | ||||||
|  | 	arch_spin_unlock(&lockdep_lock); | ||||||
|  | 	raw_local_irq_restore(flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void lockdep_reset_lock(struct lockdep_map *lock) | ||||||
|  | { | ||||||
|  | 	init_data_structures_once(); | ||||||
|  | 
 | ||||||
|  | 	if (inside_selftest()) | ||||||
|  | 		lockdep_reset_lock_imm(lock); | ||||||
|  | 	else | ||||||
|  | 		lockdep_reset_lock_reg(lock); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void __init lockdep_init(void) | void __init lockdep_init(void) | ||||||
| { | { | ||||||
| 	printk("Lock dependency validator: Copyright (c) 2006 Red Hat, Inc., Ingo Molnar\n"); | 	printk("Lock dependency validator: Copyright (c) 2006 Red Hat, Inc., Ingo Molnar\n"); | ||||||
|  | @ -4297,7 +4596,8 @@ void __init lockdep_init(void) | ||||||
| 	       (sizeof(lock_classes) + | 	       (sizeof(lock_classes) + | ||||||
| 		sizeof(classhash_table) + | 		sizeof(classhash_table) + | ||||||
| 		sizeof(list_entries) + | 		sizeof(list_entries) + | ||||||
| 		sizeof(chainhash_table) | 		sizeof(chainhash_table) + | ||||||
|  | 		sizeof(delayed_free) | ||||||
| #ifdef CONFIG_PROVE_LOCKING | #ifdef CONFIG_PROVE_LOCKING | ||||||
| 		+ sizeof(lock_cq) | 		+ sizeof(lock_cq) | ||||||
| 		+ sizeof(lock_chains) | 		+ sizeof(lock_chains) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Bart Van Assche
						Bart Van Assche