Linux内核源码分析 (6)RCU机制及内存优化屏障

这篇具有很好参考价值的文章主要介绍了Linux内核源码分析 (6)RCU机制及内存优化屏障。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Linux内核源码分析 (A.6)RCU机制及内存优化屏障

一、RCU机制

  • 问题:RCU英文全称为Read-Copy-Update,顾名思义就是读-拷贝-更新,是Linux内核中重要的同步机制。Linux内核已有原子操作、读写信号量等锁机制,为什么要单独设计一个比较复杂的新机制?

1、RCU的原理和特点

  • RCU的原理
    RCU记录所有指向共享数据的指针的使用者,当要修改该共享数据时,首先创建一个副本, 在副本中修改。所有读访问程都离开读临界区之后,指针指向新的修改后副本的指针,并且删除旧数据。
  • RCU的特点
    • RCU写者修改对象的过程是:首先复制生成一个副本,然后更新这个副本,最后使用新的对象替换旧的对象。在写者执行复制更新的时候读者可以读数据。
    • 写者删除对象,必须等到所有访问被删除对象的读者访问结束,才能够执行销毁操作。RCU关键技术是怎么判断所有读者已经完成访问。等待所有读者访问结束的时间称为宽限期(grace period) 。
    • RCU读者不并不需要直接与写者进行同步,读者与写者也能并发的执行。RCU目标最大程序来减少读者的开销。因为也经常使用于读者性能要求高的场景。
    • RCU优点:读者开销少,不需要获取任何锁,不需要执行原子指令或内存屏障;没有死锁问题;没有优先级反转的问题;没有内存泄露的危险问题;很好的实时延迟操作。
    • RCU缺点:写者的同步开销比较大的,写者之间需要互斥处理;使用其它同步机制复杂。

2、核心API(例中使用RCU保护指针)

  • 假定指针ptr指向一个被RCU保护的数据结构。直接反引用指针是禁止的,首先必须调用rcu_dereference(ptr),然后反引用返回的结果。此外,反引用指针并使用其结果的代码,需要用rcu_read_lockrcu_read_unlock调用保护起来

    rcu_read_lock(); 
    /*被反引用的指针不能在rcu_read_lock()和rcu_read_unlock()
    保护的代码范围之外使用,也不能用于写访问。*/
    p = rcu_dereference(ptr); 
    if (p != NULL) { 
    	awesome_function(p); 
    } 
    rcu_read_unlock();
    
  • 如果必须修改ptr指向的对象,则需要使用rcu_assign_pointer()

    struct super_duper *new_ptr = kmalloc(...); 
    
    new_ptr->meaning = xyz; 
    new_ptr->of = 42; 
    new_ptr->life = 23; 
    
    rcu_assign_pointer(ptr, new_ptr);
    

    按RCU的术语,该操作公布了这个指针,后续的读取操作将看到新的结构,而不是原来的如果更新可能来自内核中许多地方,那么必须使用普通的同步原语防止并发的写操作,如自旋锁。尽管RCU能保护读访问不受写访问的干扰,但它不对写访问之间的相互干扰提供防护!

  • 在新值已经公布之后,旧的结构实例会怎么样呢?在所有的读访问完成之后,内核可以释放该内存,但它需要知道何时释放内存是安全的。为此,RCU提供了另外两个函数。

    • synchronize_rcu()等待所有现存的读访问完成。在该函数返回之后,释放与原指针关联的内存是安全的。
    • call_rcu可用于注册一个函数,在所有针对共享资源的读访问完成之后调用。这要求将一个rcu_head实例嵌入(不能通过指针)到RCU保护的数据结构。
      struct super_duper {
      	struct rcu_head head; 
      	int meaning, of, life; 
      };
      
      该回调函数可通过参数访问对象的rcu_head成员,进而使用container_of机制访问对象本身。
      kernel/rcupdate.c
      void fastcall call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu))
      

3、链表操作

  • RCU不仅能保护一般的指针,还能保护内核提供的双链表和散列表。以链表为例,仍然可以使用标准的链表元素。只有在遍历链表、修改和删除链表元素时,必须调用标准函数的RCU变体(附加_rcu后缀)。
    <list.h>

    /*将新的链表元素new添加到表头为head的链表头部*/
    static inline void list_add_rcu(struct list_head *new, struct list_head *head) 
    /*将新的链表元素new添加到表头为head的链表尾部*/
    static inline void list_add_tail_rcu(struct list_head *new, struct list_head *head) 
    /*从链表删除链表元素entry*/
    static inline void list_del_rcu(struct list_head *entry) 
    /*将链表元素old替换为new*/
    static inline void list_replace_rcu(struct list_head *old, struct list_head *new)
    

    此外,list_for_each_rcu允许遍历链表的所有元素。而list_for_each_rcu_safe甚至对于删除链表元素也是安全的。这两个操作都必须通过一对rcu_read_lock()rcu_read_unlock()包围。

