由 RCU 保護的列表/陣列元素的引用計數設計¶
請注意,如果您需要結合引用計數和 RCU,percpu-ref 功能可能是您的首選。請參閱 include/linux/percpu-refcount.h 以獲取更多資訊。但是,在那些 percpu-ref 會消耗過多記憶體的特殊情況下,請繼續閱讀。
在使用傳統的讀/寫自旋鎖或訊號量保護的列表的元素的引用計數是簡單的
程式碼清單 A
1. 2.
add() search_and_reference()
{ {
alloc_object read_lock(&list_lock);
... search_for_element
atomic_set(&el->rc, 1); atomic_inc(&el->rc);
write_lock(&list_lock); ...
add_element read_unlock(&list_lock);
... ...
write_unlock(&list_lock); }
}
3. 4.
release_referenced() delete()
{ {
... write_lock(&list_lock);
if(atomic_dec_and_test(&el->rc)) ...
kfree(el);
... remove_element
} write_unlock(&list_lock);
...
if (atomic_dec_and_test(&el->rc))
kfree(el);
...
}
如果使用 RCU 使此列表/陣列無鎖,例如將 add() 和 delete() 中的 write_lock() 更改為 spin_lock(),並將 search_and_reference() 中的 read_lock() 更改為 rcu_read_lock(),則 search_and_reference() 中的 atomic_inc() 可能會持有對已從列表/陣列中刪除的元素的引用。 在這種情況下,使用 atomic_inc_not_zero(),如下所示
程式碼清單 B
1. 2.
add() search_and_reference()
{ {
alloc_object rcu_read_lock();
... search_for_element
atomic_set(&el->rc, 1); if (!atomic_inc_not_zero(&el->rc)) {
spin_lock(&list_lock); rcu_read_unlock();
return FAIL;
add_element }
... ...
spin_unlock(&list_lock); rcu_read_unlock();
} }
3. 4.
release_referenced() delete()
{ {
... spin_lock(&list_lock);
if (atomic_dec_and_test(&el->rc)) ...
call_rcu(&el->head, el_free); remove_element
... spin_unlock(&list_lock);
} ...
if (atomic_dec_and_test(&el->rc))
call_rcu(&el->head, el_free);
...
}
有時,需要在更新(寫入)流中獲取對元素的引用。 在這種情況下,atomic_inc_not_zero() 可能會過度,因為我們持有更新端的自旋鎖。 在這種情況下,可以使用 atomic_inc()。
在 search_and_reference() 程式碼路徑中處理“FAIL”並不總是方便。 在這種情況下,可以將 atomic_dec_and_test() 從 delete() 移動到 el_free(),如下所示
程式碼清單 C
1. 2.
add() search_and_reference()
{ {
alloc_object rcu_read_lock();
... search_for_element
atomic_set(&el->rc, 1); atomic_inc(&el->rc);
spin_lock(&list_lock); ...
add_element rcu_read_unlock();
... }
spin_unlock(&list_lock); 4.
} delete()
3. {
release_referenced() spin_lock(&list_lock);
{ ...
... remove_element
if (atomic_dec_and_test(&el->rc)) spin_unlock(&list_lock);
kfree(el); ...
... call_rcu(&el->head, el_free);
} ...
5. }
void el_free(struct rcu_head *rhp)
{
release_referenced();
}
關鍵點在於,add() 新增的初始引用直到刪除後的寬限期過去後才會被刪除。 這意味著 search_and_reference() 無法找到此元素,這意味著 el->rc 的值不會增加。 因此,一旦它達到零,就沒有讀者能夠或將能夠引用該元素。 因此,可以安全地釋放該元素。 這反過來又保證瞭如果任何讀者找到該元素,該讀者可以安全地獲取引用,而無需檢查引用計數器的值。
在清單 C 中基於 RCU 的模式優於清單 B 中的模式的一個明顯優勢是,即使同時呼叫 delete() 來刪除同一物件,任何對 search_and_reference() 的呼叫,只要找到給定物件,都將成功獲取對該物件的引用。 類似地,清單 B 和 C 都優於清單 A 的一個明顯優勢是,即使有任意數量的 search_and_reference() 呼叫在搜尋呼叫 delete() 的同一物件,對 delete() 的呼叫也不會延遲。 相反,所有延遲的都是 kfree() 的最終呼叫,這在現代計算機系統上通常不是問題,即使是小型計算機系統也是如此。
如果 delete() 可以睡眠,則可以從 delete() 呼叫 synchronize_rcu(),以便可以將 el_free() 併入 delete 中,如下所示
4.
delete()
{
spin_lock(&list_lock);
...
remove_element
spin_unlock(&list_lock);
...
synchronize_rcu();
if (atomic_dec_and_test(&el->rc))
kfree(el);
...
}
作為核心中的其他示例,struct pid 的引用計數使用清單 C 中的模式,而 struct posix_acl 使用清單 B 中的模式。