4. Applications of RCU

This section describes several example applications of RCU.

4.1. RCU for a Hash Table with refcnt

One common application of RCU is a list structure protected by a global mutual exclusion whose individual elements are protected by a per-element mutual exclusion. Usually, searches of the list are more common than insertions or deletions.

For example, the code for simple search and delete functions for a circular, doubly-linked list of elements would have many lock round-trips and some complicated deadlock-avoidance code, if you did not use call_rcu. The following code is much shorter and more simple, and it uses call_rcu:

 		struct element {
                        struct element *next;
                        struct element *prev;
                        atomic_t refcnt;
                        int address;
                        int data;
                        struct rcu_head rcu;
                };


                /*
                 * Find element with specified address, lock it, and return
                 * a pointer to it.  Return NULL if no such address.
                 */
                struct element *search(int addr)
                {
                        struct element *p;
                        struct element *p0;

                        p0 = &head[hashit(addr)];
			rmb(); /* read_barrier_depends() */
                        p = p0->next;
                        while (p != p0) {
				rmb(); /* read_barrier_depends() */
                                if (p->address == addr) {
                                        atomic_inc(&p->refcnt);
                                        return (p);
                                }
                                p = p->next;
                        }
                        return ((struct element *)NULL);
                }


		/*
		 * Insert an element into the hash table
		 */
		void insert(struct element *element)
		{
                        struct element *p0;

                        p0 = &head[hashit(p->addr)];
                        p->next = p0->next;
			/*
			 * Make sure that next pointer of the inserted
			 * element is visible to other CPUs before
			 * actual insertion. This allows lock-free
			 * lookup of the bucket list to search
			 * the entire list.
			 */
			wmb();
                        p0->next = p;
		}

		void myfree(void *obj)
		{
			struct element *p = obj;
		
			/*
			 * Now it is safe to check refcnt since
			 * noone can be holding a stale pointer
			 * to this element got during lock-free
			 * search().
			 */
			if (atomic_read(&p->refcnt)) {
				/*
				 * Let some other garbage collection
				 * free it
				 */
				return;
			}
			kfree(obj);
		}

                /*
                 * Delete the specified element from the table.  Element
                 * must be locked.
                 */
                void delete(struct element *p)
                {
                        /*
                         * Get global lock.  There is no possible deadlock,
                         * since the global lock is now always acquired
                         * after the per-element lock.
                         */

                        spin_lock(&hash_lock);

                        /*
                         * Delete the element from the list, release
                         * lock, free up the element, and return.
                         */
                        p->next->prev = p->prev;
                        p->prev->next = p->next;
                        spin_unlock(&hash_lock);

                        call_rcu(&p->rcu, myfree, p);
                        return;
                }
				

4.2. RCU for use with kmem_cache_free()

Unlike [kv]free(), kmem_cache_free() requires one additional pointer to the cache that frees any memory allocated from it. This pointer can be a problem for RCU because it is designed for only one pointer. You can avoid this problem by using a specific destroyer function for each cache that requires deferred freeing. If the cache pointer is a global variable, the destroyer function can directly use that and invoke kmem_cache_free() in the callback. Otherwise, the cache pointer can be embedded in the data structure along with struct rcu_head and can be re-computed by the destroyer function.

For global cache pointers the function is as follows:

	        static kmem_cache_t xxx_cache;

       	 	struct xxx_entry {
                	struct xxx_entry *next;
                	struct xxx_entry *prev;
                	int flag;
                	int info;
                	struct rcu_head rch;
        	}

        	void xxx_destroy(void *ptr) {
                	struct xxx_entry *xp = (struct xxx_entry *)ptr;
                	kmem_cache_free(&xxx_cache, (void *)xp);
        	}

        	void xxx_delete(struct xxx *xp) {
                	call_rcu(&xp->rch, xxx_destroy, xp);
        	}
				

If the cache pointer is not available as a global variable, it can still be embedded in the structure and accessed during destruction as follows:

	        struct xxx_entry {
                	struct xxx_entry *next;
                	struct xxx_entry *prev;
                	int flag;
                	int info;
                	kmem_cache_t *cachep;
                	struct rcu_head rch;
        	}

        	void xxx_destroy(void *ptr) {
                	struct xxx_entry *xp = (struct xxx_entry *)ptr;
                	kmem_cache_free(&xp->cachep, (void *)xp);
        	}

        	void xxx_delete(struct xxx *xp) {
                	call_rcu(&xp->rch, xxx_destroy, xp);
        	}