diff mbox

[V9fs-developer] Review request: v9fs xattr cache

Message ID 55643590.2000203@unitedstack.com (mailing list archive)
State Rejected, archived
Headers show

Commit Message

juncheng bai May 26, 2015, 8:57 a.m. UTC
Hi, All.

This week, I implement the feature:xattr cache. But I don't know how to
push my code to community, I post my compare file in this mail.
The different is between remotes/origin/v9fs-devel and my local branch
based on remotes/origin/v9fs-devel.

The base idea:
Query a xattr, cache one, don't like lustre cache all xattrs at the
first time.

How to implement:
Provide two queues, the first one is fast path that the xattr have been
cached and use rwlock to protect; the second one is slow path that
the xattr is querying from the backend. The aim is that multipath task
can query the same xattr at the same time, and multi xattr can be queryed.



Looking forward to your assessment and suggestion.
-----
juncheng bai
diff --git a/Documentation/filesystems/9p.txt b/Documentation/filesystems/9p.txt
index fec7144..0d6173b 100644
--- a/Documentation/filesystems/9p.txt
+++ b/Documentation/filesystems/9p.txt
@@ -91,6 +91,7 @@ OPTIONS
 			0x200 = display Fid debug
 			0x400 = display packet debug
 			0x800 = display fscache tracing debug
+			0x1000 = display xattrcache tracing debug
 
   rfdno=n	the file descriptor for reading with trans=fd
 
@@ -133,6 +134,8 @@ OPTIONS
 		cache tags for existing cache sessions can be listed at
 		/sys/fs/9p/caches. (applies only to cache=fscache)
 
+  xattrcache	enable extended attributes cache
+
 RESOURCES
 =========
 
diff --git a/fs/9p/Makefile b/fs/9p/Makefile
index ff7be98..68ee80c 100644
--- a/fs/9p/Makefile
+++ b/fs/9p/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_9P_FS) := 9p.o
 	v9fs.o \
 	fid.o  \
 	xattr.o \
+	xattr_cache.o \
 	xattr_user.o \
 	xattr_trusted.o
 
diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c
index 620d934..46fdc86 100644
--- a/fs/9p/v9fs.c
+++ b/fs/9p/v9fs.c
@@ -38,11 +38,12 @@
 #include "v9fs.h"
 #include "v9fs_vfs.h"
 #include "cache.h"
+#include "xattr_cache.h"
 
 static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
 static LIST_HEAD(v9fs_sessionlist);
 struct kmem_cache *v9fs_inode_cache;
-
+struct kmem_cache *v9fs_xattr_cache;
 /*
  * Option Parsing (code inspired by NFS code)
  *  NOTE: each transport will parse its own options
@@ -60,7 +61,9 @@ enum {
 	/* Access options */
 	Opt_access, Opt_posixacl,
 	/* Error token */
-	Opt_err
+	Opt_err,
+	/* xattr cache */
+	Opt_xcache
 };
 
 static const match_table_t tokens = {
@@ -78,6 +81,7 @@ static const match_table_t tokens = {
 	{Opt_cachetag, "cachetag=%s"},
 	{Opt_access, "access=%s"},
 	{Opt_posixacl, "posixacl"},
+	{Opt_xcache, "xattrcache"},
 	{Opt_err, NULL}
 };
 
@@ -297,7 +301,8 @@ static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
 				 "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
 #endif
 			break;
-
+		case Opt_xcache:
+			v9ses->xcache = 1;
 		default:
 			continue;
 		}
@@ -589,6 +594,17 @@ static int v9fs_init_inode_cache(void)
 	return 0;
 }
 
+static int v9fs_init_xattr_cache(void)
+{
+	v9fs_xattr_cache = kmem_cache_create("v9fs_xattr_cache",
+					sizeof(struct xattr_item),
+					0, 0, NULL);
+	if(!v9fs_xattr_cache)
+		return -ENOMEM;
+
+	return 0;
+}
+
 /**
  * v9fs_destroy_inode_cache - destroy the cache of 9P inode
  *
@@ -603,16 +619,31 @@ static void v9fs_destroy_inode_cache(void)
 	kmem_cache_destroy(v9fs_inode_cache);
 }
 
+static void v9fs_destroy_xattr_cache(void)
+{
+	rcu_barrier();
+	kmem_cache_destroy(v9fs_xattr_cache);
+}
+
 static int v9fs_cache_register(void)
 {
 	int ret;
 	ret = v9fs_init_inode_cache();
 	if (ret < 0)
 		return ret;
+
+	ret = v9fs_init_xattr_cache();
+	if(ret < 0) {
+		v9fs_destroy_inode_cache();
+		return ret;
+	}
+
 #ifdef CONFIG_9P_FSCACHE
 	ret = fscache_register_netfs(&v9fs_cache_netfs);
-	if (ret < 0)
+	if (ret < 0) {
 		v9fs_destroy_inode_cache();
+		v9fs_destroy_xattr_cache();
+	}
 #endif
 	return ret;
 }
@@ -620,6 +651,7 @@ static int v9fs_cache_register(void)
 static void v9fs_cache_unregister(void)
 {
 	v9fs_destroy_inode_cache();
+	v9fs_destroy_xattr_cache();
 #ifdef CONFIG_9P_FSCACHE
 	fscache_unregister_netfs(&v9fs_cache_netfs);
 #endif
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
index fb9ffcb..9a19cbd 100644
--- a/fs/9p/v9fs.h
+++ b/fs/9p/v9fs.h
@@ -101,6 +101,7 @@ struct v9fs_session_info {
 	unsigned short debug;
 	unsigned int afid;
 	unsigned int cache;
+	unsigned short xcache;
 #ifdef CONFIG_9P_FSCACHE
 	char *cachetag;
 	struct fscache_cookie *fscache;
@@ -131,6 +132,12 @@ struct v9fs_inode {
 	struct p9_fid *writeback_fid;
 	struct mutex v_mutex;
 	struct inode vfs_inode;
+
+	rwlock_t xal_lock;
+	struct list_head xal_head;	/* fast path, xattr is available from cache */
+	/* how to move to xal_head, need hold two locks */
+	spinlock_t xaf_lock;
+	struct list_head xaf_head;	/* slow path, xattr is lookup from backend */
 };
 
 static inline struct v9fs_inode *V9FS_I(const struct inode *inode)
diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h
index 5a0db6d..41bcd2d 100644
--- a/fs/9p/v9fs_vfs.h
+++ b/fs/9p/v9fs_vfs.h
@@ -53,6 +53,7 @@ extern const struct file_operations v9fs_cached_file_operations_dotl;
 extern const struct file_operations v9fs_mmap_file_operations;
 extern const struct file_operations v9fs_mmap_file_operations_dotl;
 extern struct kmem_cache *v9fs_inode_cache;
+extern struct kmem_cache *v9fs_xattr_cache;
 
 struct inode *v9fs_alloc_inode(struct super_block *sb);
 void v9fs_destroy_inode(struct inode *inode);
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 39eb2bc..718dd45 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -249,6 +249,12 @@ struct inode *v9fs_alloc_inode(struct super_block *sb)
 	v9inode->writeback_fid = NULL;
 	v9inode->cache_validity = 0;
 	mutex_init(&v9inode->v_mutex);
