linux内核源码分析之RCU

Posted 为了维护世界和平_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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_disable
rcu_read_lock()
//读解锁 preempt_enable
rcu_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 <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 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)

 

以上是关于linux内核源码分析之RCU的主要内容,如果未能解决你的问题,请参考以下文章

linux内核源码分析之start_kernel函数

Linux 网络协议栈之内核锁—— RCU锁

linux RCU锁机制分析

Linux 内核 内存管理RCU 机制 ⑤ ( RCU 层次架构概念 | RCU 层次架构源码解析 | RCU 层次架构每层最多叶子数 | RCU 层次架构每个叶子 CPU 数量 )

rcu机制的简单理解

Linux内核同步 - RCU synchronize原理分析