RCU机制及内存优化屏障

Posted 打工人打工魂打工人上人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RCU机制及内存优化屏障相关的知识,希望对你有一定的参考价值。

RCU机制

RCU英文全称为Read-Copy-Update,顾名思义就是 “读 - 拷贝-更新”,是内核中重要的同步机制。

RCU原理

RCU记录所有指向共享数据的指针的使用者,当要修改该共享数据时,首先创建一个副本,在副本中修改。所有读访问线程都离开读临界区之后 ,指针指向新的修改后副本的指针,并且删除旧数据。

  • 写着修改过程:首先赋值生成一个副本,然后更新副本,最后使用新的对象代替旧的对象。在写者执行复制更新时候读者可以读数据。
  • 写者删除对象,必须等到所有访问被删除对象的读者访问结束,才能够执行销毁操作。RCU关键技术是怎么判断所有读者已经访问完成。等待所有读者访问结束的时间称为宽限期(grace period)。
  • RCU读者并不需要直接与写者进行同步。读者与写者也能并发执行。RCU目标最大程度来减少读者开销。经常适用于读者性能要求高的场景。
  • RCU优先:读者开销少;不需要任何锁,不需要执行原子指令或者内存屏障;没有死锁;没有优先级反转的问题;没有内存泄漏的危险问题;很好的实时延迟操作。
  • RCU缺点:写者的同步开销比较大,写者之间需要互斥处理;使用其它同步机制复杂。
  • RCU应用场景:
    例如:每种锁都有自己适合的场景:spin lock不区分reader/writer,对于读写强度不对称是不适合的,RW spin lock和seq lock解决了这个问题,seq lock倾向于writer, RW spin lock倾向于reader。
    a. RCU只能保护动态分配的数据结构,并且必须是通过指针访问该数据结构;
    b. 受RCU保护的临界区不能sleep;
    c. 读写不对称,对writer的性能没有特别的要求,但是reader性能要求极高;
    d. reader端对新旧数据不敏感。
    RCU适用于需要频繁的读取数据,而且相应修改数据并不多的场景。例如:文件系统中,搜索定位目录,而对于目录修改相对来讲基本没用。

链表操作

RCU能保护的不仅仅是一般的指针。内核也提供标准函数,使得能通过RCU机制保护 双链表,这是RCU机制在内核内部最重要的应用。

//添加新元素到链表头部
static inline void list_add_rcu(struct list_head *new, struct list_head *head)

	__list_add_rcu(new, head, head->next);

//添加新元素到链表尾部
static inline void list_add_tail_rcu(struct list_head *new,
					struct list_head *head)

	__list_add_rcu(new, head->prev, head);

//从链表删除元素entry
static inline void list_del_rcu(struct list_head *entry)

	__list_del_entry(entry);
	entry->prev = LIST_POISON2;

//链表元素替换
static inline void list_replace_rcu(struct list_head *old,
				struct list_head *new)

	new->next = old->next;
	new->prev = old->prev;
	rcu_assign_pointer(list_next_rcu(new->prev), new);
	new->next->prev = new;
	old->prev = LIST_POISON2;

对于writer,RCU操作包括:

  1. rcu_assign_pointer 该函数被writer用来进行removal操作,在writer完成新版数据分配和更新之后,调用这个函数可以记RCU protected pointer指向RCU protected data。
  2. synchronize_rcy: writer端操作可以是同步的,也就是说,完成更新操作之后,可以调用这个函数等到所有旧版本数据上的reader线程离开临界区,一旦从函数返回,说明就得共享数据没有任何引用,直接进行reclaimation的操作。
  3. call_rcu:writer无法阻塞,可以调用call_rcu接口函数,该函数是注册callback直接返回,在适当实际会调用callback函数,完成reclaimation。

    removal:write分配一个new version共享数据进行数据更新,更新完毕后将pointer指向新版本的数据,通过这样的操作,原来reader0 reader1对共享数据的引用被删除。
    reclamation:共享数据不能有两个版本,一定要在适当的而实际回收旧版本数据。

RCU层次架构

RCU根据CPU数量的大小按照树形结构来自成其层次结构,称为RCU Hierarchy。

#ifdef CONFIG_RCU_FANOUT //每个层数最多支持的叶子数量
#define RCU_FANOUT CONFIG_RCU_FANOUT
#else /* #ifdef CONFIG_RCU_FANOUT */
# ifdef CONFIG_64BIT
# define RCU_FANOUT 64
# else
# define RCU_FANOUT 32
# endif
#endif /* #else #ifdef CONFIG_RCU_FANOUT */

