项目要在内核做和页高速缓存相类似缓存机制,在写内核代码之前必须先搞清楚页高速缓存源码是什么情况。 之前有一篇博客分析过了页高速缓存的基础,但是远远没有达到动手写代码的基础。这几天端午节假期集中精力,搞懂整个框架 与 在内核中的应用。


其他类别的博客也不会停止更新。


谨以此祭奠逝去的时间。


前言

基于内核版本 4.4.4

  • Linux 基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。
  • IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。IDR机制的API函数在lib/idr.c中实现,这里不加分析。

基于页高速缓存基础这篇博客我们已经知道:

  • 页高速缓存的核心数据结构是 address_space.

  • 每个inode表示的文件里面都有一个i_mapping字段。真正的对象就在 i_data字段。

struct address_space *i_mapping;
struct address_space    i_data;
  • 每个页描述符都包括把页链接到页高速缓存的两个字段mapping和index。
struct page {/* First double word block */unsigned long flags;       /* Atomic flags, some possibly* updated asynchronously */union {struct address_space *mapping;  /* If low bit clear, points to* inode address_space, or NULL.* If page mapped as anonymous* memory, low bit is set, and* it points to anon_vma object:* see PAGE_MAPPING_ANON below.*/void *s_mem;          /* slab first object */};/* Second double word */struct {union {pgoff_t index;      /* Our offset within mapping. */void *freelist;     /* sl[aou]b first free object */};....
....
...
};
  • address_space对象字段中, host 指向其所有者的索引对象 inode。 但真正的数据保存在 page_tree 里面。
struct address_space {struct inode       *host;      /* owner: inode, block_device  指向拥有该对象的索引节点的指针*/struct radix_tree_root  page_tree;  /* radix tree of all pages  表示拥有者页的基数radix tree 的根*/spinlock_t      tree_lock;  /* and lock protecting it 保护基树的自旋锁 */atomic_t       i_mmap_writable;/* count VM_SHARED mappings 地址空间中共享内存映射的个数*/struct rb_root      i_mmap;     /* tree of private and shared mappings   radix优先搜索树的根 */struct rw_semaphore i_mmap_rwsem;   /* protect tree, count, list  *//* Protected by tree_lock together with the radix tree */unsigned long      nrpages;    /* number of total pages */unsigned long        nrshadows;  /* number of shadow entries */pgoff_t           writeback_index;/* writeback starts here */const struct address_space_operations *a_ops;    /* methods */unsigned long      flags;      /* error bits/gfp mask */spinlock_t     private_lock;   /* for use by the address_space */struct list_head  private_list;   /* ditto */void         *private_data;  /* ditto */
}

现在就把重点放在 radix_tree_root这个数据结构:

Radix Tree

linux 内存管理通过radix树管理映射到地址空间上的核心页,该树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在 lib/radix_tree.c中。

主要数据结构:

