As noted earlier, each semop() system call presents the semid for the semaphore, which must be looked up to locate the corresponding sem_array. In addition, the semaphore state must be locked. The semop() system call invokes the ipc_lock() kernel function to do this lookup and locking, and later invokes the ipc_unlock() kernel function to do the corresponding unlocking. Since the ipc_lock() kernel function was responsible for the lock contention that motivated use of RCU, we focus on ipc_lock() and the functions that it interacts with.
Since the read-code is lock-free, nothing will prevent ipc_lock() from racing with ipc_rmid(), thus possibly gaining a reference to the structure after it is marked deleted. Figure 11 shows how ipc_lock() handles this race. Note that the ids argument is a pointer to the sole permanent variable, while the out pointer declared in Line 4 is a temporary variable. Line 5 computes the ``hash'' used to access the array of ipc_ids. Line 8 marks the beginning of the RCU read-side critical section. In preemptive kernels, this will disable preemption; in non-preemptive kernels, it does absolutely nothing other than serve as a documentation aid. Lines 9-12 check for the specified ID being out of range, returning NULL for failure if so. Line 14 allows for interactions with the grow_ary() function on multiprocessors with weak memory consistency models as described in Section 2.4. Note that since the semaphore implementation does not use linked lists, these memory-barrier primitives must be invoked explicitly - the RCU variants of the Linux list-manipulation primitives cannot be used. Lines 15-17 obtain a stable pointer to the semaphore structure. The read_barrier_depends() allows for interactions with the grow_ary() function on multiprocessors with extremely weak memory consistency models, such as the Alpha. Lines 18-21 attempt to get a reference to the semaphore structure, returning NULL if there is no such structure (perhaps due to the specified ID no longer being valid). Line 22 acquires the semaphore structure's lock. Lines 24-28 check the deleted flag to determine if the semaphore is being removed, and, if such a race occurred, returns NULL to signal failure. Note that because ipc_lock() does not block, the normal RCU grace period keeps the semaphore structure around for long enough that there is no danger of this structure being freed up before ipc_lock() can check the deleted flag. Finally, Line 29 returns a pointer to the semaphore structure, having successfully translated the specified ID. Note that this function returns with the semaphore's lock held inside a RCU read-side critical section. The ipc_unlock() function therefore releases the semaphore's lock and then ends the RCU read-side critical section by executing a rcu_read_unlock().