Search code examples
clinux-kernellock-freepreemptionrcu

Is it safe to use rcu_dereference() inside local_bh_disable()/local_bh_enable()?


The local_bh_disable-function changes per-cpu (in case of x86 and recent kernels) __preempt_count or current_thread_info()->preempt_count otherwise.

Anyway that gives us a grace period, so we can assume that it will be redundant to do rcu_read_lock() inside local_bh_disable(). Indeed: in earlier kernels we can see that local_bh_disable() is used for RCU and rcu_dereference() is called subsequently inside e.g. the dev_queue_xmit-function. Later local_bh_disable() was replaced with rcu_read_lock_bh(), which eventually became a little more complicated than just calling local_bh_disable(). Now it looks like this:

static inline void rcu_read_lock_bh(void)
{
   local_bh_disable();
   __acquire(RCU_BH);
   rcu_lock_acquire(&rcu_bh_lock_map);
   RCU_LOCKDEP_WARN(!rcu_is_watching(),"rcu_read_lock_bh() used illegally while idle");
}

Also there are enough articles describing the RCU APIs. Here we can see:

Do you need to treat NMI handlers, hardirq handlers, and code segments with preemption disabled (whether via preempt_disable(), local_irq_save(), local_bh_disable(), or some other mechanism) as if they were explicit RCU readers? If so, RCU-sched is the only choice that will work for you.

This tells us to use the RCU Sched API in such cases, so rcu_dereference_sched() should help. From this comprehensive table we can realise that rcu_dereference() should be used only inside rcu_read_lock/rcu_read_unlock-markers.

However, it is not sufficiently clear. Can I use (in case of modern kernels) rcu_dereference() inside local_bh_disable/local_bh_enable-markers without misgiving of anything going wrong?

P.S. In my case, I can't change the code calling local_bh_disable to call e.g. rcu_read_lock_bh, so my code is runnung with bh already disabled. Also the usual RCU API is used. Thus, it is fraught with rcu_read_lock nested in local_bh_disable.


Solution

  • You are not supposed to mix-and-match APIs. If you need to use the RCU-bh API, you need to use rcu_dereference_bh.

    You can see that if you called rcu_dereference_check after rcu_read_lock_bh, it would correctly surmise it is not called in a RCU read-side critical section; contrast the call to lock_is_held(&rcu_lock_map) with rcu_lock_acquire(&rcu_bh_lock_map); in your snippet above.

    The kernel documentation for RCU here (search for the section on "rcu_dereference()") gives an explicit example of correct usage; rcu_dereference* can only be called correctly after the corresponding rcu_read_lock* function has completed.