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?