这段代码中加入了调试信息,去除调试信息,可以是以下的形式(其实这也是旧版本中的代码):
#define rcu_dereference(p)     ({
typeof(p) _________p1 = p;
smp_read_barrier_depends();
(_________p1);
})
#define rcu_dereference(p)     ({
typeof(p) _________p1 = p;
smp_read_barrier_depends();
(_________p1);
})
  在赋值后加入优化屏障smp_read_barrier_depends()。
  我们之前的第四行代码改为 foo *fp = rcu_dereference(gbl_foo);,可以防止上述问题。
  数据读取的完整性
  还是通过例子来说明这个问题:

  如图我们在原list中加入一个节点new到A之前,所要做的第一步是将new的指针指向A节点,第二步才是将Head的指针指向new。这样做的目的是当插入操作完成第一步的时候,对于链表的读取并不产生影响,而执行完第二步的时候,读线程如果读到new节点,也可以继续遍历链表。如果把这个过程反过来,第一步head指向new,而这时一个线程读到new,由于new的指针指向的是Null,这样将导致读线程无法读取到A,B等后续节点。从以上过程中,可以看出RCU并不保证读线程读取到new节点。如果该节点对程序产生影响,那么需要外部调用做相应的调整。如在文件系统中,通过RCU定位后,如果查找不到相应节点,会进行其它形式的查找,相关内容等分析到文件系统的时候再进行叙述。
  我们再看一下删除一个节点的例子:

  如图我们希望删除B,这时候要做的是将A的指针指向C,保持B的指针,然后删除程序将进入宽限期检测。由于B的内容并没有变更,读到B的线程仍然可以继续读取B的后续节点。B不能立即销毁,它必须等待宽限期结束后,才能进行相应销毁操作。由于A的节点已经指向了C,当宽限期开始之后所有的后续读操作通过A找到的是C,而B已经隐藏了,后续的读线程都不会读到它。这样确保宽限期过后,删除B并不对系统造成影响。
  小结
  RCU的原理并不复杂,应用也很简单。但代码的实现确并不是那么容易,难点都集中在了宽限期的检测上,后续分析源代码的时候,我们可以看到一些极富技巧的实现方式。