+
+	rwlock_init(&v9inode->xal_lock);
+	INIT_LIST_HEAD(&v9inode->xal_head);
+	spin_lock_init(&v9inode->xaf_lock);
+	INIT_LIST_HEAD(&v9inode->xaf_head);
+
 	return &v9inode->vfs_inode;
 }
 
@@ -260,6 +266,8 @@ struct inode *v9fs_alloc_inode(struct super_block *sb)
 static void v9fs_i_callback(struct rcu_head *head)
 {
 	struct inode *inode = container_of(head, struct inode, i_rcu);
+
+	v9fs_free_inode_xattr_items(inode);
 	kmem_cache_free(v9fs_inode_cache, V9FS_I(inode));
 }
 
diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c
index 0cf44b6..92b8b54 100644
--- a/fs/9p/xattr.c
+++ b/fs/9p/xattr.c
@@ -19,8 +19,10 @@
 #include <net/9p/9p.h>
 #include <net/9p/client.h>
 
+#include "v9fs.h"
 #include "fid.h"
 #include "xattr.h"
+#include "xattr_cache.h"
 
 ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name,
 			   void *buffer, size_t buffer_size)
@@ -56,6 +58,95 @@ ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name,
 	return retval;
 }
 
+static ssize_t __v9fs_xattr_get(struct dentry *dentry, const char *name,
+				void *buffer, size_t buffer_size)
+{
+	struct p9_fid *fid;
+
+	fid = v9fs_fid_lookup(dentry);
+	if (IS_ERR(fid))
+		return PTR_ERR(fid);
+
+	return v9fs_fid_xattr_get(fid, name, buffer, buffer_size);
+}
+
+static ssize_t __v9fs_xattr_get_cached(struct dentry *dentry, const char *name,
+				void *buffer, size_t buffer_size)
+{
+	ssize_t ret;
+	struct xattr_item *xi;
+
+	xi = v9fs_find_cached_xattr_item(dentry, name);
+	if (!xi) {
+		xi = v9fs_find_or_create_cached_xattr_item_slow(dentry, name);
+		if (IS_ERR(xi))
+			return __v9fs_xattr_get(dentry, name, buffer, buffer_size);
+	}
+
+	if (v9fs_xif_test_clear_new(xi)) {
+	again:
+		ret = __v9fs_xattr_get(dentry, name, buffer, buffer_size);
+		v9fs_xattr_item_setval(xi, buffer, buffer_size, ret);
+		v9fs_move_xattr_item_to_fast(dentry, xi);
+		goto exit;
+	} else {
+		wait_xi_available(xi);
+
+		if (v9fs_xif_test_enotsupp(xi)) {
+			ret = -EOPNOTSUPP;
+			goto exit;
+		}
+
+		/* check whether the xattr name is exist */
+		if (buffer_size == 0) {
+			if (v9fs_xif_test_normal(xi) ||
+					v9fs_xif_test_nodata(xi))
+				ret = xi->xi_vlen;
+				goto exit;
+		}
+
+		if (v9fs_xif_test_nodata(xi)) {
+			if (xi->xi_vlen > buffer_size) {
+				ret = -ERANGE;
+				goto exit;
+			} else {
+				v9fs_xif_set_unavail(xi);
+				v9fs_xif_clear_nodata(xi);
+				goto again;
+			}
+		}
+
+		if (v9fs_xif_test_erange(xi)) {
+			if (xi->xi_vlen >= buffer_size) {
+				ret = -ERANGE;
+				goto exit;
+			} else {
+				v9fs_xif_set_unavail(xi);
+				v9fs_xif_clear_erange(xi);
+				goto again;
+			}
+		}
+
+		if (v9fs_xif_test_normal(xi)) {
+			if(xi->xi_vlen > buffer_size) {
+				ret = -ERANGE;
+				goto exit;
+			} else {
+				strcpy(buffer, xi->xi_value);
+				ret = xi->xi_vlen;
+				goto exit;
+			}
+		}
+
+		BUG_ON(1);
+		ret = -EINVAL;
+	}
+exit:
+	v9fs_wakeup_xi_available(xi);
+	v9fs_xattr_item_dec_ref(dentry, xi);
+
+	return ret;
+}
 
 /*
  * v9fs_xattr_get()
@@ -70,15 +161,74 @@ ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name,
 ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name,
 		       void *buffer, size_t buffer_size)
 {
-	struct p9_fid *fid;
+	struct v9fs_session_info *v9ses;
 
 	p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu\n",
 		 name, buffer_size);
-	fid = v9fs_fid_lookup(dentry);
+
+	v9ses = v9fs_dentry2v9ses(dentry);
+	/* when listxattr, the @name is NULL*/
+	if (v9ses->xcache && name)
+		return __v9fs_xattr_get_cached(dentry, name, buffer, buffer_size);
+	else
+		return __v9fs_xattr_get(dentry, name, buffer, buffer_size);
+}
+
+static int __v9fs_xattr_set(struct dentry *dentry, const char *name,
+			const void *value, size_t value_len, int flags)
+{
+	struct p9_fid *fid = v9fs_fid_lookup(dentry);
 	if (IS_ERR(fid))
 		return PTR_ERR(fid);
+	return v9fs_fid_xattr_set(fid, name, value, value_len, flags);
+}
 
