基数树是一种用空间换时间的数据结构,通过空间的冗余减少时间上的消耗.radix tree很适合稀疏的结构!

自从把RCU机制引入到基树中来,这里就有了个协议叫做:lockless的page-cache协议!

一个小小的find_get_entry函数,里面处理了两种异常的情况,一是文件系统的page被free或者将要被free掉的情况,二是文件系统的page被remove掉的情况!第三种异常是slot中直接被标记无效的情况,还有就是slot中没有对应的page*的情况!但是这个槽为什么能搜到?是否和基树的结构有关呢?

tmpfs和procfs在内存中是个什么样子呢? 是不是也是这样一个superblock挂上一个pagecache树这样的情形?

RCU锁应该是内核中最不好想的锁了吧?

介于文件系统中大量用到这个锁:包括pagecache变成lockless的了,还有dentry的管理也是采用RCU的方式.

RCU的原理很简单:该机制记录了指向共享数据结构的指针的所有使用者.在该结构将要改变时,首先创建一个副本!在副本中修改!在所有进行读访问的使用者结束对旧副本的读取之后,指针可以替换为指向新的,修改之后的副本的指针.这种机制允许读写并发执行!

反引用指针并使用其结果的代码,需要用rcu_read_lock和rcu_read_unlock调用保护起来:

rcu_read_lock();

p = rcu_dereference(ptr);

if (p != NULL)

do_something(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()等待所有现存的读访问完成.在该函数返回之后,释放与原指针关联的内存是安全的!

2. call_rcu可用于注册一个函数,在所有针对共享资源的读访问完成之后调用.这要求将一个rcu_head实例嵌入(不能通过指针)到RCU保护的数据结构:

struct super_duper {
     struct rcu_head head;

int meaning, of, life;

}

该回调函数可以通过参数访问对象的rcu_head成员,进而使用container_of机制访问对象本身.

其中super_duper是整个要释放的结构体

RCU机制中引入了垃圾回收器,真是个高大上的概念!

RCU不维护锁!

CU中有一篇文章把这个问题说得还不错, 但是仍然不知道call_rcu函数是怎么用的, 以及

http://blog.chinaunix.net/uid-23769728-id-3080134.html
http://blog.chinaunix.net/uid-20648784-id-1592810.html

--------------------

2016年11月5日

RCU writer在释放元素一般会有call_rcu, RCU writer在释放元素时的三种调用方式:调用synchronize_rcu,同步方式,

list_del_rcu(p);

synchronize_rcu();

kfree(p);

另一种是异步方式,在iRQ中调用call_rcu,异步方式

list_del_rcu(p);

call_rcu(&p->rcu, p_call_back);

static void p_callback(stuct rcu_head *);

那么就从page_cache中调用call_rcu来看看一个radix_tree_node是如何被平稳释放的!

并且同步的释放节点和异步的释放节点会不会有什么不同呢?

异步释放与同步释放是不是为了解决不同的问题?

同步释放

为什么page-cache会用rcu机制? 其实并不是为了一个page, 或者说是slot的互斥, 而是radix-node粒度的互斥. 因为page粒度的互斥, lock_page已经做了!  所以不用担心此处, 所以page-cache算是巧用了rcu机制.

为什么, 因为rcu机制本质上是很简单地对一个共享区的互斥: 很简单的一个场景, 全局变量, 所有的rcu机制都围着这一个变量转! 但是page-cache里的rcu不同了[说到这里, 好像也没啥不同的......晕,只是10分钟前一直没有找到rcu要保护的对象而已......]

也就是说整个page-cache树中的结构有两个同步机制, 一是lock_page, 二是rcu锁.

那么address_space也不难理解了,  tree_lock就是用来保护写了.

真是由于pagecache的RCU化,导致了每次lock_page成功之后, 都要判断page->mapping != mapping

=========================================

缓存是计算机中非常重要的概念, 其思想是数据访问的时间局部性,即当前被访问的数据后续很可能还会被访问到. 文件的访问是现代文件系统中pagecache是内核中非常重要的缓存,

对pagecache的操作主要有:查找,插入,删除.

插入和删除操作都是写操作, 所以需要address_space->tree_lock来保证互斥,是整棵树层面的互斥,这里是典型的RCU的写使用场景.

查找操作不需要加锁

对查找的影响是什么?

1) 暂时还找不到一个page, 查找操作结束后page进来了, 某些gang操作, insert进来的page可能不被临幸;

2) 找到了一个page, 但是这个page后来被释放掉了(最典型的场景是删除了一个page);

这些都是由于RCU引起来的吗?

首先谈比较熟悉的2)即删除的场景: 函数pagecache_get_page中,首先通过 find_get_entry 得到一个page, 然后lock_page, 就在这个find_get_entry和lock_page的空挡处, 这个page可能就被truncate掉了! 被truncate掉的page有什么什么特征呢? 被truncate掉的page->mapping is null

所以在lock_page的时候,一定要注意这个page的的mapping是否还是原来的mapping, 只要是, 那么我们就可以断定这个page肯定是不可以被truncate掉的!

2)中的场景肯定是由RCU引起来的,因为这样可以保证最大限度的读并发效率; 那么1)的场景会导致这个问题吗?应该是不会的!要么能得到这个page,要么就得不到!

那么还有个问题,释放的page什么时候回收? delete部分是pagecache操作中比较有意思的一部分,首先是从page-cache中去掉这个page,然后是调用put_page, 如果此时普通_page后page_count仍然不是zero, 那么说明page还在用, 直至最后一个page的user释放了这个page, 那么page归还到buddy中去.

