• 内核中的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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    读临界区

    • 通过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。
    • 写着线程延迟一段时间之后修改被保护数据。
  • 相关阅读:
    使用 类加载器 或者 类对象 读取文件
    DHCP服务详解
    SAP 11策略测试简介
    Java学习----前端4
    十分钟教会你如何使用VitePress搭建及部署个人博客站点
    Windows环境下安装Hadoop3.1.0全过程(超详细手把手一条龙教学)
    java 模拟输入
    关于Unity Time.deltaTime的理解和使用
    Tomcat自启动另一种方法
    三、流程控制及循环《2022 solidity8.+ 版本教程到实战》
  • 原文地址:https://blog.csdn.net/qq_42931917/article/details/128049648