内核中的RCU锁

RCU:Read-Copy-Update

已经有自旋锁,信号量,互斥锁等锁机制,为什么还需要RCU锁?
上述锁都使用了原子操作指令,即原子的访问内存,多CPU争用共享变量会让高速缓存一致性变得很糟,使得系统性能下降。

RCU实现目标

  • 读者线程没有开销或者开销很小。
  • 不使用原子操作指令和内存屏障指令也能正常访问。
  • 多写者存在需要额外的保护机制。

RCU实现关键原理
RCU记录了所有指向共享数据的指针的使用者,当要修改共享数据是,首先创建一个副本,在副本中修改,所有读者线程离开读者临街区后,指针指向修改后的副本,并且删除旧数据。

常见应用场景
使用RCU,链表可以有效的提高遍历读取数据的效率,读取链表成员使用rcu_read_lock函数,允许多线程同时读取该链表,并且允许一个线程同时修改链表。

如何保证链表访问的正确性?
读者在遍历链表时,一个线程删除了一个节点。删除线程会把这个节点从链表中移除,但是不会直接销毁它,RCU会等到所有线程读完链表数据后,才销毁这个节点。

RCU提供的接口

  • ruc_read_lock()/rcu_read_unlock,组成一个读者临界区
  • rcu_dereference(), 用于获取被RCU保护的指针,读者线程要访问RCU保护的共享数据,需要使用该函数创建一个新指针,并且指向被RCU保护的指针。
  • rcu_assign_pointer,通常用于写线程,在写者线程完成数据的修改后,调用该接口可以让被RCU保护的指针指向新创建的数据,用RCU的术语是发布更新后的数据。
  • synchronize_rcu,同步等待所有现存的读者访问完成1。
  • call_rcu,注册一个回调函数,当所有现存线程的读访问完成后,调用这个函数销毁旧数据。

测试代码

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/rcupdate.h>
#include <linux/kthread.h>
#include <linux/delay.h>

struct foo {
        int a;
        struct rcu_head rcu;
};

static struct foo *g_ptr;

static int myrcu_reader_thread1(void *data) //读者线程1
{
        struct foo *p1 = NULL;

        while (1) {
                if(kthread_should_stop())
                        break;
                msleep(20);
                rcu_read_lock();
                mdelay(200);
                p1 = rcu_dereference(g_ptr);
                if (p1)
                        printk("%s: read a=%d\n", __func__, p1->a);
                rcu_read_unlock();
        }

        return 0;
}

static int myrcu_reader_thread2(void *data) //读者线程2
{
        struct foo *p2 = NULL;

        while (1) {
                if(kthread_should_stop())
                        break;
                msleep(30);
                rcu_read_lock();
                mdelay(100);
                p2 = rcu_dereference(g_ptr);
                if (p2)
                        printk("%s: read a=%d\n", __func__, p2->a);
    
                rcu_read_unlock();
        }

        return 0;
}

static void myrcu_del(struct rcu_head *rh)
{
        struct foo *p = container_of(rh, struct foo, rcu);
        printk("%s: a=%d\n", __func__, p->a);
        kfree(p);
}

static int myrcu_writer_thread(void *p) //写者线程
{
        struct foo *old;
        struct foo *new_ptr;
        int value = (unsigned long)p;

        while (1) {
                if(kthread_should_stop())
                        break;
                msleep(250);
                new_ptr = kmalloc(sizeof (struct foo), GFP_KERNEL);
                old = g_ptr;
                *new_ptr = *old;
                new_ptr->a = value;
                rcu_assign_pointer(g_ptr, new_ptr);
                call_rcu(&old->rcu, myrcu_del);
                printk("%s: write to new %d\n", __func__, value);
                value++;
        }

        return 0;
}

static struct task_struct *reader_thread1;
static struct task_struct *reader_thread2;
static struct task_struct *writer_thread;

static int __init my_test_init(void)
{
        int value = 5;

        printk("figo: my module init\n");
        g_ptr = kzalloc(sizeof (struct foo), GFP_KERNEL);

        reader_thread1 = kthread_run(myrcu_reader_thread1, NULL, "rcu_reader1");
        reader_thread2 = kthread_run(myrcu_reader_thread2, NULL, "rcu_reader2");
        writer_thread = kthread_run(myrcu_writer_thread, (void *)(unsigned long)value, "rcu_writer");

        return 0;
}
static void __exit my_test_exit(void)
{
        printk("goodbye\n");
        kthread_stop(reader_thread1);
        kthread_stop(reader_thread2);
        kthread_stop(writer_thread);
        if (g_ptr)
                kfree(g_ptr);
}
MODULE_LICENSE("GPL");
module_init(my_test_init);
module_exit(my_test_exit);

读临界区

  • 通过rcu_read_lock()和rcu_read_unlock()函数来构建一个读者临界区。
  • 通过rcu_dereference()函数获取被保护数据的副本,即read线程的p1、p2指针,此时的p和g_ptr指针都指向旧的需要被保护的数据。
  • 读者线程延迟一段时间之后读取被保护的数据。

写临界区

  • 分配新的保护数据,并修改数据。
  • rcu_assign_pointer()函数让g_ptr指向新数据new_ptr。
  • call_rcu()函数注册一个回调函数,确保所有对旧数据的引用都执行完成之后,才调用回调函数删除旧数据old_data。
  • 写着线程延迟一段时间之后修改被保护数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值