    struct radix_tree_root {unsigned int     height;//树总高gfp_t           gfp_mask;//内存的分配方式struct radix_tree_node    __rcu *rnode;   // 间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针};
struct radix_tree_node {unsigned int height;     /* Height from the bottom */unsigned int    count; /*非叶子结点含有一个count域,表示出现在该结点的孩子的数量*/struct rcu_head rcu_head;     void __rcu    *slots[RADIX_TREE_MAP_SIZE];64个指针,每层64个子节点/* 结点标签数组=每个slot需要的最大标签位数*slot数所需的long类型变量数: 3 * 64位 */unsigned long  tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];   //管理页的状态标志数组
};

全局定义

#define RADIX_TREE_MAX_TAGS 3     /*每个slot需要的最大标签位数*/#ifdef __KERNEL__
#define RADIX_TREE_MAP_SHIFT    (CONFIG_BASE_SMALL ? 4 : 6)   /*值为6时,表示每个结点有2^6=64个slot,值为4时,表示有2^4=16个slot*/
#else
#define RADIX_TREE_MAP_SHIFT    3   /* For more stressful testing */
#endif#define RADIX_TREE_MAP_SIZE   (1UL << RADIX_TREE_MAP_SHIFT)  /*表示1个叶子结点可映射的页数,如:1<<6=64,表示可映射64个slot映射64页*/
#define RADIX_TREE_MAP_MASK (RADIX_TREE_MAP_SIZE-1)  //11111111#define RADIX_TREE_TAG_LONGS \((RADIX_TREE_MAP_SIZE + BITS_PER_LONG - 1) / BITS_PER_LONG)   //(64+32-1)/32=2/*定义slot数占用的long类型长度个数,每个slot用位图1位进行标记,如:64个slot时,值为2*/#define RADIX_TREE_INDEX_BITS  (8 /* CHAR_BIT */ * sizeof(unsigned long))        //32
#define RADIX_TREE_MAX_PATH (DIV_ROUND_UP(RADIX_TREE_INDEX_BITS, \RADIX_TREE_MAP_SHIFT))            //(32+6-1)/6=6/* Height component in node->path */
#define RADIX_TREE_HEIGHT_SHIFT (RADIX_TREE_MAX_PATH + 1)
#define RADIX_TREE_HEIGHT_MASK  ((1UL << RADIX_TREE_HEIGHT_SHIFT) - 1)/* Internally used bits of node->count */
#define RADIX_TREE_COUNT_SHIFT  (RADIX_TREE_MAP_SHIFT + 1)
#define RADIX_TREE_COUNT_MASK   ((1UL << RADIX_TREE_COUNT_SHIFT) - 1)

关系如下图:


插入函数
int radix_tree_insert(struct radix_tree_root *root,unsigned long index, void *item)
{struct radix_tree_node *node;void **slot;int error;BUG_ON(radix_tree_is_indirect_ptr(item));error = __radix_tree_create(root, index, &node, &slot);     //创建一个slotif (error)return error;if (*slot != NULL)return -EEXIST;rcu_assign_pointer(*slot, item);     //将item的值赋值给slotif (node) {node->count++;BUG_ON(tag_get(node, 0, index & RADIX_TREE_MAP_MASK));BUG_ON(tag_get(node, 1, index & RADIX_TREE_MAP_MASK));} else {BUG_ON(root_tag_get(root, 0));BUG_ON(root_tag_get(root, 1));}return 0;
}
EXPORT_SYMBOL(radix_tree_insert);

其中最重要的是__radix_tree_create 函数:

int __radix_tree_create(struct radix_tree_root *root, unsigned long index,struct radix_tree_node **nodep, void ***slotp)
{struct radix_tree_node *node = NULL, *slot;unsigned int height, shift, offset;int error;/* Make sure the tree is high enough.  */if (index > radix_tree_maxindex(root->height)) {     //发现需要插入的index大于目前树的叶子个数error = radix_tree_extend(root, index);             //扩展树if (error)return error;}slot = indirect_to_ptr(root->rnode);height = root->height;shift = (height-1) * RADIX_TREE_MAP_SHIFT;offset = 0;           /* uninitialised var warning */while (height > 0) {if (slot == NULL) {/* Have to add a child node.  */if (!(slot = radix_tree_node_alloc(root)))return -ENOMEM;slot->path = height;slot->parent = node;if (node) {rcu_assign_pointer(node->slots[offset], slot);node->count++;slot->path |= offset << RADIX_TREE_HEIGHT_SHIFT;} elsercu_assign_pointer(root->rnode, ptr_to_indirect(slot));}/* Go a level down */offset = (index >> shift) & RADIX_TREE_MAP_MASK;node = slot;slot = node->slots[offset];shift -= RADIX_TREE_MAP_SHIFT;height--;}if (nodep)*nodep = node;if (slotp)*slotp = node ? node->slots + offset : (void **)&root->rnode;return 0;
}
查找函数
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
{return __radix_tree_lookup(root, index, NULL, NULL);
}void *__radix_tree_lookup(struct radix_tree_root *root, unsigned long index,struct radix_tree_node **nodep, void ***slotp)
{struct radix_tree_node *node, *parent;unsigned int height, shift;void **slot;node = rcu_dereference_raw(root->rnode);if (node == NULL)return NULL;if (!radix_tree_is_indirect_ptr(node)) {if (index > 0)return NULL;if (nodep)*nodep = NULL;if (slotp)*slotp = (void **)&root->rnode;return node;}node = indirect_to_ptr(node);height = node->path & RADIX_TREE_HEIGHT_MASK;if (index > radix_tree_maxindex(height))return NULL;shift = (height-1) * RADIX_TREE_MAP_SHIFT;do {parent = node;slot = node->slots + ((index >> shift) & RADIX_TREE_MAP_MASK);node = rcu_dereference_raw(*slot);if (node == NULL)return NULL;shift -= RADIX_TREE_MAP_SHIFT;height--;} while (height > 0);if (nodep)*nodep = parent;if (slotp)*slotp = slot;return node;
}

按照index >> shift 左移来查找,找不到返回空,如果找到返回存储地址。


下面这篇文档非常重要,原来已经有大佬指点江山!!!
参考: https://blog.csdn.net/joker0910/article/details/8250085

下面的内容来自上面的文档

摘自文档

(4) 并行操作的优化

Linux radix树并行操作包括并行查询和并行修改,其中,并行修改在标准内核中未完没有实现,需要通过打补丁获得该功能。并行操作说明如下:

RCU并发查询

通过使用RCU,RCU Radix树可以进行完全并发的查询操作。RCU从根本上要求原子操作地移动指针从数据结构的一个版本到新的版本,保持旧版本直到系统经过静止状态。在静止状态点,旧版本数据结构已没有用户,因此可以被安全释放。
RCU radix树的修改操作之间还需要串行化,但是查询不再需要与修改操作串行化。

并发修改

RCU可使RCU radix树查询完全并行化,但修改操作成了“瓶颈”。这可通过将全树的锁破碎成较小的锁进行改善,再明显的方法是对结点进行加锁而非对整个树加锁。
radix树修改操作可分为单向和双向操作。单向操作仅执行从根节点和叶子结点的单方向指针移动,它包括插入、更新和设置标签操作。双向操作较复杂,它需要在指针移到叶子后又回移,它包括删除和清除标签操作。
梯级加锁(Ladder Locking)和锁耦合(Lock-Coupling)技术常用于数据库方面,允许单向遍历结点加锁的树(双向可能产生死锁)。如果所有的修改者从树顶到树底进行修改,并且修改的结点持有锁,那么,向下遍历时对孩子加锁,在孩子被锁住时再释放该结点锁。在这种情况下并发操作是可能的,因为只要根结点解锁,另一个操作就可以自上向下进行。如果两操作的路径没有相同操作结点,后一个操作可能在前一个操作完成之前完成。最坏的情况是流水线操作,但这还是比串行化操作好很多。

双向操作包括删除和清除标签操作,分别说明如下:

1)清除标签

在radix树中清除一个标签包括向下遍历树、查找定位条目和清除条目标签的操作。只要孩子结点没有打标签的条目,就可以向上遍历结点清除标签。结束条件是:如果遍历遇到一个结点,在清除一个标签后,它还有一个或多个条目带有标签集,就可以结束向上遍历。为了与向下遍历期间有同样的结束点,将终止条件改为:向上遍历将在有比清除标签数更多标签的结点处结束。这样,不论何时遇到这样的结点,将作为上遍历树的结束点。

2)删除元素

