一,nfs-ganesha版本2.3.3 ,2.4.5cache分析
https://github.com/zanglinjie/nfs-ganesha点击打开链接
mdcache在2.4.0之后放在了FSAL层,对应的目录为src\FSAL\Stackable_FSALs\FSAL_MDCACHE
2.4版本ganesha缓存配置块
struct config_block mdcache_param_blk = {
.dbus_interface_name = "org.ganesha.nfsd.config.cache_inode",
.blk_desc.name = "CacheInode",
.blk_desc.type = CONFIG_BLOCK,
.blk_desc.u.blk.init = mdcache_param_init,
.blk_desc.u.blk.params = mdcache_params,
.blk_desc.u.blk.commit = noop_conf_commit
};
ganesha框架图采用模块化的设计不易实现,但易于维护
二,cache_inode_lookup 与mdcache_lookup
2.1cache_inode_lookup
2.2mdcahce_lookup
函数定义:
fsal_status_t mdc_lookup(mdcache_entry_t *mdc_parent, const char *name,
bool uncached, mdcache_entry_t **new_entry,
struct attrlist *attrs_out)
流程图:

mdcache_entry_t结构体!!!如下:
typedef struct mdcache_fsal_obj_handle mdcache_entry_t;
mdcache_entry_t结构体是一个对象(文件/目录等)cache的实体,mdc_parent是父目录存在于cache中的cache实例。
mdcache_entry->fsobj.fsdir.parent保存了父目录的信息,如果是查找父目录的话,直接调用mdcache_locate_host将mdcache_entry->fsobj.fsdir.parent转化为父目录的mdcache_entry_t条目返回即可。
接下来调用mdc_try_get_cached,通过mdc_parent和name查找cache,如果不存在的话,我们调用mdc_lookup_uncached。
struct mdcache_fsal_obj_handle {
/** Reader-writer lock for attributes */
pthread_rwlock_t attr_lock;
/** MDCache FSAL Handle */
struct fsal_obj_handle obj_handle;
/** Sub-FSAL handle */
struct fsal_obj_handle *sub_handle;
/** Cached attributes */
struct attrlist attrs;
/** FH hash linkage */
struct {
struct avltree_node node_k; /*< AVL node in tree */
mdcache_key_t key; /*< Key of this entry */
bool inavl;
} fh_hk;
/** Flags for this entry */
uint32_t mde_flags;
/** Time at which we last refreshed attributes. */
time_t attr_time;
/** Time at which we last refreshed acl. */
time_t acl_time;
/** New style LRU link */
mdcache_lru_t lru;
/** Exports per entry (protected by attr_lock) */
struct glist_head export_list;
/** ID of the first mapped export for fast path
* This is an int32_t because we need it to be -1 to indicate
* no mapped export.
*/
int32_t first_export_id;
/** Lock on type-specific cached content. See locking
discipline for details. */
pthread_rwlock_t content_lock;
/** Filetype specific data, discriminated by the type field.
Note that data for special files is in
attributes.rawdev */
union mdcache_fsobj {
struct state_hdl hdl;
struct {
/** List of chunks in this directory, not ordered */
struct glist_head chunks;
/** List of detached directory entries. */
struct glist_head detached;
/** Spin lock to protect the detached list. */
pthread_spinlock_t spin;
/** Count of detached directory entries. */
int detached_count;
/** @todo FSF
*
* This is somewhat fragile, however, a reorganization
* is possible. If state_lock was to be moved into
* state_file and state_dir, and the state code was
* made clear which it was working with, dhdl could
* be replaced with a state_dir which would be
* smaller than state_file, and then the additional
* members of fsdir would basically overlay
* the larger state_file that hdl is.
*
* Such a reorg could save memory AND make for a
* crisper interface.
*/
struct state_hdl dhdl; /**< Storage for dir state */
/** The parent host-handle of this directory ('..') */
struct gsh_buffdesc parent;
/** The first dirent cookie in this directory.
* 0 if not known.
*/
fsal_cookie_t first_ck;
struct {
/** Children by name hash */
struct avltree t;
/** Table of dirents by FSAL cookie */
struct avltree ck;
/** Table of dirents in sorted order. */
struct avltree sorted;
/** Heuristic. Expect 0. */
uint32_t collisions;
} avl;
} fsdir; /**< DIRECTORY data */
} fsobj;
}
2.2.1 mdc_lookup_uncached函数,了解元数据怎么加入cache中之后,就会理解怎么查找。
在调用了FSAL文件系统(CEPH)的lookup之后,进行cache的创建操作,这里主要看mdcache_alloc_and_check_handle流程:

看流程比较清晰,做三个事情:
1、新建条目。
2、增加到父目录的avl树中。
3、针对目录保存父目录的key。
下面分别将这三件事情做了什么详细说明:
2.2.2新建条目
这个是最复杂的事情,首先还是照常先看流程图:

这里着重讨论新建部分,主要是针对目录的avl树的初始化,以及加入到全局的avl树中。
对于目录来说,会初始化它子目录的avl树,以缓存所有的子目录和文件。下面是
mdcache_entry->fsobj.fsdir.avl的结构的定义。
struct {
/*目录下条目的avl树Children by name hash */
struct avltree t;
/*删除条目的avl树 删除*/
struct avltree c;
/** FSAL的cookie的构成的avl树 */
struct avltree ck;
/**排序的avl树 需要支持fso_compute_readdir_cookie, 暂时不分析*/
struct avltree sorted;
/** 冲突标记0,暂时不知道搞什么飞机的东西. */
uint32_t collisions;
} avl;
主要是两棵树,t 和 ck
,t保存以文件名的hash值作为比较值的avl树,可以用来查找某个文件,而ck则构建的以子文件或目录在文件夹中的offset为比较值的avl树,主要用来列举目录的所有或部分条目。
主要对这几个树进行初始化。
然后加入到全局的avl树(key的hash值作为avl树的比较值)中。
struct cih_lookup_table结构体保存的全局的cache,默认有7个分区(配置文件中Nparts设置),每个分区存在一个avl树,而且每个分区有cache字段,直接cache了32633个cache条目。全局cache查找策略是,通过key的hash直接查找,如果在cache没有查到,才到avl树中找,找到了之后替换掉cache。
struct cih_lookup_table {
GSH_CACHE_PAD(0);
cih_partition_t *partition;
uint32_t npart;
uint32_t cache_sz;
};
/* Support inline lookups */
extern struct cih_lookup_table cih_fhcache;
增加到父目录的avl树中
主要做到是事情是新建一个mdcache_dir_entry_t条目,将其加入到的avl树(t, ck)中。
2.2.3 针对目录保存父目录的key
这里在上面提过,如果查找到的是目录,需要将父目录的key赋值给目录cache条目的
fsobj.fsdir.parent字段,方便lookup..的查找。
mdc_try_get_cached存在cache的流程
主要是在parent的avl树t中查找,如果查到了,在全局的avl树中确认存在,即返回。
cache每个avl树的作用的简单总结
全局avl树的作用
通过key快速查询mdcache_entry_t条目信息。
目录avl树中的t
查找子文件或者目录时使用。
目录avl树中的ck
主要是readdir使用
三,cache_inode_readdir与mdcache_readdir
mdcache_readdir
在2.5.0之后的版本中,加入了readdir chunk,可以不完全将目录存入cache中,默认每个chunk存128个条目(可以通过Dir_Chunk设置),整个系统chunk的水线为10000(可以通过Chunks_HWMark设置)。chunk也有自己的lru列表,如果超过chunk的数目,就会被踢掉。
if (test_mde_flags(directory, MDCACHE_BYPASS_DIRCACHE)) {
/* Not caching dirents; pass through directly to FSAL */
return mdcache_readdir_uncached(directory, whence, dir_state,
cb, attrmask, eod_met);
}
if (mdcache_param.dir.avl_chunk > 0) {
/* Dirent chunking is enabled. */
LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_CACHE_INODE,
"Calling mdcache_readdir_chunked whence=%"PRIx64,
whence ? *whence : (uint64_t) 0);
return mdcache_readdir_chunked(directory,
whence ? *whence : (uint64_t) 0,
dir_state, cb, attrmask,
eod_met);
}
MDCACHE_BYPASS_DIRCACHE标记目前只有在没有开启chunk的情况下,如果目录过大,会打上此标记,不会被缓存,开启了chunk,此标记永远失效。除了这两行之外,后面的操作是在没有开启chunk的情况下的流程,暂时不做分析。所以mdcache_readdir其实是执行了mdcache_readdir_chunked函数。
mdcache_readdir_chunked的流程如下:

还是和lookup一样,我们先看没有找到的流程。
3.1
mdcache_populate_dir_chunk
流程如下:

做了3件事情:
1、mdcache_get_chunk。
2、调用本地的readdir操作。
3、本地的readdir调用回调函数mdc_readdir_chunk_object。
3.1.1 mdcache_get_chunk
这个函数是获取一个可用的chunk,如果全局的chunk数达到了阈值,就从lru中淘汰出一个。
3.1.2 调用本地的readdir操作
本地读取数据
3.1.3 本地每读取一个数据,调用一次mdc_readdir_chunk_object回调函数,我们照常看看mdc_readdir_chunk_object的流程

其实可以发现,很多事情和lookup类似的。与lookup不同的是,lookup如果没有找到,会将chunk置无效(compute_readdir_cookie没有实现的情况下,如果实现了,会加入chunk中),而在这里,chunk的结构体中,存在一个dirents参数,这里保存这个chunk的所有文件的链表。
3.2 mdcache_avl_lookup_ck
现在我们回头看看直接在cache中找的函数,其实就是遍历ck的过程。在mdcache_entry_t结构体中,存在一个
first_ck的字段,作为一个目录ck的初始值,每个chunk中存这next_ck的字段,就可以进行ck的遍历操作。
3.3 chunk->dirents的loop操作
针对每个ck查找到的chunk,对chunk->dirents 的loop操作,对每个cache条目,调用上层的回调函数,已完成readdir的操作。