With the new approach to handling busy, unlinked dentries, the
DCACHE_HIDDEN flag is no longer needed, shrinking the demo patch by
more than 40%.  Only two core files are touched, dcache.c and dcache.h:

git diff 5023112d97ace1a7363ab4b0da2701a21f6e3ffd | diffstat
 include/linux/dcache.h     |   11 ++++++
 fs/dcache.c                |    6 ++-
 fs/ext2/dir.c              |   61 ++++++++++++++++++++++++++++++++-
 fs/ext2/inode.c            |    1
 fs/ext2/namei.c            |   82 ++++++++++++++++++++++++++++++---------------
 fs/ext2/super.c            |   36 +++++++++++++++++++
 include/linux/ext2_fs_sb.h |    1
 7 files changed, 169 insertions(+), 29 deletions(-)

Still need to add handling for deferred inode create and rename.  It's
getting there.

Regards,

Daniel

diff --git a/Makefile b/Makefile
index 56fb747..985a7ce 100644
--- a/Makefile
+++ b/Makefile
@@ -538,7 +538,7 @@ NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
 CHECKFLAGS     += $(NOSTDINC_FLAGS)
 
 # warn about C99 declaration after statement
-KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
+#KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
 
 # disable pointer signed / unsigned warnings in gcc 4.0
 KBUILD_CFLAGS += $(call cc-option,-Wno-pointer-sign,)