删除元素在删除无用结点时还需要删除该条目的所有标签。它的终止条件需要满足这两个方面。向上回退遍历树时需要满足下面的条件:当遇到一个非空结点且没有无用的标签时应终止向上回退遍历树。
在向下遍历树时鉴别此点的条件是:当遇到有超过2个孩子的结点、并且每个标签来说结点有多于一个标签条目被清除时,结束向上遍历。该条件用来鉴别向上回退遍历的终止点。

(5)radix树API说明

声明和初始化radix树

声明和初始化radix树的方法列出如下:

#include <linux/radix-tree.h>
/* gfp_mask表示如何执行内存分配,如果操作(如:插入)以原子性上下文中执行,其值为GFP_ATOMIC*/
RADIX_TREE(name, gfp_mask); /* 声明和初始化名为name的树*/

struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);

插入条目

插入条目的函数定义列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)

函数radix_tree_insert插入条目item到树root中,如果插入条目中内存分配错误,将返回错误-ENOMEM。该函数不能覆盖写正存在的条目。如果索引键值index已存在于树中,返回错误-EEXIST。插入操作成功是,返回0。
对于插入条目操作失败将引起严重问题的场合,下面的一对函数可避免插入操作失败:

int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);

函数radix_tree_preload尝试用给定的gfp_mask分配足够的内存,保证下一个插入操作不会失败。在调用插入操作函数之前调用此函数,分配的结构将存放在每CPU变量中。函数radix_tree_preload操作成功后,将完毕内核抢占。因此,在插入操作完成之后,用户应调用函数radix_tree_preload_end打开内核抢占。

