内核同步之(读-拷贝-更新)RCU

RCU:读-拷贝-更新

众所周知,为了保护共享数据,需要一些同步机制,如自旋锁(spinlock),读写锁(rwlock),它们使用起来非常简单,而且是一种很有效的同步机制,在UNIX系统和Linux系统中得到了广泛的使用,但是它存在两个问题。

1.它的开销相对于CPU速度而言就越来越大

随着计算机硬件的快速发展,获得这种锁的开销相对于CPU的速度在成倍地增加,原因很简单,CPU的速度与访问内存的速度差距越来越大,而这种锁使用了原子操作指令,它需要原子地访问内存,也就说获得锁的开销与访存速度相关,另外在大部分非x86架构上获取锁使用了内存栅(Memory Barrier),这会导致处理器流水线停滞或刷新,因此它的开销相对于CPU速度而言就越来越大。

2.可扩展性

RCU它克服了以上锁的缺点,具有很好的扩展性,但是这种锁机制的使用范围比较窄,它只适用于读多写少的情况,如网络路由表的查询更新、设备状态表的维护、数据结构的延迟释放以及多径I/O设备的维护等

    1.对于被RCU保护的数据,读执行单元不需要获得任何锁就可以访问它,不使用原子指令,而且在除了Alpha的所有架构上不需要内存栅(Memory Barrier),因此不会导致锁竞争,内存延迟以及流水线停滞。

    2.使用RCU的写执行单元在访问共享数据前先复制一个副本,然后对副本进行操作,最后使用一个回调机制在适当的时机把指向原数据的指针重新指向被修改的副本。这个时机就是在所有引用该数据的CPU都退出对共享数据的操作的时候。

    3.读执行单元没有任何同步开销,而写执行单元的同步开销则取决于使用的写执行单元间的同步机制。

    4.相比读写锁,RCU的优点在于既允许多个读执行单元同时访问被保护的数据,又允许多个读执行单元和多个写执行单元同时访问被保护的数据。

    5.RCU不能替代读写锁,如果写比较多时,对读执行的性能提高不能弥补写执行导致的损失。使用RCU,写执行单元之间的同步开销会比较大,它需要延迟数据的释放,复制被修改的数据,它也必须使用某种锁机制同步并行的其它写执行单元的修改操作。

操作:

定义于#include<linux/rcupdate.h>

1.读锁定

rcu_read_lock();

rcu_read_lock_bh();

2.读解锁

rcu_read_unlock();

rcu_read_unlock_bh();

3.同步RCU

 sysnchronize_rcu();

由RCU写执行单元调用,它将阻塞写执行单元,直到所有的读执行单元已经完成读执行单元临界区,才会继续下一下操作。如果有多个RCU写执行单元调用该函数,它们将在下一个gace period(所有的读执行已经完成对临界区的访问)之后全部被唤醒。

synchronize_rcu()保证所有的CPU都处理完正在运行的读执行单元临界区。

synchronize_kernel();

内核代码使用该函数来等待所有CPU处于可抢占状态,目前功能等同于sysnchronize_rcu(),但现在是使用sysnchronize_sched(),它能保证正在运行的中断处理函数运行完毕,但不能保证正在运行的软中断处理完毕。

4.连接回调

void fastcall call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu));

函数call_rcu()也由RCU写执行单元调用,它不会使写执行单元阻塞,因面可以在中断上下文或软中断使用。该函数将把函数func挂接到RCU回调函数上,然后立即返回。函数synchronize_rcu()的实现实际上使用了call_rcu()函数。

void fastcall call_rcu_bh(struct rcu_head *head,void (*func)(struct rcu_head *rcu));

call_rcu_bh()的功能几乎与call_rcu()完全相同,唯一的差别就是它把软中断的完成当做经历一个quiesecent state(静默状态),因此如果写执行单元使用了该函数,在进程上下文的读执行单元必须使用rcu_read_lock_bh().

每个CPU维护两个数据结构rcu_date和rcu_bh_date,它们用于保存回调函数,函数call_rcu()把回调函数注册到rcu_date,call_rcu_bh()则把回调函数注册到rcu_bh_date,在每一个数据结构上,回调函数被注册到一个链表,先注册的排到前面,后注册排到末尾