#ifdef CONFIG_RCU_FANOUT_LEAF  //一个子叶子的CPU数量
#define RCU_FANOUT_LEAF CONFIG_RCU_FANOUT_LEAF
#else /* #ifdef CONFIG_RCU_FANOUT_LEAF */
# ifdef CONFIG_64BIT
# define RCU_FANOUT_LEAF 64
# else
# define RCU_FANOUT_LEAF 32
# endif
#endif /* #else #ifdef CONFIG_RCU_FANOUT_LEAF */

【32核处理器的CPU层次结构】
两个层次,level0 包含两个struct rcu_node, 每个struct rcu_node管理16个struct rcu_data数据结构,分别表示16个CPU独立的struct rcu_data数据结构;在level1层级,有一个struct rcu_node节点管理level0层级的两个rcu_node节点,level1层级中的rcu_node节点称为根节点,level0层级的连个rcu_node节点是叶子节点。

优化屏障

  1. 编译器优化:提高系统性能,编译器在不影响逻辑的情况下会调整指令的顺序。
  2. CPU执行优化:提高流水线性能,CPU的乱序执行可能会让后面的没有寄存器冲突和汇编指令优于前面的指令完成。
  3. 优化屏障避免编译的重新排序优化操作,保证编译程序时在优化屏障之前的指令不会 在优化屏障之后执行。
/* Optimization barrier */

/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")
//__asm__表示插入汇编语言程序;__volatile__表示组织编译对该值进行优化,确保变量使用了用户定义的精确地址,而不是装有同一信息的一些别名。memory表示修改了内存单元。

内存屏障

内存屏障,也称内存栅障或屏障指令等,是一类同步屏障指令,是编译器或CPU对内存访问操作的时候,严格按照一定顺序来执行,也就是memory barrier之前的指令和memory barrier之后的指令不会由于系统优化等原因而导致乱序。
memory barrier包括两类:编译器屏障(complier barrier)和CPU屏障(cpu barrier)

  • CPU内存屏障:防止指令之间重排序;保证数据可见性。
  • 分类:
The Linux kernel has eight basic CPU memory barriers:

	TYPE		MANDATORY		SMP CONDITIONAL
	===============	=======================	===========================
	GENERAL(保证读写操作有序)		mb()			smp_mb()
	WRITE(仅保证写操作有序)		wmb()			smp_wmb()
	READ(仅保证读操作有序)		rmb()			smp_rmb()
	

mb()/rmab()/wmb()将硬件内存屏障插入到代码流程中,barrier插入一个优化屏障,该指令告知编译器,保存在CPU寄存器中、在屏障之前有效的所有内存地址,在屏障之后全部消失。本质,编译器在屏障之前发出的读写请求完成之前,会处理屏障之后的任何读写请求。
smp_mp()/smp()_rmb()/smp_wmb()相当于硬件内存屏障,只适用于SMP系统。在单处理器系统上产生的是软件屏障。
内存屏障作用:解决CPU高速缓存存在的问题,无锁数据结构,内存屏障很有用处。

总结

本文主要介绍了RCU机制,包括原理、链表操作、层次架构、32核处理器的CPU层次结构,及内存屏障,包括编译器屏障、CPU屏障等。

技术参考

https://ke.qq.com/webcourse/3294666/103425320#taid=11144563963086282&vid=5285890815289192909

以上是关于RCU机制及内存优化屏障的主要内容,如果未能解决你的问题,请参考以下文章

Linux 内核 内存管理RCU 机制 ② ( RCU 机制适用场景 | RCU 机制特点 | 使用 RCU 机制保护链表 )

Linux 内核 内存管理RCU 机制 ② ( RCU 机制适用场景 | RCU 机制特点 | 使用 RCU 机制保护链表 )

Linux 内核 内存管理RCU 机制 ④ ( RCU 模式下更新链表项 list_replace_rcu 函数 | 链表操作时使用 smp_wmb() 函数保证代码执行顺序 )

Linux 内核 内存管理RCU 机制 ① ( RCU 机制简介 | RCU 机制的优势与弊端 | RCU 机制的链表应用场景 )

Linux 内核 内存管理RCU 机制 ① ( RCU 机制简介 | RCU 机制的优势与弊端 | RCU 机制的链表应用场景 )

Linux设备驱动程序 之 RCU机制