其实整个系统中涉及到插入删除的还真不是page, 而是radix_tree_node结构体, page-cache是基于radix-tree的, 所以radix-tree的插入删除算法中涉及到中间索引节点的变更,radix_tree_node的变更!

中间节点的变更其实只是page插入删除的一个副反应! 为什么这么说是因为page-cache的操作对象都是page,page通过中间的radix_tree_node索引

在删除radix_tree_node时, 必须要有page的locked状态! lockpage与RCU完美搭配,实现了tree_node顺利卸载!

那么问题又来了,如何rcu是如何保证读和写同时读到一个指针时读不会读到中间状态的?(和RCU机制相关,assigned)

入下面代码, 指针的赋值采用 rcu_assign_pointer 去做: #define rcu_assign_pointer(p, v) smp_store_release(&p, RCU_INITIALIZER(v))

 56 #define smp_store_release(p, v)                     \
57 do { \
58 compiletime_assert_atomic_type(*p); \
59 smp_mb(); \
60 ACCESS_ONCE(*p) = (v); \
61 } while (0)
62

这里是一个原子的操作[后续可以研究下arm64中原子操作是怎么完成的], 后面的rcu reader就都读不到这个page了, 但和教科书中案例相比,本例有个非常非常重要的不同, 那就是指针指向的page是不需要调用free掉的,因为这棵树中的指针直接就是struct page结构体的地址, 有点探底了,不涉及到内存的释放,这里就是最彻底的释放了!

那么radix_tree_node的释放是怎么回事呢? 怎么就丝毫没有使用assign_pointer这样的赋值呢?

是因为rcu的本质是去找page,所以slot中指向page的指针才是要保护的关键, 所以radix_tree_node就是可有可无的赋值了?

[page chache的命中率, extent_tree的命中率,对数字敏感!]

--------2016.11.07

pagecache的

http://lwn.net/Articles/291826/

基数树与RCU锁的更多相关文章

  1. 基数树(radix tree)

    原文   基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制).内存管理等.ID ...

  2. Linux 内核中的数据结构:基数树(radix tree)

    转自:https://www.cnblogs.com/wuchanming/p/3824990.html   基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相 ...

  3. (转)Linux内核基数树应用分析

    Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...

  4. nginx 学习八 高级数据结构之基数树ngx_radix_tree_t

    1 nginx的基数树简单介绍 基数树是一种二叉查找树,它具备二叉查找树的全部长处:检索.插入.删除节点速度快,支持范围查找.支持遍历等. 在nginx中仅geo模块使用了基数树. nginx的基数树 ...

  5. [翻译]Linux 内核里的数据结构 —— 基数树

    目录 Linux 内核里的数据结构 -- 基数树 基数树 Radix tree Linux内核基数树API 链接 Linux 内核里的数据结构 -- 基数树 基数树 Radix tree 正如你所知道 ...

  6. linux RCU锁机制分析

    openVswitch(OVS)源代码之linux RCU锁机制分析 分类: linux内核  |  标签: 云计算,openVswitch,linux内核,RCU锁机制  |  作者: yuzhih ...

  7. 014-数据结构-树形结构-基数树、Patricia树、默克尔树、梅克尔帕特里夏树( Merkle Patricia Tree, MPT)

    一.基数树 Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构.与Trie不同的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩.同样的,Radi ...

  8. 菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t[转]

    菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blo ...

  9. 一步一步分析Gin框架路由源码及radix tree基数树

    Gin 简介 Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much ...

随机推荐

  1. Java Enumeration接口

    Enumeration接口定义 Enumeration接口与Iterator接口用法比较 一. 1.Enumeration接口定义 public interface Enumeration<E& ...

  2. How to Operate SharePoint User Alerts with PowerShell

    When you migrate list or site, the user alerts in the site will not be migrated together with the co ...

  3. 使用NSAssert()和NSParameterAssert调试程序

    NSAssert: NSAssert()只是一个宏,用于开发阶段调试程序中的Bug,通过为NSAssert()传递条件表达式来断定是否属于Bug,满足条件返回真值,程序继续运行,如果返回假值,则抛出异 ...

  4. Java内存以及GC

    <深入理解Java虚拟机>第二三章摘要 Java内存区域与内存溢出 Java虚拟机中的内存分配图: 各个区域的特性总结如下表: 补充说明: 当多线程情形下,可能多个线程要在堆上分配内存,那 ...

  5. 【转】Android Studio下加入百度地图的使用 (一)——环境搭建

    最近有学 生要做毕业设计,会使用到定位及地图信息的功能,特此研究了一下,供大家参考,百度定位SDK已经更新到了5.0,地图SDK已经更新到了3.5,但是在 AndroidStudio中使用还是存在一些 ...

  6. [转]看懂UML类图

    这里不会将UML的各种元素都提到,我只想讲讲类图中各个类之间的关系: 能看懂类图中各个类之间的线条.箭头代表什么意思后,也就足够应对 日常的工作和交流: 同时,我们应该能将类图所表达的含义和最终的代码 ...

  7. Invoke--转载

    在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界 ...

  8. linux c++编译问题和虚拟机网络通信

    1.gcc main.cpp -lstdc++ -o test5 2. service network stop service NetworkManager restart

  9. 设计模式C#实现(十四)——责任链模式

    意图 0 适用性 1 结构 2 实现 3 效果 4 参考 5 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象 ...

  10. 如何快速简单上传类库到CocoaPods - 图文攻略步骤

    当自己的库已经上传GitHub后,那么如何快速简单的开源自己的库呢? 这里就是介绍如何将自己的类库上传到pods管理库,以便开源所有人都能方便使用. 准备前提: - 项目已上传到GitHub (注意, ...