diff --git a/fs/dcache.c b/fs/dcache.c
index 6068c25..e00e86b 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1387,14 +1387,16 @@ out:
  
 void d_delete(struct dentry * dentry)
 {
-	int isdir = 0;
+	int isdir = 0, hidden;
 	/*
 	 * Are we the only user?
 	 */
 	spin_lock(&dcache_lock);
 	spin_lock(&dentry->d_lock);
 	isdir = S_ISDIR(dentry->d_inode->i_mode);
-	if (atomic_read(&dentry->d_count) == 1) {
+	hidden = dentry->d_op && dentry->d_op->d_hide && dentry->d_op->d_hide(dentry);
+
+	if (atomic_read(&dentry->d_count) == 1 + hidden) {
 		dentry_iput(dentry);
 		fsnotify_nameremove(dentry, isdir);
 		return;
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index a78c6b4..1263cc8 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -270,6 +270,59 @@ static inline void ext2_set_de_type(ext2_dirent *de, struct inode *inode)
 		de->file_type = 0;
 }
 
+int real_unlink(struct inode *dir, struct dentry *dentry);
+void dentry_iput(struct dentry *dentry);
+
+int ext2_flush_dir(struct dentry *dir)
+{
+	printk(">>> ext2_sync_dir %p \"%.*s\"\n", dir, dir->d_name.len, dir->d_name.name);
+	struct list_head *next;
+	spin_lock(&dcache_lock);
+	for (next = dir->d_subdirs.next; next != &dir->d_subdirs;) {
+		struct dentry *dentry = list_entry(next, struct dentry, d_u.d_child);
+		next = next->next;
+		show_dentry("dentry", dentry);
+		if (d_unhashed(dentry))
+			continue;
+		spin_lock(&dentry->d_lock);
+		if (d_negative(dentry)) {
+			if ((dentry->d_flags & DCACHE_STALE)) {
+				dentry->d_flags &= ~DCACHE_STALE;
+				show_dentry("deferred unlink", dentry);
+				spin_unlock(&dentry->d_lock);
+				spin_unlock(&dcache_lock);
+				real_unlink(dir->d_inode, dentry);
+				dput(dentry);
+				spin_lock(&dcache_lock);
+				continue;
+			}
+		} else {
+			if (!(dentry->d_flags & DCACHE_BACKED)) {
+				int stale = dentry->d_flags & DCACHE_STALE;
+				show_dentry("deferred link", dentry);
+				dentry->d_flags &= ~DCACHE_STALE;
+				dentry->d_flags |= DCACHE_BACKED;
+				spin_unlock(&dentry->d_lock);
+				spin_unlock(&dcache_lock);
+				if (stale)
+					real_unlink(dir->d_inode, dentry);
+				struct inode *inode = dentry->d_inode;
+				int err = ext2_add_link(dentry, inode);
+				if (err) {
+					inode_dec_link_count(inode);
+					iput(inode);
+				}
+				dput(dentry);
+				spin_lock(&dcache_lock);
+				continue;
+			}
+		}
+		spin_unlock(&dentry->d_lock);
+	}
+	spin_unlock(&dcache_lock);
+	return 0; // really??
+}
+
 static int
 ext2_readdir (struct file * filp, void * dirent, filldir_t filldir)
 {
@@ -283,6 +336,8 @@ ext2_readdir (struct file * filp, void * dirent, filldir_t filldir)
 	unsigned char *types = NULL;
 	int need_revalidate = filp->f_version != inode->i_version;
 
+	ext2_flush_dir(filp->f_path.dentry);
+
 	if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
 		return 0;
 
@@ -699,6 +754,12 @@ not_empty:
 	return 0;
 }
 
+int ext2_sync_dir(struct file *file, struct dentry *dir, int datasync)
+{
+	ext2_flush_dir(dir);
+	return ext2_sync_file(file, dir, datasync);
+}
+
 const struct file_operations ext2_dir_operations = {
 	.llseek		= generic_file_llseek,
 	.read		= generic_read_dir,
@@ -707,5 +768,5 @@ const struct file_operations ext2_dir_operations = {
 #ifdef CONFIG_COMPAT
 	.compat_ioctl	= ext2_compat_ioctl,
 #endif
-	.fsync		= ext2_sync_file,
+	.fsync		= ext2_sync_dir,
 };
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 384fc0d..a9ce89b 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -58,6 +58,7 @@ static inline int ext2_inode_is_fast_symlink(struct inode *inode)
  */
 void ext2_delete_inode (struct inode * inode)
 {
+	printk(">>> ext2_delete_inode\n");
 	truncate_inode_pages(&inode->i_data, 0);
 
 	if (is_bad_inode(inode))
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index 80c97fd..0ed08fc 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -36,16 +36,56 @@
 #include "acl.h"
 #include "xip.h"
 
-static inline int ext2_add_nondir(struct dentry *dentry, struct inode *inode)
+static int ext2_hide_dentry(struct dentry *dentry)
 {
-	int err = ext2_add_link(dentry, inode);
-	if (!err) {
-		d_instantiate(dentry, inode);
+	struct dentry *clone;
+	if (!(dentry->d_flags & DCACHE_BACKED)) {
+		/* converting unbacked to negative */
+		dput(dentry); /* Cancel dget from deferred create */
 		return 0;
 	}
-	inode_dec_link_count(inode);
-	iput(inode);
-	return err;
+	if (atomic_read(&dentry->d_count) == 1) {
+		BUG_ON(!dentry->d_inode);
+		show_dentry("hide dentry", dentry);
+		dentry->d_flags &= ~DCACHE_BACKED;
+		dentry->d_flags |= DCACHE_STALE;
+		dget(dentry);
+		return 1;
+	}
+	show_dentry("busy dentry", dentry);
+	spin_unlock(&dentry->d_lock);
+	spin_unlock(&dcache_lock);
+	clone = d_alloc(dentry->d_parent, &dentry->d_name);
+	show_dentry("clone dentry", clone);
+	clone->d_flags |= DCACHE_STALE;
+	d_instantiate(clone, NULL);
+	d_rehash(clone);
+	spin_lock(&dentry->d_lock);
+	spin_lock(&dcache_lock);
+	return atomic_read(&dentry->d_count) == 1;
+}
+
+static struct dentry_operations ext2_dentry_operations = {
+	.d_hide = ext2_hide_dentry,
+};
+
+static int ext2_unlink(struct inode *dir, struct dentry *dentry)
+{
+	show_dentry("defer unlink", dentry);
+	dentry->d_inode->i_ctime = dir->i_ctime;
+	inode_dec_link_count(dentry->d_inode);
+	return 0;
+}
+
+static int ext2_add_nondir(struct dentry *dentry, struct inode *inode)
+{
+	show_dentry("defer create", dentry);
+	if (!(dentry->d_flags & DCACHE_STALE)) /* already deferred? */
+		dget(dentry);
+	dentry->d_op = &ext2_dentry_operations;
+	d_instantiate(dentry, inode);
+	show_dentry("instantiated", dentry);
+	return 0;
 }
 
 /*
@@ -66,7 +106,10 @@ static struct dentry *ext2_lookup(struct inode * dir, struct dentry *dentry, str
 		inode = ext2_iget(dir->i_sb, ino);
 		if (IS_ERR(inode))
 			return ERR_CAST(inode);
+		dentry->d_flags |= DCACHE_BACKED;
+		show_dentry("found real dirent", dentry);
 	}
+	dentry->d_op = &ext2_dentry_operations;
 	return d_splice_alias(inode, dentry);
 }
 
@@ -237,6 +280,7 @@ static int ext2_mkdir(struct inode * dir, struct dentry * dentry, int mode)
 	if (err)
 		goto out_fail;
 
+	dentry->d_op = &ext2_dentry_operations;
 	d_instantiate(dentry, inode);
 out:
 	return err;
@@ -250,26 +294,12 @@ out_dir:
 	goto out;
 }
 
-static int ext2_unlink(struct inode * dir, struct dentry *dentry)
+int real_unlink(struct inode *dir, struct dentry *dentry)
 {
-	struct inode * inode = dentry->d_inode;
-	struct ext2_dir_entry_2 * de;
-	struct page * page;
-	int err = -ENOENT;
-
-	de = ext2_find_entry (dir, dentry, &page);
-	if (!de)
-		goto out;
-
-	err = ext2_delete_entry (de, page);
-	if (err)
-		goto out;
-
-	inode->i_ctime = dir->i_ctime;
-	inode_dec_link_count(inode);
-	err = 0;
-out:
-	return err;
+	struct page *page;
+	struct ext2_dir_entry_2 *de = ext2_find_entry(dir, dentry, &page);
+	show_dentry("ext2_unlink", dentry);
+	return de ? ext2_delete_entry(de, page) : -ENOENT;
 }
 
 static int ext2_rmdir (struct inode * dir, struct dentry *dentry)
diff --git a/fs/ext2/super.c b/fs/ext2/super.c
index ef50cbc..e8066b3 100644
--- a/fs/ext2/super.c
+++ b/fs/ext2/super.c
@@ -295,17 +295,52 @@ static ssize_t ext2_quota_read(struct super_block *sb, int type, char *data, siz
 static ssize_t ext2_quota_write(struct super_block *sb, int type, const char *data, size_t len, loff_t off);
 #endif
 
+extern spinlock_t inode_lock;
+
+static inline void show_inode(char *tag, struct inode *inode)
+{
+	printk(">>> %s: %p/%i %x\n", tag, inode,
+		atomic_read(&inode->i_count), inode->i_flags);
+}
+
+static void defer_drop_inode(struct inode *inode)
+{
+	if (inode->i_nlink) {
+		generic_drop_inode(inode);
+		return;
+	}
+	show_inode("defer inode delete", inode);
+	inode->i_state |= I_DIRTY;
+	list_move(&inode->i_list, &EXT2_SB(inode->i_sb)->delete);
+	spin_unlock(&inode_lock);
+}
+
+extern struct list_head inode_unused;
+
+static int ext2_sync_fs(struct super_block *sb, int wait)
+{
+	while (!list_empty(&EXT2_SB(sb)->delete)) {
+		struct inode *inode = list_entry(EXT2_SB(sb)->delete.next, struct inode, i_list);
+		show_inode("delete deferred inode", inode);
+		spin_lock(&inode_lock);
+		generic_delete_inode(inode); /* removes from list and drops lock */
+	}
+	return 0;
+}
+
 static const struct super_operations ext2_sops = {
 	.alloc_inode	= ext2_alloc_inode,
 	.destroy_inode	= ext2_destroy_inode,
 	.write_inode	= ext2_write_inode,
 	.delete_inode	= ext2_delete_inode,
+	.drop_inode	= defer_drop_inode,
 	.put_super	= ext2_put_super,
 	.write_super	= ext2_write_super,
 	.statfs		= ext2_statfs,
 	.remount_fs	= ext2_remount,
 	.clear_inode	= ext2_clear_inode,
 	.show_options	= ext2_show_options,
+	.sync_fs	= ext2_sync_fs,
 #ifdef CONFIG_QUOTA
 	.quota_read	= ext2_quota_read,
 	.quota_write	= ext2_quota_write,
@@ -614,6 +649,7 @@ static int ext2_setup_super (struct super_block * sb,
 			EXT2_BLOCKS_PER_GROUP(sb),
 			EXT2_INODES_PER_GROUP(sb),
 			sbi->s_mount_opt);
+	INIT_LIST_HEAD(&sbi->delete);
 	return res;
 }
 
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d982eb8..70dd03f 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -133,6 +133,7 @@ struct dentry_operations {
 	void (*d_release)(struct dentry *);
 	void (*d_iput)(struct dentry *, struct inode *);
 	char *(*d_dname)(struct dentry *, char *, int);
+	int (*d_hide)(struct dentry *);
 };
 
 /* the dentry parameter passed to d_hash and d_compare is the parent
@@ -176,6 +177,9 @@ d_iput:		no		no		no       yes
 
 #define DCACHE_INOTIFY_PARENT_WATCHED	0x0020 /* Parent inode is watched */
 
+#define DCACHE_STALE		0x0040 /* FS has wrong dirent */
+#define DCACHE_BACKED		0x0080 /* FS has dirent */
+
 extern spinlock_t dcache_lock;
 extern seqlock_t rename_lock;
 
@@ -341,6 +345,11 @@ static inline int d_unhashed(struct dentry *dentry)
 	return (dentry->d_flags & DCACHE_UNHASHED);
 }
 
+static inline int d_negative(struct dentry *dentry)
+{
+	return !dentry->d_inode;
+}
+
 static inline struct dentry *dget_parent(struct dentry *dentry)
 {
 	struct dentry *ret;
@@ -363,4 +372,11 @@ extern struct dentry *lookup_create(struct nameidata *nd, int is_dir);
 
 extern int sysctl_vfs_cache_pressure;
 
+static inline void show_dentry(char *tag, struct dentry *dentry)
+{
+	printk(">>> %s: %p/%i %x %p \"%.*s\"\n", tag, dentry,
+		atomic_read(&dentry->d_count), dentry->d_flags, dentry->d_inode,
+		dentry->d_name.len, dentry->d_name.name);
+}
+
 #endif	/* __LINUX_DCACHE_H */
diff --git a/include/linux/ext2_fs_sb.h b/include/linux/ext2_fs_sb.h
index f273415..54ef185 100644
--- a/include/linux/ext2_fs_sb.h
+++ b/include/linux/ext2_fs_sb.h
@@ -106,6 +106,7 @@ struct ext2_sb_info {
 	spinlock_t s_rsv_window_lock;
 	struct rb_root s_rsv_window_root;
 	struct ext2_reserve_window_node s_rsv_window_head;
+	struct list_head delete;
 };
 
 #endif	/* _LINUX_EXT2_FS_SB */
_______________________________________________
Tux3 mailing list
[email protected]
http://mailman.tux3.org/cgi-bin/mailman/listinfo/tux3

Reply via email to