Wednesday, February 11, 2015

ACCESS_ONCE() usage in linux kernel

Most concurrent access to data should be protected by locks. Spinlocks and mutexes both function as optimization barriers,meaning that they prevent optimizations on one side of the barrier from carrying over to the other. If code only accessesa shared variable with the relevant lock held, and if that variable can only change when the lock is released (and held by a different thread), the compiler will not create subtle problems.

It is only in places where shared data is accessed without locks (or explicit barriers) that a construct like ACCESS_ONCE() is required.

The actual implementation of ACCESS_ONCE(), found in <linux/compiler.h>, is fairly straightforward:

    #define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))


In this case x variable can be declared as  volatile would deoptimize things much more than is necessary. Rather than do that,  the developers involved used ACCESS_ONCE() and things work as they should. So, making the variable as volatileat run time when ever it is required.


Consider, for example, the following code snippet from kernel/mutex.c:

for (;;) {

         struct task_struct *owner;
          owner = ACCESS_ONCE(lock->owner);
          if (owner && !mutex_spin_on_owner(lock, owner))
                 break;
 /* ... */

Compiler can optimize the code like below  if  lock->owner is not volatile:

    owner = ACCESS_ONCE(lock->owner);
    for (;;) {
            if (owner && !mutex_spin_on_owner(lock, owner))
            break;


What the compiler has missed is the fact that lock->owner is being changed byanother thread of execution entirely. The result is code that will fail to noticeany such changes as it executes the loop multiple times, leading to unpleasantresults. The ACCESS_ONCE()call prevents this optimization happening, with theresult that the code (hopefully) executes as intended.

No comments:

Post a Comment