基本原理
RCU - Read-Copy Update
,适用于读多写少(最好只有一个线程写)。其背后思想非常简单
- Read时直接Read, 唯一约束时暂时当前线程不允许被抢占
preempt
,其overhead基本可以忽略不计。 - Copy Update指的是update前先把要改的内存copy一份到其他内存上,然后对这段内存进行update
- 在update结束后,将原来的指针指向新的指针(原子更新)
- 最后记得回收掉以前不用的内存
约束
- RCU机制并不保护并发写,如果有并发写,还需要用其他锁进一步进行保护。
- 读线程的操作被包在
rcu_read_lock
和rcu_read_unlock
宏之间。在这之间不允许切换线程。schedule()
,kmalloc()
,copy_from_user()
等操作都会引起切换线程,因此这段期间不允许用。
grace period
-
rcu_read_lock
宏等同于preempt_disable()
。可以推断rcu_read_lock
和rcu_read_unlock
组成的critical section
中,线程不会被切换。反之,如果线程被切换,则说明critical section
已经结束。 -
grace period
开始与写线程更新指针。 -
grace period
结束于一个同步点。synchronize_rcu()
等待所有旧的数据的写线程都退出了critical section
。 -
grace period
结束后,就可以回收对应的内存,因为已经没有人再用它了。 - 除了
synchronize_rcu()
,call_rcu
也能代表grace period
的结束
图例
image.png- 上图中每个蓝色的reader方块代表了一个read side的critical section的长度。
- 黄色方块代表updater的
grace period
。黄色左端边缘表示grace period
的开始。所有和左边缘有overlap的reader,都有可能还再用旧的内存。 - 因此grace period的右端结束时间,取决于上述的那些有overlap的writer的最后一个结束点。同步点也就是等最后一个有overlap的结束。
例子1
写操作1
11~14行运行的时候可能会和代码顺序不一致。需要用barrier改写下。
1 struct foo {
2 int a;
3 int b;
4 int c;
5 };
6 struct foo *gp = NULL;
7
8 /* . . . */
9
10 p = kmalloc(sizeof(*p), GFP_KERNEL);
11 p->a = 1;
12 p->b = 2;
13 p->c = 3;
14 gp = p;
写操作2
将上述代码改写下,rcu_assign_pointer
内部调用了barrier,保证了顺序。rcu_assign_pointer
语义等同一个publish
,将旧指针换成了新指针。
1 p->a = 1;
2 p->b = 2;
3 p->c = 3;
4 rcu_assign_pointer(gp, p);
读操作1
对于有些CPU,p->a
的预读会发生在第2行之前,也会乱序。
1 p = gp;
2 if (p != NULL) {
3 do_something_with(p->a, p->b, p->c);
4 }
读操作2
将上述代码做如下改写,rcu_dereference()
起到了subscribe
的语义。
1 rcu_read_lock();
2 p = rcu_dereference(gp);
3 if (p != NULL) {
4 do_something_with(p->a, p->b, p->c);
5 }
6 rcu_read_unlock();
例子2
写操作
1 struct foo {
2 struct list_head list;
3 int a;
4 int b;
5 int c;
6 };
7 LIST_HEAD(head);
8
9 /* . . . */
10
11 p = search(head, key);
12 if (p == NULL) {
13 /* Take appropriate action, unlock, and return. */
14 }
15 q = kmalloc(sizeof(*p), GFP_KERNEL);
16 *q = *p;
17 q->b = 2;
18 q->c = 3;
19 list_replace_rcu(&p->list, &q->list);
20 synchronize_rcu();
21 kfree(p);
读操作
1 rcu_read_lock();
2 list_for_each_entry_rcu(p, head, list) {
3 do_something_with(p->a, p->b, p->c);
4 }
5 rcu_read_unlock();
网友评论