Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
		
			
				
	
	
		
			141 lines
		
	
	
	
		
			4.4 KiB
			
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
	
		
			4.4 KiB
			
		
	
	
	
		
			Text
		
	
	
	
	
	
Using RCU to Protect Read-Mostly Arrays
 | 
						|
 | 
						|
 | 
						|
Although RCU is more commonly used to protect linked lists, it can
 | 
						|
also be used to protect arrays.  Three situations are as follows:
 | 
						|
 | 
						|
1.  Hash Tables
 | 
						|
 | 
						|
2.  Static Arrays
 | 
						|
 | 
						|
3.  Resizeable Arrays
 | 
						|
 | 
						|
Each of these situations are discussed below.
 | 
						|
 | 
						|
 | 
						|
Situation 1: Hash Tables
 | 
						|
 | 
						|
Hash tables are often implemented as an array, where each array entry
 | 
						|
has a linked-list hash chain.  Each hash chain can be protected by RCU
 | 
						|
as described in the listRCU.txt document.  This approach also applies
 | 
						|
to other array-of-list situations, such as radix trees.
 | 
						|
 | 
						|
 | 
						|
Situation 2: Static Arrays
 | 
						|
 | 
						|
Static arrays, where the data (rather than a pointer to the data) is
 | 
						|
located in each array element, and where the array is never resized,
 | 
						|
have not been used with RCU.  Rik van Riel recommends using seqlock in
 | 
						|
this situation, which would also have minimal read-side overhead as long
 | 
						|
as updates are rare.
 | 
						|
 | 
						|
Quick Quiz:  Why is it so important that updates be rare when
 | 
						|
	     using seqlock?
 | 
						|
 | 
						|
 | 
						|
Situation 3: Resizeable Arrays
 | 
						|
 | 
						|
Use of RCU for resizeable arrays is demonstrated by the grow_ary()
 | 
						|
function used by the System V IPC code.  The array is used to map from
 | 
						|
semaphore, message-queue, and shared-memory IDs to the data structure
 | 
						|
that represents the corresponding IPC construct.  The grow_ary()
 | 
						|
function does not acquire any locks; instead its caller must hold the
 | 
						|
ids->sem semaphore.
 | 
						|
 | 
						|
The grow_ary() function, shown below, does some limit checks, allocates a
 | 
						|
new ipc_id_ary, copies the old to the new portion of the new, initializes
 | 
						|
the remainder of the new, updates the ids->entries pointer to point to
 | 
						|
the new array, and invokes ipc_rcu_putref() to free up the old array.
 | 
						|
Note that rcu_assign_pointer() is used to update the ids->entries pointer,
 | 
						|
which includes any memory barriers required on whatever architecture
 | 
						|
you are running on.
 | 
						|
 | 
						|
	static int grow_ary(struct ipc_ids* ids, int newsize)
 | 
						|
	{
 | 
						|
		struct ipc_id_ary* new;
 | 
						|
		struct ipc_id_ary* old;
 | 
						|
		int i;
 | 
						|
		int size = ids->entries->size;
 | 
						|
 | 
						|
		if(newsize > IPCMNI)
 | 
						|
			newsize = IPCMNI;
 | 
						|
		if(newsize <= size)
 | 
						|
			return newsize;
 | 
						|
 | 
						|
		new = ipc_rcu_alloc(sizeof(struct kern_ipc_perm *)*newsize +
 | 
						|
				    sizeof(struct ipc_id_ary));
 | 
						|
		if(new == NULL)
 | 
						|
			return size;
 | 
						|
		new->size = newsize;
 | 
						|
		memcpy(new->p, ids->entries->p,
 | 
						|
		       sizeof(struct kern_ipc_perm *)*size +
 | 
						|
		       sizeof(struct ipc_id_ary));
 | 
						|
		for(i=size;i<newsize;i++) {
 | 
						|
			new->p[i] = NULL;
 | 
						|
		}
 | 
						|
		old = ids->entries;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Use rcu_assign_pointer() to make sure the memcpyed
 | 
						|
		 * contents of the new array are visible before the new
 | 
						|
		 * array becomes visible.
 | 
						|
		 */
 | 
						|
		rcu_assign_pointer(ids->entries, new);
 | 
						|
 | 
						|
		ipc_rcu_putref(old);
 | 
						|
		return newsize;
 | 
						|
	}
 | 
						|
 | 
						|
The ipc_rcu_putref() function decrements the array's reference count
 | 
						|
and then, if the reference count has dropped to zero, uses call_rcu()
 | 
						|
to free the array after a grace period has elapsed.
 | 
						|
 | 
						|
The array is traversed by the ipc_lock() function.  This function
 | 
						|
indexes into the array under the protection of rcu_read_lock(),
 | 
						|
using rcu_dereference() to pick up the pointer to the array so
 | 
						|
that it may later safely be dereferenced -- memory barriers are
 | 
						|
required on the Alpha CPU.  Since the size of the array is stored
 | 
						|
with the array itself, there can be no array-size mismatches, so
 | 
						|
a simple check suffices.  The pointer to the structure corresponding
 | 
						|
to the desired IPC object is placed in "out", with NULL indicating
 | 
						|
a non-existent entry.  After acquiring "out->lock", the "out->deleted"
 | 
						|
flag indicates whether the IPC object is in the process of being
 | 
						|
deleted, and, if not, the pointer is returned.
 | 
						|
 | 
						|
	struct kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id)
 | 
						|
	{
 | 
						|
		struct kern_ipc_perm* out;
 | 
						|
		int lid = id % SEQ_MULTIPLIER;
 | 
						|
		struct ipc_id_ary* entries;
 | 
						|
 | 
						|
		rcu_read_lock();
 | 
						|
		entries = rcu_dereference(ids->entries);
 | 
						|
		if(lid >= entries->size) {
 | 
						|
			rcu_read_unlock();
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
		out = entries->p[lid];
 | 
						|
		if(out == NULL) {
 | 
						|
			rcu_read_unlock();
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
		spin_lock(&out->lock);
 | 
						|
 | 
						|
		/* ipc_rmid() may have already freed the ID while ipc_lock
 | 
						|
		 * was spinning: here verify that the structure is still valid
 | 
						|
		 */
 | 
						|
		if (out->deleted) {
 | 
						|
			spin_unlock(&out->lock);
 | 
						|
			rcu_read_unlock();
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
		return out;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
Answer to Quick Quiz:
 | 
						|
 | 
						|
	The reason that it is important that updates be rare when
 | 
						|
	using seqlock is that frequent updates can livelock readers.
 | 
						|
	One way to avoid this problem is to assign a seqlock for
 | 
						|
	each array entry rather than to the entire array.
 |