4、RCU应用场景

  • 每种锁都有自己适合场景:spinlock不分区reader/writer,对于些读写强度不对称的是不适合的, RW spinlockseqlock解决了这个问题,seqlock倾向writerRW spinlock倾向reader。RCU适用于需要频繁的读取数据,而相应修改数据并不多的场景。比如:文件系统中,搜索定位目录,而对目录修改相对来讲基本没有。
    • RCU只能保护动态分配的数据结构,并且必须是通过指针访问该数据结构;
    • 受RCU保护的临界区不能sleep;
    • 读写不对称,对writer的性能没有特别的要求,但是reader性能要求极高;
    • reader端对新旧数据不敏感。

二、内存和优化屏障

1、优化屏障

  • 在编程时,指令一般不按照源程序顺序执行,原因是为提高程序执行性能,会对它进行优化,主要为两种:

    • 编译器优化:提高系统的性能,编译器在不影响逻辑的情况下会调整指令的顺序。
    • CPU执行优化:提高流水线的性能,CPU的乱序执行可能会让后面的没有寄存器冲突和汇编指令先于前面的指令完成。
  • Linux使用barrier()函数实现优化屏障,如其编译器的优化屏障源码为:

    static inline void barrier(void)
    {
    	/*asm表示插入汇编语言程序;volatile表示阻止编译对该值进行优化,确保变量使用了用
    	户定义的精确地址,而不是装有同意信息的一些别名。memory表示修改了内存单元。*/
    	asm volatile("" : : : "memory");
    }
    
  • 优化屏障的一个特定应用是内核抢占机制。要注意,preempt_disable对抢占计数器加1因而停用了抢占,preempt_enable通过对抢占计数器减1而再次启用抢占。这两个命令之间的代码,可免受抢占的影响。看一看下列代码:

    preempt_disable(); 
    function_which_must_not_be_preempted(); 
    preempt_enable();
    

    如果编译器决定将代码重新排序,那就会出现问题:

    function_which_must_not_be_preempted(); 
    preempt_disable(); 
    preempt_enable();
    

    另一种重排也会出现问题

    preempt_disable(); 
    preempt_enable(); 
    function_which_must_not_be_preempted();
    
  • 上述的错误时间不会发生,因为preempt_disable在抢占计数器加1之后插入一个内存屏障,preempt_enable在再次启用抢占之前插入一个优化屏障:
    <preempt.h>

    #define preempt_disable() \ 
    do { \ 
    	inc_preempt_count(); \ 
    	barrier(); \ 
    } while (0)
    

    这防止了编译器将inc_preempt_count()与后续的语句交换位置。
    <preempt.h>

    #define preempt_enable() \ 
    do { \ 
    ... 
    	barrier(); \ 
    	preempt_check_resched(); \ 
    } while (0)
    

    这种措施可以防止上文给出的第二种错误的重排。

2、内存屏障

  • 内存屏障是一种保证内存访问顺序的方法,解决内存访问乱序问题:

    • 编译器编译代码时可能重新排序汇编指令,使编译出来的程序在处理器上执行速度更快,但是有的时候优化结果可能不符合软件开发工程师意图。
    • 新式处理器采用超标量体系结构和乱序执行技术,能够在一个时钟周期并行执行多条指令。一句话总结为:顺序取指令,乱序执行,顺序提交执行结果。
    • 多处理器系统当中,硬件工程师使用存储缓冲区等机制实现高效性能,引入处理器之间的内存访问乱序问题。|
  • 处理器内存屏障解决CPU之间的内存访问乱序问题和处理器访问外围设备的乱序问题。

    内存屏障类型 强制性的内存屏障 SMP的内存屏障
    通用内存屏障 mb () smp_mb ()
    写内存屏障 wmb () smp_wmb ()
    读内存屏障 rmb () smp_rmb ()
    数据依赖屏障 read_barrier_depends() smp_read_barrier_depends()

    除数据依赖屏障之外,所有处理器内存屏障隐含编译器优化屏障。注意:smb_mb()smp_rmb()smp_wmb()只在SMP系统中有硬件屏障,它们在单处理器系统上产生的是软件屏障(编译器优化屏障)。文章来源地址https://www.toymoban.com/news/detail-693723.html

