> 文档中心 > linux内核源码分析之RCU

linux内核源码分析之RCU

目录

RCU概念

核心函数

实例

实例二

实例三


 

 

RCU概念

RCU(Read Copy-Update),读-复制更新,是一种同步机制。它是在2.5开发过程中添加到Linux内核的一种同步机制,该机制针对以读为主的情况进行了优化。

 

读临界区:每个临界区开始与rcu_read_lock(),结束于rcu_read_unlock()。这些指针函数实现了依赖顺序加载的概念。

写临界区:推迟销毁并维护多个版本的数据结构,有同步开销。

 

RCU核心思想:

1)复制后更新;2)延迟回收内存

 

  • 删除指向数据结构的指针,以便后续读取器无法获得对它的引用。
  • 等待所有之前的读者完成他们的RCU阅读端关键部分。
  • 此时,不可能有任何读者持有对数据结构的引用,因此现在可以安全地回收它 例如,kfree()d)。

 

使用场景

  • 多读少写
  • RCU保护的是指针,因为指针赋值是一条单指令
  • 对数据没有强一致性要求
  • 不能发生进程上下文切换,可能阻塞

核心函数

//读锁 preempt_disablercu_read_lock()//读解锁 preempt_enablercu_read_unlock()//注册一个函数和自变量,这些函数和自变量在所有正在进行的RCU读取侧关键部分均已完成之后被调用synchronize_rcu()/call_rcu()//将新指针赋给RCU结构体,赋值前的读者看到的还是旧的指针rcu_assign_pointer()//获取受保护的RCU指针rcu_dereference()

 

本节展示了如何简单使用核心RCU API来保护指向动态分配结构的全局指针

实例一

struct foo {int a;char b;long c;};DEFINE_SPINLOCK(foo_mutex);struct foo __rcu *gbl_foo;void foo_update_a(int new_a){struct foo *new_fp;struct foo *old_fp;new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);spin_lock(&foo_mutex);old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));*new_fp = *old_fp;new_fp->a = new_a;rcu_assign_pointer(gbl_foo, new_fp);spin_unlock(&foo_mutex);synchronize_rcu();kfree(old_fp);}int foo_get_a(void){int retval;rcu_read_lock();retval = rcu_dereference(gbl_foo)->a;rcu_read_unlock();return retval;}
  • 使用rcu_read_lock()和rcu_read_unlock()来保护RCU,读侧关键部分
  • 在RCU读取端临界区内,使用RCU_dereference()来解除对受RCU保护的指针的引用。
  • 使用一些可靠的方案(例如锁或信号量)来防止并发更新相互干扰
  • 使用rcu_assign_pointer()更新受rcu保护的指针;
  • 使用synchronize_rcu()从受rcu保护的数据结构中删除数据元素

 

实例二

在上面的例子中,foo_update_a()会一直阻塞,直到宽限期结束。这很简单,但在某些情况下,人们不能等待太久,可能还有其他高优先级的工作要做。

 

void call_rcu(struct rcu_head * head,   void (*func)(struct rcu_head *head));

此函数在宽限期过后调用func,此调用可能发生在softirq或进程上下文中,因此不允许函数阻塞 

struct foo {int a;char b;long c;struct rcu_head rcu;};void foo_update_a(int new_a){struct foo *new_fp;struct foo *old_fp;new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);spin_lock(&foo_mutex);old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));*new_fp = *old_fp;new_fp->a = new_a;rcu_assign_pointer(gbl_foo, new_fp);spin_unlock(&foo_mutex);call_rcu(&old_fp->rcu, foo_reclaim);}void foo_reclaim(struct rcu_head *rp){struct foo *fp = container_of(rp, struct foo, rcu);foo_cleanup(fp->a);kfree(fp);}

使用call_rcu()确保在释放旧结构之前,所有可能引用旧结构的读都已完成。

 

rcu_read_lock函数的实现是 preempt_disable

static inline void __rcu_read_lock(void){    preempt_disable();}

 

实例三

模块创建三个内核线程,两个读线程,一个写线程

#include #include #include #include #include #include #include #include struct ftestrcu{    int a;    struct rcu_head rcu;};static struct ftestrcu *g_ptr;static int myrcu_reader_thread1(void *data){    struct ftestrcu *p1 = NULL;    while (1)    { if (kthread_should_stop())     break; msleep(10); rcu_read_lock(); mdelay(100); p1 = rcu_dereference(g_ptr); if (p1)     printk("%s: Read1 a1=%d\n", __func__, p1->a); rcu_read_unlock();    }    return 0;}static int myrcu_reader_thread2(void *data){    struct ftestrcu *p2 = NULL;    while (1)    { if (kthread_should_stop())     break; msleep(20); rcu_read_lock(); mdelay(200); p2 = rcu_dereference(g_ptr); if (p2)     printk("%s:Read2 a2=%d\n", __func__, p2->a); rcu_read_unlock();    }    return 0;}static void myrcu_del(struct rcu_head *rh){    struct ftestrcu *p = container_of(rh, struct ftestrcu, rcu);    printk("%s: a=%d\n", __func__, p->a);    kfree(p);}static int myrcu_writer_thread(void *p){    struct ftestrcu *old;    struct ftestrcu *new_ptr;    int value = (unsigned long)p;    while (1)    { if (kthread_should_stop())     break; msleep(300); new_ptr = kmalloc(sizeof(struct ftestrcu), 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 myrcu_init(void){    int value = 5;    printk("\n\nRCU Module Init OK.\n");    g_ptr = kzalloc(sizeof(struct ftestrcu), 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 myrcu_exit(void){    printk("\nRCU Unloading Module Exit.\n\n");    kthread_stop(reader_thread1);    kthread_stop(reader_thread2);    kthread_stop(writer_thread);    if (g_ptr) kfree(g_ptr);}module_init(myrcu_init);module_exit(myrcu_exit);MODULE_AUTHOR("Wy");MODULE_LICENSE("GPL");

dmesg 查看打印输出

[130138.082438] myrcu_reader_thread1: Read1 a1=0[130138.189646] myrcu_reader_thread2:Read2 a2=0[130138.198330] myrcu_reader_thread1: Read1 a1=0[130138.270907] myrcu_writer_thread: Write to new 5[130138.314884] myrcu_reader_thread1: Read1 a1=5[130138.414074] myrcu_reader_thread2:Read2 a2=5[130138.419551] myrcu_del: a=0[130138.433998] myrcu_reader_thread1: Read1 a1=5[130138.550795] myrcu_reader_thread1: Read1 a1=5[130138.591066] myrcu_writer_thread: Write to new 6[130138.638104] myrcu_reader_thread2:Read2 a2=6[130138.670482] myrcu_reader_thread1: Read1 a1=6[130138.786063] myrcu_reader_thread1: Read1 a1=6[130138.861480] myrcu_reader_thread2:Read2 a2=6[130138.867331] myrcu_del: a=5[130138.901986] myrcu_reader_thread1: Read1 a1=6[130138.915200] myrcu_writer_thread: Write to new 7[130139.018513] myrcu_reader_thread1: Read1 a1=7[130139.085237] myrcu_reader_thread2:Read2 a2=7[130139.133893] myrcu_reader_thread1: Read1 a1=7[130139.235345] myrcu_writer_thread: Write to new 8[130139.250811] myrcu_reader_thread1: Read1 a1=8[130139.309200] myrcu_reader_thread2:Read2 a2=8

 

参考

内核源码:Documentation/RCU/whatisRCU.rst

Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂 (qq.com)

 

多事通