RCU-55——RCU案例汇总

发布时间 2023-04-27 21:13:38作者: Hello-World3

基于 Linux-5.10

一、经典(可抢占)RCU

1. 例子-RCU链表

假设链表节点和头结点如下:

typedef struct {
    struct list_head link;
    struct rcu_head rcu; //used for call_rcu()
    int key;
    int val;
} test_entry;

struct list_head test_head;

读者访问链表方法如下:

int test_read(int key, int *val_ptr)
{
    test_entry *entry;
    int found = 0;

    rcu_read_lock();
    list_for_each_entry_rcu(entry, &test_head, link) {
        if (entry->key == key) {
            *val_ptr = entry->val;
            found = 1;
            break;
        }
    }
    rcu_read_unlock();

    return found;
}

1.1. 如果只有一个写者,那么写者是不需要使用锁进行保护的,添加、更新、删除的操作实现方法如下:

(1) 写者添加一个节点到链表尾部

void test_add_node(test_entry *entry)
{
    list_add_tail_rcu(&entry->link, &test_head);
}

(2) 写者更新一个节点

更新的过程是:首先把旧的节点复制更新,然后使用新节点替换旧节点,最后使用函数 call_rcu() 注册回调函数,延后释放旧节点。

void test_update_node(int key, int new_val)
{
    test_entry *entry, *new_entry;
    int ret = -ENOENT;

    list_for_each_entry(entry, &test_head, link) {
        if (entry->key == key) {
            new_entry = kmalloc(sizeof(test_entry), GFP_ATOMIC);
            if (new_entry == NULL) {
                ret = -ENOMEM;
                break;
            }

            *new_entry = *entry;
            new_entry->val = new_val;
            list_replace_rcu(&entry->link, &new_entry->link);
            call_rcu(&entry->rcu, test_free_node);
            ret = 0;
            break;
        }
    }

    return ret;
}

void test_free_node(struct rcu_head *head)
{
    test_entry *entry = container_of(head, test_entry, rcu);
    kfree(entry);
}

(3) 写者删除一个节点

第一种方法:首先将节点从链表中删除,然后使用函数 call_rcu() 注册回调函数延后释放节点。

int test_del_node(int key)
{
    test_entry *entry;
    int found = 0;

    list_for_each_entry(entry, &test_head, link) {
        if (entry->key == key) {
            list_del_rcu(&entry->link);
            call_rcu(&entry->rcu, test_free_node); //不会休眠
            found = 1;
            break;
        }
    }

    return found;
}

第二种方法:首先把节点从链表中删除,然后使用 synchronize_rcu() 等待宽限期结束,最后释放节点。

int test_del_node(int key)
{
    test_entry *entry;
    int found = 0;

    list_for_each_entry(entry, &test_head, link) {
        if (entry->key == key) {
            list_del_rcu(&entry->link);
            synchronize_rcu(); //会休眠
            kfree(entry);
            found = 1;
            break;
        }
    }

    return found;
}

 

1.2. 如果有多个写者,那么写者之间必须使用锁互斥,添加、更新、删除的操作实现方法如下:

(1) 写者添加一个节点到链表尾部,假设使用 spin_lock 保护链表,视情况也可以使用其它互斥锁。

void test_add_node(test_entry *entry)
{
    spin_lock(&test_lock); //struct spinlock test_lock;
    list_add_tail_rcu(&entry->link, &test_head);
    spin_unlock(&test_lock);
}

(2) 写者更新一个节点

void test_update_node(int key, int new_val)
{
    test_entry *entry, *new_entry;
    int ret = -ENOENT;

    spin_lock(&test_lock);
    list_for_each_entry(entry, &test_head, link) {
        if (entry->key == key) {
            new_entry = kmalloc(sizeof(test_entry), GFP_ATOMIC);
            if (new_entry == NULL) {
                ret = -ENOMEM;
                break;
            }

            *new_entry = *entry;
            new_entry->val = new_val;
            list_replace_rcu(&entry->link, &new_entry->link);
            call_rcu(&entry->rcu, test_free_node);
            ret = 0;
            break;
        }
    }
    spin_unlock(&test_lock);

    return ret;
}

(3) 写者删除一个节点

int test_del_node(int key)
{
    test_entry *entry;
    int found = 0;

    spin_lock(&test_lock);
    list_for_each_entry(entry, &test_head, link) {
        if (entry->key == key) {
            list_del_rcu(&entry->link);
            call_rcu(&entry->rcu, test_free_node); //不会休眠
            found = 1;
            break;
        }
    }
    spin_unlock(&test_lock);

    return found;
}

使用 spin_lock 进行保护,释放就不能使用会导致睡眠的同步等待宽限期结束的 synchronize_rcu() 函数了。

 

二、加速RCU

TODO


三、可抢占RCU

TODO


四、可休眠RCU

TODO