由 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 中的模式。