使用CRU时,读执行单元必须提供一个信号给写执行者能够确定数据可以被安全地释放或修改的时机。等待适当时机的这一时期称为grace period,而CPU发生了上下文切换称为经历一个quiescent state,grace period就是所有CPU都经历一次quiescent state所需要的等待的时间,调用grace period后一个专门的垃圾回收器不断探测读执行单元的信号,一旦所有的读执行单元都已经发送信号告知它们都不在使用被CRU保护的数据结构,垃圾回收器就调用回收函数完成最后的数据释放或修改操作。

以下以链表元素删除为例详细说明这一过程。

参考:  http://livelife8.blogbus.com/logs/168205500.html

写者要从链表中删除元素 B,它首先遍历该链表得到指向元素 B 的指针,然后修改元素 B 的前一个元素的 next 指针指向元素 B 的 next 指针指向的元素C,修改元素 B 的 next 指针指向的元素 C 的 prep 指针指向元素 B 的 prep指针指向的元素 A,在这期间可能有读者访问该链表,修改指针指向的操作是原子的,所以不需要同步,而元素 B 的指针并没有去修改,因为读者可能正在使用 B 元素来得到下一个或前一个元素。写者完成这些操作后注册一个回调函数以便在 grace period 之后删除元素 B,然后就认为已经完成删除操作。垃圾收集器在检测到所有的CPU不在引用该链表后,即所有的 CPU 已经经历了 quiescent state,grace period 已经过去后,就调用刚才写者注册的回调函数删除了元素 B。

            图 2  使用 RCU 进行链表删除操作

5.RCU链表操作

static inline void list_add_rcu(struct list_head *new, struct list_head *head);

该函数把链表元素插入到CRU保护的链表head的开头,内存栅保证了在引用这个新插入的链表元素之前,新链表元素的链接指针的修改对所有读执行单元是可见的。

static inline void list_add_tail_rcu(struct list_head *new, struct list_head *head);

该函数类似于list_add_rcu(),它将新的链表元素new被添加到被CRU保护的链表的末尾。

static inline void list_del_rcu(struct list_head *entry);

该函数从RCU保护的链表中删除指定的链表元素entry。

static inleine void list_replace_rcu(struct list_head* old, struct list_head *new);

该函数是RCU新添加的函数,度不存在非RCU版本。它使用新指针new代替old,内存栅保证在引用新元素之前,它对链接指针的修正对所有读者是可见的。

list_for_each_rcu(pos,head);

该宏用于遍历由RCU保护的链表head,只要在读执行单元临界区使用该函数,它就可以安全地和其它_rcu链表操作函数并发运行如list_add_rcu().

list_for_each_safe_rcu(pos, n, head);

该宏类似于list_for_rcu(),不同之处在于它允许安全在删除当前的链表元素pos。

list_for_each_entry_rcu(pos, head, member);

该宏类似于list_for_each(),不同之处在于它用于遍历指定类型的数据结构链表,当前元素pos为一包含struct list_head结构的特定的数据结构。

static inline hlist_del_rcu(struct hlist_node *n);

它从由RCU保护的哈希链表中移走链表元素n。

static inline void hlist_add_head_rcu(struct hlist_node *n, struct list_head *h);

该函数用于把链表元素n插入到被RCU保护的哈希链表的开头,但同时允许读执行者对该哈希链表的遍历。内存栅确保在引用新链表元素之前,它对指针的修改对所有读执行单元可见。

hlist_for_each(pos, head);

该宏用于遍历由RCU保护的哈希链表head,只要在读端临界区使用该函数,它就可安全地和其它_rcu哈希链表操作函数(如list_add_rcu)并发地运行。

hlist_for_each_entry_rcu(tpos, pos, head, member); 

似于hlist_for_each(),不同之处在于它用于遍历指定类型的数据结构链表,当前元素pos为一包含struct list_head结构的特定的数据结构。

目前,RCU的使用在内核中已经非常普遍,大量原先使用读写锁的代码被RCU替代。

RCU还可用于实现对链表、映射、hash表等的操作进行同步保护;事实上,内核中已经实现了RCU版本的链表操作函数;

转自:http://blog.csdn.net/williamwang2013/article/details/8524519

你可能感兴趣的:(内核同步,RCU,读-拷贝-更新)