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)