-	return v9fs_fid_xattr_get(fid, name, buffer, buffer_size);
+static int __v9fs_xattr_set_cached(struct dentry *dentry, const char *name,
+			const void *value, size_t value_len, int flags)
+{
+	int ret;
+	struct xattr_item *xi;
+
+	/* remove xattr item */
+	if (flags == XATTR_REPLACE && value == NULL) {
+		xi = v9fs_find_cached_xattr_item_full(dentry, name);
+		if (xi) {
+			v9fs_xif_set_removing(xi);
+			v9fs_xif_set_unavail(xi);
+
+			ret = __v9fs_xattr_set(dentry, name, value,
+						value_len, flags);
+			if(ret != 0)
+				/* remove fail */
+				v9fs_xif_clear_removing(xi);
+
+			goto exit;
+		} else
+			return __v9fs_xattr_set(dentry, name, value, value_len, flags);
+	} else {
+		/* create or replace */
+		xi = v9fs_find_cached_xattr_item(dentry, name);
+		if (!xi)
+			xi = v9fs_find_or_create_cached_xattr_item_slow(dentry, name);
+
+		if (!IS_ERR(xi)) {
+			if (!v9fs_xif_test_clear_new(xi))
+				wait_xi_available(xi);
+
+			/* BUG: the return value */
+			ret = __v9fs_xattr_set(dentry, name, value, value_len, flags);
+			if (ret == 0)
+				v9fs_xattr_item_setval(xi, value, value_len, value_len);
+
+			goto exit;
+		} else
+			return __v9fs_xattr_set(dentry, name, value, value_len, flags);
+	}
+
+exit:
+	v9fs_wakeup_xi_available(xi);
+	v9fs_xattr_item_dec_ref(dentry, xi);
+	return ret;
 }
 
 /*
@@ -96,10 +246,13 @@ ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name,
 int v9fs_xattr_set(struct dentry *dentry, const char *name,
 		   const void *value, size_t value_len, int flags)
 {
-	struct p9_fid *fid = v9fs_fid_lookup(dentry);
-	if (IS_ERR(fid))
-		return PTR_ERR(fid);
-	return v9fs_fid_xattr_set(fid, name, value, value_len, flags);
+	struct v9fs_session_info *v9ses;
+
+	v9ses = v9fs_dentry2v9ses(dentry);
+	if (v9ses->xcache)
+		return __v9fs_xattr_set_cached(dentry, name, value, value_len, flags);
+	else
+		return __v9fs_xattr_set(dentry, name, value, value_len, flags);
 }
 
 int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name,
diff --git a/fs/9p/xattr_cache.c b/fs/9p/xattr_cache.c
new file mode 100644
index 0000000..8474305
--- /dev/null
+++ b/fs/9p/xattr_cache.c
@@ -0,0 +1,258 @@
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include "xattr.h"
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "xattr_cache.h"
+
+static void v9fs_init_xattr_item(struct xattr_item *xi, const char *xname)
+{
+	xi->xi_name = kstrdup(xname, GFP_NOFS);
+	xi->xi_nlen = strlen(xname);
+
+	xi->xi_value = NULL;
+	xi->xi_vlen = 0;
+
+	v9fs_xif_set_unavail(xi);
+	v9fs_xif_set_slowpath(xi);
+	v9fs_xif_set_new(xi);
+	atomic_set(&xi->ref, 1);
+}
+
+static struct xattr_item *v9fs_find_cached_xattr_slow_lock(
+			struct list_head *head,
+			const char *xname,
+			int *removing)
+{
+	struct xattr_item *xi = NULL;
+	int found = 0;
+
+	list_for_each_entry (xi, head, xi_list) {
+		if (0 == memcmp(xname, xi->xi_name, xi->xi_nlen)) {
+			p9_debug(P9_DEBUG_XCACHE, "found cached xattr from slow path,"
+				"name:%s nlen:%u flags:%lu\n",
+				xname, xi->xi_nlen, xi->flags);
+
+			if (v9fs_xif_test_removing(xi)) {
+				found = 0;
+				*removing = 1;
+				break;
+			}
+
+			found = 1;
+			break;
+		}
+	}
+
+	if(!found)
+		xi = NULL;
+	else
+		atomic_inc(&xi->ref);
+
+	return xi;
+}
+
+static void v9fs_free_cached_xattr_item(struct dentry *dentry,
+			struct xattr_item *xi)
+{
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+	int flag = v9fs_xif_test_slowpath(xi);
+
+	p9_debug(P9_DEBUG_XCACHE, "free cached xattr item,"
+				"name:%s flags:%lu\n",
+				xi->xi_name, xi->flags);
+	if (flag)
+		spin_lock(&v9inode->xaf_lock);
+	else
+		write_lock(&v9inode->xal_lock);
+
+	list_del(&xi->xi_list);
+	if (xi->xi_value)
+		kfree(xi->xi_value);
+	kfree(xi->xi_name);
+	kmem_cache_free(v9fs_xattr_cache, xi);
+
+	if (flag)
+		spin_unlock(&v9inode->xaf_lock);
+	else
+		write_unlock(&v9inode->xal_lock);
+}
+
+struct xattr_item *v9fs_find_cached_xattr_item(struct dentry *dentry,
+			const char *xname)
+{
+	int found;
+	struct v9fs_inode *v9inode;
+	struct xattr_item * xi;
+
+	v9inode = V9FS_I(dentry->d_inode);
+
+	read_lock(&v9inode->xal_lock);
+
+	found = 0;
+	list_for_each_entry(xi, &v9inode->xal_head, xi_list) {
+		if (0 == memcmp(xname, xi->xi_name, xi->xi_nlen) &&
+				!v9fs_xif_test_removing(xi)) {
+			p9_debug(P9_DEBUG_XCACHE, "found cached xattr from fast path,"
+				"name:%s nlen:%u flags:%lu\n",
+				xname, xi->xi_nlen, xi->flags);
+
+			found = 1;
+			break;
+		}
+	}
+
+	read_unlock(&v9inode->xal_lock);
+
+	if (!found)
+		xi = NULL;
+	else
+		atomic_inc(&xi->ref);
+
+	return xi;
+}
+
+struct xattr_item *v9fs_find_cached_xattr_item_full(
+			struct dentry *dentry,
+			const char *xname)
+{
+	int removing = 0;
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+	struct xattr_item *xi;
+
+	xi = v9fs_find_cached_xattr_item(dentry, xname);
+	if (!xi) {
+		spin_lock(&v9inode->xaf_lock);
+		xi = v9fs_find_cached_xattr_slow_lock(&v9inode->xaf_head,
+				xname, &removing);
+		spin_unlock(&v9inode->xaf_lock);
+	}
+
+	return xi;
+}
+
+struct xattr_item *v9fs_find_or_create_cached_xattr_item_slow(
+			struct dentry *dentry,
+			const char *xname)
+{
+	int removing = 0;
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+	struct xattr_item *xi = NULL;
+
+	spin_lock(&v9inode->xaf_lock);
+	xi = v9fs_find_cached_xattr_slow_lock(&v9inode->xaf_head, xname, &removing);
+	if (!xi && !removing) {
+		xi = kmem_cache_alloc(v9fs_xattr_cache, GFP_NOFS);
+		if (!xi) {
+			spin_unlock(&v9inode->xaf_lock);
+			p9_debug(P9_DEBUG_XCACHE, "create cached xattr item fail,"
+				"name:%s\n", xname);
+			return ERR_PTR(-ENOMEM);
+		}
+
+		p9_debug(P9_DEBUG_XCACHE, "create cached xattr item success,"
+				"name:%s\n", xname);
+
+		v9fs_init_xattr_item(xi, xname);
+		list_add(&xi->xi_list, &v9inode->xaf_head);
+	} else if (!xi) {
+		spin_unlock(&v9inode->xaf_lock);
+		return ERR_PTR(-ENOENT);
+	}
+	spin_unlock(&v9inode->xaf_lock);
+
+	return xi;
+}
+
+void v9fs_wakeup_xi_available(struct xattr_item *xi)
+{
+	v9fs_xif_clear_unavail(xi);
+	smp_mb__after_atomic();
+	wake_up_bit(&xi->flags, V9FS_XI_Unavail);
+}
+
+void v9fs_move_xattr_item_to_fast(struct dentry *dentry, struct xattr_item *xi)
+{
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+
+	p9_debug(P9_DEBUG_XCACHE, "move xattr item to fast path,"
+				"name:%s flags:%lu\n", xi->xi_name, xi->flags);
+
+	write_lock(&v9inode->xal_lock);
+	spin_lock(&v9inode->xaf_lock);
+
+	list_move(&xi->xi_list, &v9inode->xal_head);
+	v9fs_xif_clear_slowpath(xi);
+
+	spin_unlock(&v9inode->xaf_lock);
+	write_unlock(&v9inode->xal_lock);
+}
+
+int v9fs_xattr_item_setval(struct xattr_item *xi, const char *buffer,
+				size_t buffer_size, ssize_t ret)
+{
+	p9_debug(P9_DEBUG_XCACHE, "set value for xattr item,"
+			"name:%s flags:%lu buffer:%s buffer_size:%zu ret:%zu\n",
+			xi->xi_name, xi->flags, buffer ? buffer : "null",
+			buffer_size, ret);
+
+	if (ret >= 0) {
+		if (buffer_size) {
+			if(xi->xi_value)
+				kfree(xi->xi_value);
+
+			xi->xi_vlen = ret;
+			xi->xi_value = kstrdup(buffer, GFP_NOFS);
+			if (!xi->xi_value) {
+					v9fs_xif_set_nodata(xi);
+					return -ENOMEM;
+			}
+
+			v9fs_xif_set_normal(xi);
+		} else {
+			v9fs_xif_set_nodata(xi);
+			xi->xi_vlen = ret;
+		}
+	} else if (ret == -EOPNOTSUPP) {
+		v9fs_xif_set_enotsupp(xi);
+	} else if (ret == -ERANGE) {
+		xi->xi_vlen = buffer_size;
+		v9fs_xif_set_erange(xi);
+	}
+
+	return ret;
+}
+
+void v9fs_xattr_item_dec_ref(struct dentry *dentry, struct xattr_item *xi)
+{
+	if (v9fs_xif_test_removing(xi) && atomic_dec_and_test(&xi->ref))
+		v9fs_free_cached_xattr_item(dentry, xi);
+	else
+		atomic_dec(&xi->ref);
+}
+
+void v9fs_free_inode_xattr_items(struct inode *inode)
+{
+	struct v9fs_inode *v9inode = V9FS_I(inode);
+	struct xattr_item *xi, *tmp;
+
+	p9_debug(P9_DEBUG_XCACHE, "free all xattr items for inode,"
+			"inode:%p\n", inode);
+
+	list_for_each_entry_safe(xi, tmp, &v9inode->xal_head, xi_list) {
+		if (xi->xi_vlen)
+			kfree(xi->xi_value);
+		kfree(xi->xi_name);
+		kmem_cache_free(v9fs_xattr_cache, xi);
+	}
+
+	list_for_each_entry_safe(xi, tmp, &v9inode->xaf_head, xi_list) {
+		if (xi->xi_vlen)
+			kfree(xi->xi_value);
+		kfree(xi->xi_name);
+		kmem_cache_free(v9fs_xattr_cache, xi);
+	}
+}
diff --git a/fs/9p/xattr_cache.h b/fs/9p/xattr_cache.h
new file mode 100644
index 0000000..7df3cce
--- /dev/null
+++ b/fs/9p/xattr_cache.h
@@ -0,0 +1,101 @@
+#ifndef FS_9P_XCACHE_H
+#define FS_9P_XCACHE_H
+
+struct xattr_item {
+	struct list_head xi_list;
+
+	char *xi_name;
+	u16 xi_nlen;
+	char *xi_value;
+	u16 xi_vlen;
+
+	unsigned long flags;
+	atomic_t ref;
+};
+
+enum v9fs_xi_flags {
+	V9FS_XI_Normal,
+	V9FS_XI_Nodata,
+	V9FS_XI_Erange,
+	V9FS_XI_Enotsupp,
+	V9FS_XI_Unavail,
+	V9FS_XI_New,
+	V9FS_XI_Removing,
+	V9FS_XI_Slowpath,
+};
+
+#define V9FS_XIF_FNS(bit, name)						\
+static inline void v9fs_xif_set_##name(struct xattr_item *xi)		\
+{									\
+	set_bit(V9FS_XI_##bit, &(xi)->flags);				\
+}									\
+static inline void v9fs_xif_clear_##name(struct xattr_item *xi)		\
+{									\
+	clear_bit(V9FS_XI_##bit, &(xi)->flags);				\
+}									\
+static inline int v9fs_xif_test_##name(const struct xattr_item *xi)		\
+{									\
+	return test_bit(V9FS_XI_##bit, &(xi)->flags);			\
+}
+
+#define V9FS_XIF_TAS(bit, name)						\
+static inline int v9fs_xif_test_set_##name(struct xattr_item *xi)	\
+{									\
+	return test_and_set_bit(V9FS_XI_##bit, &(xi)->flags);		\
+}									\
+static inline int v9fs_xif_test_clear_##name(struct xattr_item *xi)	\
+{									\
+	return test_and_clear_bit(V9FS_XI_##bit, &(xi)->flags);		\
+}
+
+
+V9FS_XIF_FNS(Normal, normal);
+V9FS_XIF_FNS(Nodata, nodata);
+V9FS_XIF_FNS(Erange, erange);
+V9FS_XIF_FNS(Enotsupp, enotsupp);
+V9FS_XIF_FNS(Unavail, unavail);
+V9FS_XIF_FNS(New, new);
+V9FS_XIF_FNS(Removing, removing);
+V9FS_XIF_FNS(Slowpath, slowpath);
+
+V9FS_XIF_TAS(New, new);
+V9FS_XIF_TAS(Unavail, unavail);
+
+static int sleep_on_xattritem(void *word)
+{
+	schedule();
+	return 0;
+}
+
+static inline void __wait_xi_available(struct xattr_item *xi)
+{
+	wait_on_bit(&xi->flags, V9FS_XI_Unavail,
+					sleep_on_xattritem, TASK_UNINTERRUPTIBLE);
+}
+
+static inline void wait_xi_available(struct xattr_item *xi)
+{
+	might_sleep();
+	if (v9fs_xif_test_set_unavail(xi)) {
+		__wait_xi_available(xi);
+	}
+}
+
+extern struct xattr_item *v9fs_find_cached_xattr_item(struct dentry *dentry,
+			const char *xname);
+extern struct xattr_item *v9fs_find_cached_xattr_item_full(
+			struct dentry *dentry,
+			const char *xname);
+extern struct xattr_item *v9fs_find_or_create_cached_xattr_item_slow(
+			struct dentry *dentry,
+			const char *xname);
+extern void v9fs_wakeup_xi_available(struct xattr_item *xi);
+extern void v9fs_move_xattr_item_to_fast(struct dentry *dentry,
+			struct xattr_item *xi);
+extern int v9fs_xattr_item_setval(struct xattr_item *xi, const char *buffer,
+			size_t buffer_size, ssize_t ret);
+extern void v9fs_xattr_item_dec_ref(struct dentry *dentry,
+			struct xattr_item *xi);
+extern void v9fs_free_inode_xattr_items(struct inode *inode);
+
+#endif
diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h
index 27dfe85..8346030 100644
--- a/include/net/9p/9p.h
+++ b/include/net/9p/9p.h
@@ -59,6 +59,7 @@ enum p9_debug_flags {
 	P9_DEBUG_PKT =		(1<<10),
 	P9_DEBUG_FSC =		(1<<11),
 	P9_DEBUG_VPKT =		(1<<12),
+	P9_DEBUG_XCACHE =	(1<<13),
 };
 
 #ifdef CONFIG_NET_9P_DEBUG
------------------------------------------------------------------------------
One dashboard for servers and applications across Physical-Virtual-Cloud 
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.
http://ad.doubleclick.net/ddm/clk/290420510;117567292;y
diff mbox

Patch

diff --git a/Documentation/filesystems/9p.txt 
b/Documentation/filesystems/9p.txt
index fec7144..0d6173b 100644
--- a/Documentation/filesystems/9p.txt
+++ b/Documentation/filesystems/9p.txt
@@ -91,6 +91,7 @@  OPTIONS
  			0x200 = display Fid debug
  			0x400 = display packet debug
  			0x800 = display fscache tracing debug
+			0x1000 = display xattrcache tracing debug

    rfdno=n	the file descriptor for reading with trans=fd

@@ -133,6 +134,8 @@  OPTIONS
  		cache tags for existing cache sessions can be listed at
  		/sys/fs/9p/caches. (applies only to cache=fscache)

+  xattrcache	enable extended attributes cache
+
  RESOURCES
  =========

diff --git a/fs/9p/Makefile b/fs/9p/Makefile
index ff7be98..68ee80c 100644
--- a/fs/9p/Makefile
+++ b/fs/9p/Makefile
@@ -11,6 +11,7 @@  obj-$(CONFIG_9P_FS) := 9p.o
  	v9fs.o \
  	fid.o  \
  	xattr.o \
+	xattr_cache.o \
  	xattr_user.o \
  	xattr_trusted.o

diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c
index 620d934..46fdc86 100644
--- a/fs/9p/v9fs.c
+++ b/fs/9p/v9fs.c
@@ -38,11 +38,12 @@ 
  #include "v9fs.h"
  #include "v9fs_vfs.h"
  #include "cache.h"
+#include "xattr_cache.h"

  static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
  static LIST_HEAD(v9fs_sessionlist);
  struct kmem_cache *v9fs_inode_cache;
-
+struct kmem_cache *v9fs_xattr_cache;
  /*
   * Option Parsing (code inspired by NFS code)
   *  NOTE: each transport will parse its own options
@@ -60,7 +61,9 @@  enum {
  	/* Access options */
  	Opt_access, Opt_posixacl,
  	/* Error token */
