rcu_assign_pointer()

The time passes so quickly and all the interns have only a few weeks before the end of this internship. Anyway, my work for Linux kernel will continue for sure even after OPW.

In this post I will present two Coccinelle scripts for one of the tasks that I talked about in the previous posts (the one with the “rcu_assign_pointer()” and its replacement with “RCU_INIT_POINTER()” or not).

The following Coccinelle script says when we need to keep the “rcu_assign_pointer()” call (and not replacing it with “RCU_INIT_POINTER()”) for the case when the value that will be assigned to the pointer is updated before the call (by a call to another function that might update it or in other ways):

/* The first rule finds different ways in which the value is updated before the call */

@ value_updated @
identifier upd;
identifier v;
@@
(
 (<+…v…+>) = …;
|
\(memset\|memcpy\)((<+…v…+>), …);
|
upd(v, …);
|
atomic_inc((<+…v…+>));
|
++(<+…v…+>);
)
… when any
rcu_assign_pointer(…, v);

/* This rule sees if the update function found in the previous rule updates the parameter */

@ update_func @
identifier value_updated.upd;
identifier fv;
type t;
@@

upd(t fv,…) {
… when any
(
 (<+…fv…+>) = …;
|
\(memset\|memcpy\)((<+…fv…+>), …);
|
atomic_inc((<+…fv…+>));
)
… when any
}

/* The rule ignores this case because this is a case where we actually can use “RCU_INIT_POINTER()” according to “RCU_INIT_POINTER()’s” block comment, section 3a. */

@ ignore @
identifier value_updated.v;
@@

v = rcu_dereference(…);
… when != rcu_dereference(…);
      when != v = …;
      when != \(memcpy\|memset\)(…);
rcu_assign_pointer(…, v);

/* This rule highlights the pairs “update the value – call of “rcu_assign_pointer()”” because I want to see only these cases and not other uses of “rcu_assign_pointer()” that are not treated in this script */

@ match1 depends on !ignore @
identifier v;
@@

(
* (<+…v…+>) = …;
|
* \(memset\|memcpy\)((<+…v…+>), …);
|
* atomic_inc((<+…v…+>));
|
* ++(<+…v…+>);
)
… when any
* rcu_assign_pointer(…, v);

/* Also, this rule highlights the pairs “function that updates the value – call of “rcu_assign_pointer()”” */

@ print depends on update_func @
identifier v;
identifier value_updated.upd;
@@

* upd(v, …);
… when any
* rcu_assign_pointer(…, v);

 

Let’s see some examples:

-> for the file “block/blk-cgroup.c” the output is:

– blkg = radix_tree_lookup(&blkcg->blkg_tree, q->id);
if (blkg && blkg->q == q) {
if (update_hint) {
lockdep_assert_held(q->queue_lock);
– rcu_assign_pointer(blkcg->blkg_hint, blkg);

 

-> for the file “kernel/workqueue.c”:

– link_pwq(pwq);

old_pwq = rcu_access_pointer(wq->numa_pwq_tbl[node]);
– rcu_assign_pointer(wq->numa_pwq_tbl[node], pwq);

* the function “link_pwd()” updates the parameter “pwq”

-> for the file “drivers/infiniband/hw/qib/qib_qp.c”:

– atomic_inc(&qp->refcount);
spin_lock_irqsave(&dev->qpt_lock, flags);if (qp->ibqp.qp_num == 0)
– rcu_assign_pointer(ibp->qp0, qp);
else if (qp->ibqp.qp_num == 1)
– rcu_assign_pointer(ibp->qp1, qp);
else {
– qp->next = dev->qp_table[n];
– rcu_assign_pointer(dev->qp_table[n], qp);

 

Another Coccinelle semantic patch that actually replaces the function is:

/* “RCU_INIT_POINTER()’s” block comment, section 1 is used for the NULL part and the section 3a is used for “rcu_dereference_protected()” and “rtnl_dereference()” (which actually calls the previous one) */
@ match1 @
@@

– rcu_assign_pointer
+ RCU_INIT_POINTER
(…,
(
NULL
|
rcu_dereference_protected(…)
|
rtnl_dereference(…)
) )

/* Because there are cases where between a “rcu_dereference()” call and a “rcu_assign_pointer()” call can be updates of the value that interests us, we need to ignore them and replace only when the update is not happening */

@ match2 @
identifier v;
@@

v = rcu_dereference(…);
… when != rcu_dereference(…);
      when != v = …;
      when != \(memcpy\|memset\)(…);
(
– rcu_assign_pointer
+ RCU_INIT_POINTER
(…, v);
|
if(…) {
… when != v = …;
– rcu_assign_pointer
+ RCU_INIT_POINTER
(…, v);
… when != v = …;
}
)

 

Some output examples (or parts of examples):

-> for the file “block/genhd.c”:

part = rcu_dereference(ptbl->part[i]);if (part && sector_in_part(part, sector)) {

– rcu_assign_pointer(ptbl->last_lookup, part);
+ RCU_INIT_POINTER(ptbl->last_lookup, part);if (old_ptbl) {

– rcu_assign_pointer(old_ptbl->last_lookup, NULL);
+ RCU_INIT_POINTER(old_ptbl->last_lookup, NULL);

 

-> for the file “drivers/infiniband/ulp/ipoib/ipoib_main.c”:

– rcu_assign_pointer(*np,
+ RCU_INIT_POINTER(*np,
rcu_dereference_protected(neigh->hnext,
lockdep_is_held(&priv->lock)));

 

Also, I have some good news. I successfully ran the kernel with qemu with the help of my mentor 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s