删除条目

删除条目的函数定义列出如下:

void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)

函数radix_tree_delete删除与索引键值index相关的条目,如果删除条目在树中,返回该条目的指针,否则返回NULL。

查询操作

用于查询操作的函数定义列出如下:

/在树中查找指定键值的条目,查找成功,返回该条目的指针,否则,返回NULL/
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
/返回指向slot的指针,该slot含有指向查找到条目的指针/
void **radix_tree_lookup_slot(struct radix_tree_root *root, unsigned long index);
/查询返回max_items条目在results中。查询时键值索引从first_index开始/
radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);

标签操作

与标签操作相关的函数说明列出如下:

/将键值index对应的条目设置标签tag,返回值为设置标签的条目/
void *radix_tree_tag_set(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/从键值index对应的条目清除标签tag,返回值为清除标签的条目/
void *radix_tree_tag_clear(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/检查键值index对应的条目是否为标签tag,如果键值不存在,返回0,如果键值存在,但标签未设置,返回-1;如果键值存在,且标签已设置,返回1/
int radix_tree_tag_get(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/从first_index起查询树root中标签值为tag的条目,在results中返回/
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag);
/如果树root中有任何条目使用tag标签,返回键值/
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);

(6)并行操作使用radix树API的方法

查询获取slot操作

查询操作支持RCU无阻塞并行读操作,因此,需要遵循RCU的用法加RCU读锁,还需要将rcu_dereference()用于获得的slot,在写(或更新)操作时,需要给新的slot使用rcu_assign_pointer()。查询操作的使用方法列出如下:

struct page **slot, *page;
rcu_read_lock();
slot = radix_tree_lookup_slot(&mapping->page_tree, index);
page = rcu_dereference(*slot);
rcu_read_unlock();

查询修改slot操作

Linux内核的radix树需要打补丁才支持并发修改。查询仅有一个全局状态:RCU静止状态,并发修改需要跟踪持有什么锁。锁状态对于操作来说必须是外部的,因此,我们需要实例化一个本地上下文跟踪这些锁。查询修改slot的方法列出如下:

struct page *slot;
DEFINE_RADIX_TREE_CONTEXT(ctx,&mapping->page_tree);
radix_tree_lock(&ctx); /
锁住了根结点/
/ ctx.tree代替&mapping->page_tree作为根,可以传递上下文
slot = radix_tree_lookup_slot(tx.tree, index);
rcu_assign_pointer(*slot, new_page);
radix_tree_unlock(&ctx);

radix树API函数radix_tree_lookup_slot含有锁从树顶向下移动机制,锁移动的代码部分列出如下:

void *radix_tree_lookup_slot(struct radix_tree root, unsigned long index)
{

RADIX_TREE_CONTEXT(context, root); /
提供上下文和实际的root指针


do {

/ 从树顶向下移动锁/
radix_ladder_lock(context, node);

} while (height > 0);

}

(7)radix树API的实现

树的操作通常包括查找、插入、删除和树调整,下面分别说明radix树这些操作的实现。

… …
… …

未完,待续!

【页高速缓存】radix tree 源码解析相关推荐

  1. Redis radix tree源码解析

    Redis实现了不定长压缩前缀的radix tree,用在集群模式下存储slot对应的的所有key信息.本文将详述在Redis中如何实现radix tree. 核心数据结构 raxNode是radix ...

  2. 前端单页路由《stateman》源码解析

    <stateman>是波神的一个超级轻量的单页路由,拜读之后写写自己的小总结. stateman的github地址 github.com/leeluolee/s- 简单使用 以下文章全部以 ...

  3. Android多页蒙版遮罩引导功能(源码+解析)

    #Android多页蒙版遮罩引导功能(源码+解析) 需求:博主前段时间做的教育类型APP,需要引导用户(低龄化小朋友),播放器的播放,页面可以左右滑动,以及右上方进入答题卡入口(小朋友都是很聪明的,引 ...

  4. 详细讲解go web框架之gin框架源码解析记录及思路流程和理解

    开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...

  5. 2022-10-24 ClickHouse 源码解析-查询引擎经典理论

    ClickHouse 源码解析: 综述 ClickHouse 源码解析: MergeTree Write-Path ClickHouse 源码解析: MergeTree Read-Path Click ...

  6. Linux-4.20.8内核桥收包源码解析(一)----------sk_buff(详细)

    作者:lwyang? 内核版本:Linux-4.20.8 网络子系统中用来存储数据的缓冲区叫做套接字缓存,简称SKB,可处理变长数据,尽量避免数据的复制. 每一个SKB都在设备中标识发送报文的目的或接 ...

  7. Netty源码解析之内存管理-PooledByteBufAllocator-PoolArena

      PooledByteBufAllocator是Netty中比较复杂的一种ByteBufAllocator , 因为他涉及到对内存的缓存,分配和释放策略,PooledByteBufAllocator ...

  8. 尤雨溪的5KB petite-vue源码解析

    写在开头 近期尤雨溪发布了5kb的petite-vue,好奇的我,clone了他的源码,给大家解析一波. 最近由于工作事情多,所以放缓了原创的脚步!大家谅解 想看我往期手写源码+各种源码解析的可以关注 ...

  9. React深入学习与源码解析笔记

    ***当前阶段的笔记 *** 「面向实习生阶段」https://www.aliyundrive.com/s/VTME123M4T9 提取码: 8s6v 点击链接保存,或者复制本段内容,打开「阿里云盘」 ...

最新文章

  1. python检测端口是否被占用_Python_监测某一个端口是否被占用
  2. Javascript正则表达式难点、重点
  3. python matplotlib画折线图_python使用matplotlib绘制折线图教程
  4. python web-python web入坑指南
  5. SAP gateway 后台OData model data查看工具
  6. bitherj java_比太钱包为什么从 Bitcoinj 切换到了 Bitherj ?比太钱包从 Bitcoinj 切换到了 Bitherj原因是什么?...
  7. 折半枚举(双向搜索)
  8. 原生JS获取元素属性值
  9. mysql追溯历史性能问题_【踩坑記錄】記一次MySQL主從復制延遲的坑
  10. 第七章、epub文件处理 -- 解析 .xhtml文件 (一)
  11. MAC 下开发 不区分大小写问题及解决
  12. SendMessage功能(中规中矩)
  13. 如何将每日新闻添加到自己博客中,发送到微信群中
  14. 抓取淘宝天猫的商品的促销价格
  15. 一个域名可以对应多个ip地址的案例
  16. 家乡的春节html,家乡的春节作文(通用10篇)
  17. 如何做数据竞赛·优秀案例学习(DC03)
  18. Arch Linux折腾系列教程
  19. 国网GIM设备三维模型要求细则 - 换流阀塔
  20. 如何免费把OFD格式转换成WORD

热门文章

  1. 2022年山东省安全员C证考试题库及山东省安全员C证考试报名
  2. 华为云普惠AI:把人工智能变成“水电煤气”
  3. Wifi内窥镜模块 wifi图传方案 wifi视频方案定制
  4. 《MIT 6.828 Lab 1 Exercise 10》实验报告
  5. 应届生大学毕业后后档案的三种去处及优缺点分析
  6. c语言cl.exe什么意思,C语言编译器CL.exe
  7. 聚焦Open Infrastructure丹佛峰会,九州云分享边缘计算新技术
  8. 九州云:云游戏在边缘计算中的实践
  9. nginx-rtmp(直播点播)配置
  10. 阿里java开发一二面面经