-	Opt_err
+	Opt_err,
+	/* xattr cache */
+	Opt_xcache
  };

  static const match_table_t tokens = {
@@ -78,6 +81,7 @@  static const match_table_t tokens = {
  	{Opt_cachetag, "cachetag=%s"},
  	{Opt_access, "access=%s"},
  	{Opt_posixacl, "posixacl"},
+	{Opt_xcache, "xattrcache"},
  	{Opt_err, NULL}
  };

@@ -297,7 +301,8 @@  static int v9fs_parse_options(struct 
v9fs_session_info *v9ses, char *opts)
  				 "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
  #endif
  			break;
-
+		case Opt_xcache:
+			v9ses->xcache = 1;
  		default:
  			continue;
  		}
@@ -589,6 +594,17 @@  static int v9fs_init_inode_cache(void)
  	return 0;
  }

+static int v9fs_init_xattr_cache(void)
+{
+	v9fs_xattr_cache = kmem_cache_create("v9fs_xattr_cache",
+					sizeof(struct xattr_item),
+					0, 0, NULL);
+	if(!v9fs_xattr_cache)
+		return -ENOMEM;
+
+	return 0;
+}
+
  /**
   * v9fs_destroy_inode_cache - destroy the cache of 9P inode
   *
@@ -603,16 +619,31 @@  static void v9fs_destroy_inode_cache(void)
  	kmem_cache_destroy(v9fs_inode_cache);
  }

+static void v9fs_destroy_xattr_cache(void)
+{
+	rcu_barrier();
+	kmem_cache_destroy(v9fs_xattr_cache);
+}
+
  static int v9fs_cache_register(void)
  {
  	int ret;
  	ret = v9fs_init_inode_cache();
  	if (ret < 0)
  		return ret;
+
+	ret = v9fs_init_xattr_cache();
+	if(ret < 0) {
+		v9fs_destroy_inode_cache();
+		return ret;
+	}
+
  #ifdef CONFIG_9P_FSCACHE
  	ret = fscache_register_netfs(&v9fs_cache_netfs);
-	if (ret < 0)
+	if (ret < 0) {
  		v9fs_destroy_inode_cache();
+		v9fs_destroy_xattr_cache();
+	}
  #endif
  	return ret;
  }
@@ -620,6 +651,7 @@  static int v9fs_cache_register(void)
  static void v9fs_cache_unregister(void)
  {
  	v9fs_destroy_inode_cache();
+	v9fs_destroy_xattr_cache();
  #ifdef CONFIG_9P_FSCACHE
  	fscache_unregister_netfs(&v9fs_cache_netfs);
  #endif
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
index fb9ffcb..9a19cbd 100644
--- a/fs/9p/v9fs.h
+++ b/fs/9p/v9fs.h
@@ -101,6 +101,7 @@  struct v9fs_session_info {
  	unsigned short debug;
  	unsigned int afid;
  	unsigned int cache;
+	unsigned short xcache;
  #ifdef CONFIG_9P_FSCACHE
  	char *cachetag;
  	struct fscache_cookie *fscache;
@@ -131,6 +132,12 @@  struct v9fs_inode {
  	struct p9_fid *writeback_fid;
  	struct mutex v_mutex;
  	struct inode vfs_inode;
+
+	rwlock_t xal_lock;
+	struct list_head xal_head;	/* fast path, xattr is available from cache */
+	/* how to move to xal_head, need hold two locks */
+	spinlock_t xaf_lock;
+	struct list_head xaf_head;	/* slow path, xattr is lookup from backend */
  };

  static inline struct v9fs_inode *V9FS_I(const struct inode *inode)
diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h
index 5a0db6d..41bcd2d 100644
--- a/fs/9p/v9fs_vfs.h
+++ b/fs/9p/v9fs_vfs.h
@@ -53,6 +53,7 @@  extern const struct file_operations 
v9fs_cached_file_operations_dotl;
  extern const struct file_operations v9fs_mmap_file_operations;
  extern const struct file_operations v9fs_mmap_file_operations_dotl;
  extern struct kmem_cache *v9fs_inode_cache;
+extern struct kmem_cache *v9fs_xattr_cache;

  struct inode *v9fs_alloc_inode(struct super_block *sb);
  void v9fs_destroy_inode(struct inode *inode);
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 39eb2bc..718dd45 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -249,6 +249,12 @@  struct inode *v9fs_alloc_inode(struct super_block *sb)
  	v9inode->writeback_fid = NULL;
  	v9inode->cache_validity = 0;
  	mutex_init(&v9inode->v_mutex);
+
+	rwlock_init(&v9inode->xal_lock);
+	INIT_LIST_HEAD(&v9inode->xal_head);
+	spin_lock_init(&v9inode->xaf_lock);
+	INIT_LIST_HEAD(&v9inode->xaf_head);
+
  	return &v9inode->vfs_inode;
  }

@@ -260,6 +266,8 @@  struct inode *v9fs_alloc_inode(struct super_block *sb)
  static void v9fs_i_callback(struct rcu_head *head)
  {
  	struct inode *inode = container_of(head, struct inode, i_rcu);
+
+	v9fs_free_inode_xattr_items(inode);
  	kmem_cache_free(v9fs_inode_cache, V9FS_I(inode));
  }

diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c
index 0cf44b6..92b8b54 100644
--- a/fs/9p/xattr.c
+++ b/fs/9p/xattr.c
@@ -19,8 +19,10 @@ 
  #include <net/9p/9p.h>
  #include <net/9p/client.h>

+#include "v9fs.h"
  #include "fid.h"
  #include "xattr.h"
+#include "xattr_cache.h"

  ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name,
  			   void *buffer, size_t buffer_size)
@@ -56,6 +58,95 @@  ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const 
char *name,
  	return retval;
  }

+static ssize_t __v9fs_xattr_get(struct dentry *dentry, const char *name,
+				void *buffer, size_t buffer_size)
+{
+	struct p9_fid *fid;
+
+	fid = v9fs_fid_lookup(dentry);
+	if (IS_ERR(fid))
+		return PTR_ERR(fid);
+
+	return v9fs_fid_xattr_get(fid, name, buffer, buffer_size);
+}
+
+static ssize_t __v9fs_xattr_get_cached(struct dentry *dentry, const 
char *name,
+				void *buffer, size_t buffer_size)
+{
+	ssize_t ret;
+	struct xattr_item *xi;
+
+	xi = v9fs_find_cached_xattr_item(dentry, name);
+	if (!xi) {
+		xi = v9fs_find_or_create_cached_xattr_item_slow(dentry, name);
+		if (IS_ERR(xi))
+			return __v9fs_xattr_get(dentry, name, buffer, buffer_size);
+	}
+
+	if (v9fs_xif_test_clear_new(xi)) {
+	again:
+		ret = __v9fs_xattr_get(dentry, name, buffer, buffer_size);
+		v9fs_xattr_item_setval(xi, buffer, buffer_size, ret);
+		v9fs_move_xattr_item_to_fast(dentry, xi);
+		goto exit;
+	} else {
+		wait_xi_available(xi);
+
+		if (v9fs_xif_test_enotsupp(xi)) {
+			ret = -EOPNOTSUPP;
+			goto exit;
+		}
+
+		/* check whether the xattr name is exist */
+		if (buffer_size == 0) {
+			if (v9fs_xif_test_normal(xi) ||
+					v9fs_xif_test_nodata(xi))
+				ret = xi->xi_vlen;
+				goto exit;
+		}
+
+		if (v9fs_xif_test_nodata(xi)) {
+			if (xi->xi_vlen > buffer_size) {
+				ret = -ERANGE;
+				goto exit;
+			} else {
+				v9fs_xif_set_unavail(xi);
+				v9fs_xif_clear_nodata(xi);
+				goto again;
+			}
+		}
+
+		if (v9fs_xif_test_erange(xi)) {
+			if (xi->xi_vlen >= buffer_size) {
+				ret = -ERANGE;
+				goto exit;
+			} else {
+				v9fs_xif_set_unavail(xi);
+				v9fs_xif_clear_erange(xi);
+				goto again;
+			}
+		}
+
+		if (v9fs_xif_test_normal(xi)) {
+			if(xi->xi_vlen > buffer_size) {
+				ret = -ERANGE;
+				goto exit;
+			} else {
+				strcpy(buffer, xi->xi_value);
+				ret = xi->xi_vlen;
+				goto exit;
+			}
+		}
+
+		BUG_ON(1);
+		ret = -EINVAL;
+	}
+exit:
+	v9fs_wakeup_xi_available(xi);
+	v9fs_xattr_item_dec_ref(dentry, xi);
+
+	return ret;
+}

  /*
   * v9fs_xattr_get()
@@ -70,15 +161,74 @@  ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, 
const char *name,
  ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name,
  		       void *buffer, size_t buffer_size)
  {
-	struct p9_fid *fid;
+	struct v9fs_session_info *v9ses;

  	p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu\n",
  		 name, buffer_size);
-	fid = v9fs_fid_lookup(dentry);
+
+	v9ses = v9fs_dentry2v9ses(dentry);
+	/* when listxattr, the @name is NULL*/
+	if (v9ses->xcache && name)
+		return __v9fs_xattr_get_cached(dentry, name, buffer, buffer_size);
+	else
+		return __v9fs_xattr_get(dentry, name, buffer, buffer_size);
+}
+
+static int __v9fs_xattr_set(struct dentry *dentry, const char *name,
+			const void *value, size_t value_len, int flags)
+{
+	struct p9_fid *fid = v9fs_fid_lookup(dentry);
  	if (IS_ERR(fid))
  		return PTR_ERR(fid);
+	return v9fs_fid_xattr_set(fid, name, value, value_len, flags);
+}

