Again, the code fragment is as follows:
1 struct foo {
2 struct list_head list;
3 ...
4 };
5
6 LIST_HEAD(mylist);
7 struct srcu_struct mysrcu;
8
9 void process(void)
10 {
11 int i1, i2;
12 struct foo *p;
13
14 i1 = srcu_read_lock(&mysrcu);
15 list_for_each_entry_rcu(p, &mylist, list) {
16 do_something_with(p);
17 i2 = srcu_read_lock(&mysrcu);
18 srcu_read_unlock(&mysrcu, i1);
19 i1 = i2;
20 }
21 srcu_read_unlock(&mysrcu, i1);
22 }
As is customary with SRCU, the list is manipulated using
list_add_rcu(), list_del_rcu, and friends.
What are the advantages and disadvantages of this hand-over-hand SRCU list traversal?
The biggest disadvantage is that it is totally broken.
To see this, note the definition of list_for_each_rcu():
1 #define list_for_each_entry_rcu(pos, head, member) \ 2 for (pos = list_entry_rcu((head)->next, typeof(*pos), member); \ 3 prefetch(pos->member.next), &pos->member != (head); \ 4 pos = list_entry_rcu(pos->member.next, typeof(*pos), member))
The bug is that the nth list_for_each_entry_rcu()
fetches SRCU-protected pointer p in one SRCU read-side
critical section, but then the n+1th
list_for_each_entry_rcu() uses p after that
SRCU read-side critical section has ended.
We can fix that bug by changing process() so as to
open-code list_for_each_entry_rcu(),
advancing the pointer while under the protection of both of the SRCU
read-side critical sections (and dispensing with the prefetching):
1 void process(void)
2 {
3 int i1, i2;
4 struct foo *p;
5
6 i1 = srcu_read_lock(&mysrcu);
7 p = list_entry_rcu(mylist.next, struct foo, list);
8 while (&p->list != &mylist) {
9 do_something_with(p);
10 i2 = srcu_read_lock(&mysrcu);
11 p = list_entry_rcu(p->list.next, struct foo, list);
12 srcu_read_unlock(&mysrcu, i1);
13 i1 = i2;
14 }
15 srcu_read_unlock(&mysrcu, i1);
16 }
What are the advantages and disadvantages of this version of hand-over-hand SRCU list traversal?