基数树与RCU锁
基数树是一种用空间换时间的数据结构,通过空间的冗余减少时间上的消耗.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锁的更多相关文章
- 基数树(radix tree)
原文 基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制).内存管理等.ID ...
- Linux 内核中的数据结构:基数树(radix tree)
转自:https://www.cnblogs.com/wuchanming/p/3824990.html 基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相 ...
- (转)Linux内核基数树应用分析
Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...
- nginx 学习八 高级数据结构之基数树ngx_radix_tree_t
1 nginx的基数树简单介绍 基数树是一种二叉查找树,它具备二叉查找树的全部长处:检索.插入.删除节点速度快,支持范围查找.支持遍历等. 在nginx中仅geo模块使用了基数树. nginx的基数树 ...
- [翻译]Linux 内核里的数据结构 —— 基数树
目录 Linux 内核里的数据结构 -- 基数树 基数树 Radix tree Linux内核基数树API 链接 Linux 内核里的数据结构 -- 基数树 基数树 Radix tree 正如你所知道 ...
- linux RCU锁机制分析
openVswitch(OVS)源代码之linux RCU锁机制分析 分类: linux内核 | 标签: 云计算,openVswitch,linux内核,RCU锁机制 | 作者: yuzhih ...
- 014-数据结构-树形结构-基数树、Patricia树、默克尔树、梅克尔帕特里夏树( Merkle Patricia Tree, MPT)
一.基数树 Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构.与Trie不同的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩.同样的,Radi ...
- 菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t[转]
菜鸟nginx源码剖析数据结构篇(五) 基数树 ngx_radix_tree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blo ...
- 一步一步分析Gin框架路由源码及radix tree基数树
Gin 简介 Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much ...
随机推荐
- 转:什么是即时编译(JIT)!?OpenJDK HotSpot VM剖析
重点 应用程序可以选择一个适当的即时编译器来进行接近机器级的性能优化. 分层编译由五层编译构成. 分层编译提供了极好的启动性能,并指导编译的下一层编译器提供高性能优化. 提供即时编译相关诊断信息的JV ...
- 咱小谈CLR
1.什么是CLR CLR(Common Language Runtime)公共语言远行时,是一个可由多种编程语言使用的“远行时”.CLR的核心功能(比如内存管理.程序集加载.安全性.异常处理和线程同步 ...
- form表单提交和ajax表单提交,关于移动端如何通过软键盘上的【搜索】和【前进】进行提交操作
[文章来源]由于自己对于form研究甚少,所以一直用的都是AJAX进行提交,这次后台提出要用form提交,顺便深入研究一下:之前在做表单的时候,发现input可以通过设置不同的type属性,调用不同的 ...
- “.”(十六进制值 0x00)是无效的字符解决方案
自从我们的项目数据层从读取数据库改为读取接口服务后,经常会出现一些类似于的错误.我们的数据结构如下所示 <type><![CDATA[gp]]></type> &l ...
- AMD and CMD are dead之KMDjs内核之分号
在老版本的kmdjs中,强制了分号的要求.但是总感觉不爽,因为在开发Ket - Kmdjs Extension Tools的时候,总需要导入一些开源的库,然后痛苦就来了,总是报错,一查,就是缺少分号! ...
- Sass初使用
看慕课网materliu前辈的sass教程,http://www.imooc.com/learn/364.顺便把刚做完的项目重构一下,然后把一些笔记和心得都写在这里~ 首先安装sass,这里直接参考 ...
- 将oracle冷备份恢复到另外一个数据库实例中
因更换服务器需要将Oracle数据库转移到另外台Oracle中.说明: 1.测试环境为:windows server2003 和 oracle 10g. 2.2台服务器安装的程序目录一样,数据目录不一 ...
- AFNetworking的理解
AFNetworking的理解 使用方法 1. 新建的工程中导入AFNetworking3.0中的(AFNetworking 和UIKit+AFNetworking两个文件夹) 2. 在用到AFNet ...
- RSA算法及其在iOS中的使用
因为项目中需要传输用户密码,为了安全需要用RSA加密,所以就学习了下RSA加密在iOS中的应用. 关于RSA的历史及原理,下面的两篇文章讲的很清楚了: http://www.ruanyifeng.c ...
- Android studio 使用Gradle发布Android开源项目到JCenter 总结
1.注册账号 先到https://bintray.com注册一个账号. 这个网站支持 github 账户直接登录的 2.获取 bintray.user 和 bintray.apikey ...