到了这里,关于Linux内核源码分析 (6)RCU机制及内存优化屏障的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux内核】内存管理——内存回收机制

    转载请注明: https://www.cnblogs.com/Ethan-Code/p/16626560.html 前文提到malloc的内存分配方式,malloc申请的是虚拟内存,只有在程序去访问时,才会触发缺页异常进入内核态,在缺页中断函数中建立物理内存映射。 如果物理内存充足,则直接建立页框与页的映射。当物理内存不足时,内

    2023年04月09日
    浏览(51)
  • 【Linux内核解析-linux-5.14.10-内核源码注释】关于Linux同步机制知识点整理

    在Linux系统中,同步机制是操作系统中非常重要的一部分,以下是一些基本要点: 什么是同步机制?同步机制是一种操作系统提供的机制,用于协调多个进程或线程之间的访问共享资源,防止出现竞态条件和死锁等问题。 Linux中常用的同步机制有哪些?Linux中常用的同步机制

    2024年02月04日
    浏览(49)
  • Linux内核源码剖析之TCP保活机制(KeepAlive)

    版本信息: Linux内核2.6.24(大部分centos、ubuntu应该都在3.1+。但是2.6的版本比较稳定,后续版本本质变化也不是很大) ipv4 协议 https://blog.csdn.net/ComplexMaze/article/details/124201088 本文使用案例如上地址,感谢案例的分享,本篇文章核心部分还是在Linux内核源码分析~ 为什么写下这

    2024年02月12日
    浏览(51)
  • Linux内核分析(五)--IO机制原理与系统总线

    一、引言 二、I/O设备 ------2.1、块设备 ------2.2、字符设备 ------2.3、设备控制器 ------------2.3.1、I/O寻址 ------------2.3.2、内存映射 I/O 三、系统总线 ------3.1、数据总线 ------3.2、地址总线 ------3.3、控制总线 ------3.4、单总线结构 ------3.5、多总线结构 ------3.6、添加cache的三总线结构

    2024年02月05日
    浏览(50)
  • 深入分析linux内核的内存分配函数devm_kzalloc

    在分析驱动代码的时候,经常会遇到使用devm_kzalloc()为一个设备分配一片内存的情况。devm_kzalloc()是内核用来分配内存的函数,同样可以分配内存的内核函数还有devm_kmalloc, kzalloc, kmalloc。它们之间的区别在于devm_XXX分配的内存可以跟设备进行绑定,当设备跟驱动分离时,跟设备

    2024年02月02日
    浏览(42)
  • Linux内核源码分析 1:Linux内核体系架构和学习路线

    好久没有动笔写文章了,这段时间经历了蛮多事情的。这段时间自己写了一两个基于不同指令集的 Linux 内核, x86 和 RISC-V 。期间也去做了一些嵌入式相关的工作,研究了一下 ARM 指令集架构。 虽然今年九月份我就要申请了,具体申请 AI 方向还是机器人、嵌入式、操作系统、

    2024年02月07日
    浏览(54)
  • Linux内核源码分析 (A)常见内核面试题

    系统调用 do_fork() : copy_process() 定时中断 do_timer() 唤醒进程 wake_up_process() :进程由睡眠状态转为 RUNNING 状态 系统调用 sys_sched_yield() 改变进程的调度策略 sched_setscheduler() : 什么情况下会发生调度时机:阻塞操作、中断返回之前(系统调用返回用户空间时)、被唤醒的进程会

    2024年02月10日
    浏览(59)
  • 【Linux 内核源码分析笔记】系统调用

    在Linux内核中,系统调用是用户空间程序与内核之间的接口,它允许用户空间程序请求内核执行特权操作或访问受保护的内核资源。系统调用提供了一种安全可控的方式,使用户程序能够利用内核功能而不直接访问底层硬件。 系统调用: 通过系统调用,用户程序可以请求内核

    2024年02月03日
    浏览(44)
  • Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制。该系列教程从Linux内核的各个模块入手,逐一分析其源码实现,并结合实际应用场景进行讲解。通过学习本系列,读者可以深入了解Linux操作系统的底层机制,掌握

    2024年01月21日
    浏览(50)
  • postgresql 内核源码分析 btree索引插入分析,索引页面分裂流程,多举措进行并发优化,对异常进行保护处理

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 ​ 开源贡献 : toadb开源库 个人主页 :我的主页 管理社区 :开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. B树索引在PostgreSQL中得到了广泛应用,它是一种自平衡树数据结构,可以维

    2024年02月08日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包