-	return v9fs_fid_xattr_get(fid, name, buffer, buffer_size);
+static int __v9fs_xattr_set_cached(struct dentry *dentry, const char *name,
+			const void *value, size_t value_len, int flags)
+{
+	int ret;
+	struct xattr_item *xi;
+
+	/* remove xattr item */
+	if (flags == XATTR_REPLACE && value == NULL) {
+		xi = v9fs_find_cached_xattr_item_full(dentry, name);
+		if (xi) {
+			v9fs_xif_set_removing(xi);
+			v9fs_xif_set_unavail(xi);
+
+			ret = __v9fs_xattr_set(dentry, name, value,
+						value_len, flags);
+			if(ret != 0)
+				/* remove fail */
+				v9fs_xif_clear_removing(xi);
+
+			goto exit;
+		} else
+			return __v9fs_xattr_set(dentry, name, value, value_len, flags);
+	} else {
+		/* create or replace */
+		xi = v9fs_find_cached_xattr_item(dentry, name);
+		if (!xi)
+			xi = v9fs_find_or_create_cached_xattr_item_slow(dentry, name);
+
+		if (!IS_ERR(xi)) {
+			if (!v9fs_xif_test_clear_new(xi))
+				wait_xi_available(xi);
+
+			/* BUG: the return value */
+			ret = __v9fs_xattr_set(dentry, name, value, value_len, flags);
+			if (ret == 0)
+				v9fs_xattr_item_setval(xi, value, value_len, value_len);
+
+			goto exit;
+		} else
+			return __v9fs_xattr_set(dentry, name, value, value_len, flags);
+	}
+
+exit:
+	v9fs_wakeup_xi_available(xi);
+	v9fs_xattr_item_dec_ref(dentry, xi);
+	return ret;
  }

  /*
@@ -96,10 +246,13 @@  ssize_t v9fs_xattr_get(struct dentry *dentry, const 
char *name,
  int v9fs_xattr_set(struct dentry *dentry, const char *name,
  		   const void *value, size_t value_len, int flags)
  {
-	struct p9_fid *fid = v9fs_fid_lookup(dentry);
-	if (IS_ERR(fid))
-		return PTR_ERR(fid);
-	return v9fs_fid_xattr_set(fid, name, value, value_len, flags);
+	struct v9fs_session_info *v9ses;
+
+	v9ses = v9fs_dentry2v9ses(dentry);
+	if (v9ses->xcache)
+		return __v9fs_xattr_set_cached(dentry, name, value, value_len, flags);
+	else
+		return __v9fs_xattr_set(dentry, name, value, value_len, flags);
  }

  int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name,
diff --git a/fs/9p/xattr_cache.c b/fs/9p/xattr_cache.c
new file mode 100644
index 0000000..8474305
--- /dev/null
+++ b/fs/9p/xattr_cache.c
@@ -0,0 +1,258 @@ 
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include "xattr.h"
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "xattr_cache.h"
+
+static void v9fs_init_xattr_item(struct xattr_item *xi, const char *xname)
+{
+	xi->xi_name = kstrdup(xname, GFP_NOFS);
+	xi->xi_nlen = strlen(xname);
+
+	xi->xi_value = NULL;
+	xi->xi_vlen = 0;
+
+	v9fs_xif_set_unavail(xi);
+	v9fs_xif_set_slowpath(xi);
+	v9fs_xif_set_new(xi);
+	atomic_set(&xi->ref, 1);
+}
+
+static struct xattr_item *v9fs_find_cached_xattr_slow_lock(
+			struct list_head *head,
+			const char *xname,
+			int *removing)
+{
+	struct xattr_item *xi = NULL;
+	int found = 0;
+
+	list_for_each_entry (xi, head, xi_list) {
+		if (0 == memcmp(xname, xi->xi_name, xi->xi_nlen)) {
+			p9_debug(P9_DEBUG_XCACHE, "found cached xattr from slow path,"
+				"name:%s nlen:%u flags:%lu\n",
+				xname, xi->xi_nlen, xi->flags);
+
+			if (v9fs_xif_test_removing(xi)) {
+				found = 0;
+				*removing = 1;
+				break;
+			}
+
+			found = 1;
+			break;
+		}
+	}
+
+	if(!found)
+		xi = NULL;
+	else
+		atomic_inc(&xi->ref);
+
+	return xi;
+}
+
+static void v9fs_free_cached_xattr_item(struct dentry *dentry,
+			struct xattr_item *xi)
+{
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+	int flag = v9fs_xif_test_slowpath(xi);
+
+	p9_debug(P9_DEBUG_XCACHE, "free cached xattr item,"
+				"name:%s flags:%lu\n",
+				xi->xi_name, xi->flags);
+	if (flag)
+		spin_lock(&v9inode->xaf_lock);
+	else
+		write_lock(&v9inode->xal_lock);
+
+	list_del(&xi->xi_list);
+	if (xi->xi_value)
+		kfree(xi->xi_value);
+	kfree(xi->xi_name);
+	kmem_cache_free(v9fs_xattr_cache, xi);
+
+	if (flag)
+		spin_unlock(&v9inode->xaf_lock);
+	else
+		write_unlock(&v9inode->xal_lock);
+}
+
+struct xattr_item *v9fs_find_cached_xattr_item(struct dentry *dentry,
+			const char *xname)
+{
+	int found;
+	struct v9fs_inode *v9inode;
+	struct xattr_item * xi;
+
+	v9inode = V9FS_I(dentry->d_inode);
+
+	read_lock(&v9inode->xal_lock);
+
+	found = 0;
+	list_for_each_entry(xi, &v9inode->xal_head, xi_list) {
+		if (0 == memcmp(xname, xi->xi_name, xi->xi_nlen) &&
+				!v9fs_xif_test_removing(xi)) {
+			p9_debug(P9_DEBUG_XCACHE, "found cached xattr from fast path,"
+				"name:%s nlen:%u flags:%lu\n",
+				xname, xi->xi_nlen, xi->flags);
+
+			found = 1;
+			break;
+		}
+	}
+
+	read_unlock(&v9inode->xal_lock);
+
+	if (!found)
+		xi = NULL;
+	else
+		atomic_inc(&xi->ref);
+
+	return xi;
+}
+
+struct xattr_item *v9fs_find_cached_xattr_item_full(
+			struct dentry *dentry,
+			const char *xname)
+{
+	int removing = 0;
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+	struct xattr_item *xi;
+
+	xi = v9fs_find_cached_xattr_item(dentry, xname);
+	if (!xi) {
+		spin_lock(&v9inode->xaf_lock);
+		xi = v9fs_find_cached_xattr_slow_lock(&v9inode->xaf_head,
+				xname, &removing);
+		spin_unlock(&v9inode->xaf_lock);
+	}
+
+	return xi;
+}
+
+struct xattr_item *v9fs_find_or_create_cached_xattr_item_slow(
+			struct dentry *dentry,
+			const char *xname)
+{
+	int removing = 0;
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+	struct xattr_item *xi = NULL;
+
+	spin_lock(&v9inode->xaf_lock);
+	xi = v9fs_find_cached_xattr_slow_lock(&v9inode->xaf_head, xname, 
&removing);
+	if (!xi && !removing) {
+		xi = kmem_cache_alloc(v9fs_xattr_cache, GFP_NOFS);
+		if (!xi) {
+			spin_unlock(&v9inode->xaf_lock);
+			p9_debug(P9_DEBUG_XCACHE, "create cached xattr item fail,"
+				"name:%s\n", xname);
+			return ERR_PTR(-ENOMEM);
+		}
+
+		p9_debug(P9_DEBUG_XCACHE, "create cached xattr item success,"
+				"name:%s\n", xname);
+
+		v9fs_init_xattr_item(xi, xname);
+		list_add(&xi->xi_list, &v9inode->xaf_head);
+	} else if (!xi) {
+		spin_unlock(&v9inode->xaf_lock);
+		return ERR_PTR(-ENOENT);
+	}
+	spin_unlock(&v9inode->xaf_lock);
+
+	return xi;
+}
+
+void v9fs_wakeup_xi_available(struct xattr_item *xi)
+{
+	v9fs_xif_clear_unavail(xi);
+	smp_mb__after_atomic();
+	wake_up_bit(&xi->flags, V9FS_XI_Unavail);
+}
+
+void v9fs_move_xattr_item_to_fast(struct dentry *dentry, struct 
xattr_item *xi)
+{
+	struct v9fs_inode *v9inode = V9FS_I(dentry->d_inode);
+
+	p9_debug(P9_DEBUG_XCACHE, "move xattr item to fast path,"
+				"name:%s flags:%lu\n", xi->xi_name, xi->flags);
+
+	write_lock(&v9inode->xal_lock);
+	spin_lock(&v9inode->xaf_lock);
+
+	list_move(&xi->xi_list, &v9inode->xal_head);
+	v9fs_xif_clear_slowpath(xi);
+
+	spin_unlock(&v9inode->xaf_lock);
+	write_unlock(&v9inode->xal_lock);
+}
+
+int v9fs_xattr_item_setval(struct xattr_item *xi, const char *buffer,
+				size_t buffer_size, ssize_t ret)
+{
+	p9_debug(P9_DEBUG_XCACHE, "set value for xattr item,"
+			"name:%s flags:%lu buffer:%s buffer_size:%zu ret:%zu\n",
+			xi->xi_name, xi->flags, buffer ? buffer : "null",
+			buffer_size, ret);
+
+	if (ret >= 0) {
+		if (buffer_size) {
+			if(xi->xi_value)
+				kfree(xi->xi_value);
+
+			xi->xi_vlen = ret;
+			xi->xi_value = kstrdup(buffer, GFP_NOFS);
+			if (!xi->xi_value) {
+					v9fs_xif_set_nodata(xi);
+					return -ENOMEM;
+			}
+
+			v9fs_xif_set_normal(xi);
+		} else {
+			v9fs_xif_set_nodata(xi);
+			xi->xi_vlen = ret;
+		}
+	} else if (ret == -EOPNOTSUPP) {
+		v9fs_xif_set_enotsupp(xi);
+	} else if (ret == -ERANGE) {
+		xi->xi_vlen = buffer_size;
+		v9fs_xif_set_erange(xi);
+	}
+
+	return ret;
+}
+
+void v9fs_xattr_item_dec_ref(struct dentry *dentry, struct xattr_item *xi)
+{
+	if (v9fs_xif_test_removing(xi) && atomic_dec_and_test(&xi->ref))
+		v9fs_free_cached_xattr_item(dentry, xi);
+	else
+		atomic_dec(&xi->ref);
+}
+
+void v9fs_free_inode_xattr_items(struct inode *inode)
+{
+	struct v9fs_inode *v9inode = V9FS_I(inode);
+	struct xattr_item *xi, *tmp;
+
+	p9_debug(P9_DEBUG_XCACHE, "free all xattr items for inode,"
+			"inode:%p\n", inode);
+
+	list_for_each_entry_safe(xi, tmp, &v9inode->xal_head, xi_list) {
+		if (xi->xi_vlen)
+			kfree(xi->xi_value);
+		kfree(xi->xi_name);
+		kmem_cache_free(v9fs_xattr_cache, xi);
+	}
+
+	list_for_each_entry_safe(xi, tmp, &v9inode->xaf_head, xi_list) {
+		if (xi->xi_vlen)
+			kfree(xi->xi_value);
+		kfree(xi->xi_name);
+		kmem_cache_free(v9fs_xattr_cache, xi);
+	}
+}
diff --git a/fs/9p/xattr_cache.h b/fs/9p/xattr_cache.h
new file mode 100644
index 0000000..7df3cce
--- /dev/null
+++ b/fs/9p/xattr_cache.h
@@ -0,0 +1,101 @@ 
+#ifndef FS_9P_XCACHE_H
+#define FS_9P_XCACHE_H
+
+struct xattr_item {
+	struct list_head xi_list;
+
+	char *xi_name;
+	u16 xi_nlen;
+	char *xi_value;
+	u16 xi_vlen;
+
+	unsigned long flags;
+	atomic_t ref;
+};
+
+enum v9fs_xi_flags {
+	V9FS_XI_Normal,
+	V9FS_XI_Nodata,
+	V9FS_XI_Erange,
+	V9FS_XI_Enotsupp,
+	V9FS_XI_Unavail,
+	V9FS_XI_New,
+	V9FS_XI_Removing,
+	V9FS_XI_Slowpath,
+};
+
+#define V9FS_XIF_FNS(bit, name)						\
+static inline void v9fs_xif_set_##name(struct xattr_item *xi)		\
+{									\
+	set_bit(V9FS_XI_##bit, &(xi)->flags);				\
+}									\
+static inline void v9fs_xif_clear_##name(struct xattr_item *xi)		\
+{									\
+	clear_bit(V9FS_XI_##bit, &(xi)->flags);				\
+}									\
+static inline int v9fs_xif_test_##name(const struct xattr_item *xi)		\
+{									\
+	return test_bit(V9FS_XI_##bit, &(xi)->flags);			\
+}
+
+#define V9FS_XIF_TAS(bit, name)						\
+static inline int v9fs_xif_test_set_##name(struct xattr_item *xi)	\
+{									\
+	return test_and_set_bit(V9FS_XI_##bit, &(xi)->flags);		\
+}									\
+static inline int v9fs_xif_test_clear_##name(struct xattr_item *xi)	\
+{									\
+	return test_and_clear_bit(V9FS_XI_##bit, &(xi)->flags);		\
+}
+
+
+V9FS_XIF_FNS(Normal, normal);
+V9FS_XIF_FNS(Nodata, nodata);
+V9FS_XIF_FNS(Erange, erange);
+V9FS_XIF_FNS(Enotsupp, enotsupp);
+V9FS_XIF_FNS(Unavail, unavail);
+V9FS_XIF_FNS(New, new);
+V9FS_XIF_FNS(Removing, removing);
+V9FS_XIF_FNS(Slowpath, slowpath);
+
+V9FS_XIF_TAS(New, new);
+V9FS_XIF_TAS(Unavail, unavail);
+
+static int sleep_on_xattritem(void *word)
+{
+	schedule();
+	return 0;
+}
+
+static inline void __wait_xi_available(struct xattr_item *xi)
+{
+	wait_on_bit(&xi->flags, V9FS_XI_Unavail,
+					sleep_on_xattritem, TASK_UNINTERRUPTIBLE);
+}
+
+static inline void wait_xi_available(struct xattr_item *xi)
+{
+	might_sleep();
+	if (v9fs_xif_test_set_unavail(xi)) {
+		__wait_xi_available(xi);
+	}
+}
+
+extern struct xattr_item *v9fs_find_cached_xattr_item(struct dentry 
*dentry,
+			const char *xname);
+extern struct xattr_item *v9fs_find_cached_xattr_item_full(
+			struct dentry *dentry,
+			const char *xname);
+extern struct xattr_item *v9fs_find_or_create_cached_xattr_item_slow(
+			struct dentry *dentry,
+			const char *xname);
+extern void v9fs_wakeup_xi_available(struct xattr_item *xi);
+extern void v9fs_move_xattr_item_to_fast(struct dentry *dentry,
+			struct xattr_item *xi);
+extern int v9fs_xattr_item_setval(struct xattr_item *xi, const char 
*buffer,
+			size_t buffer_size, ssize_t ret);
+extern void v9fs_xattr_item_dec_ref(struct dentry *dentry,
+			struct xattr_item *xi);
+extern void v9fs_free_inode_xattr_items(struct inode *inode);
+
+#endif
diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h
index 27dfe85..8346030 100644
--- a/include/net/9p/9p.h
+++ b/include/net/9p/9p.h
@@ -59,6 +59,7 @@  enum p9_debug_flags {
  	P9_DEBUG_PKT =		(1<<10),
  	P9_DEBUG_FSC =		(1<<11),
  	P9_DEBUG_VPKT =		(1<<12),
+	P9_DEBUG_XCACHE =	(1<<13),
  };

  #ifdef CONFIG_NET_9P_DEBUG