Inode cache
authorBarret Rhoden <brho@cs.berkeley.edu>
Sat, 11 Sep 2010 02:43:49 +0000 (19:43 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:54 +0000 (17:35 -0700)
Much easier than the dentry cache...  Which should tell you something
about the relative difficulty of different kref models.

Incidentally, these caches are necessary, at least for the in-use
objects, so that multiple lookups get the same object.  imagine having an
open file, while some other thread tries a lookup.  Without a dentry
cache, you'd get different in-memory objects for the same path.  Without
the inode cache, you'd get different objects for the same disk file if
you came in via different hard links.

kern/include/vfs.h
kern/src/vfs.c

index c64613a..2e0c204 100644 (file)
@@ -459,6 +459,9 @@ int create_symlink(struct inode *dir, struct dentry *dentry,
 int check_perms(struct inode *inode, int access_mode);
 void inode_release(struct kref *kref);
 void stat_inode(struct inode *inode, struct kstat *kstat);
+struct inode *icache_get(struct super_block *sb, unsigned int ino);
+void icache_put(struct super_block *sb, struct inode *inode);
+struct inode *icache_remove(struct super_block *sb, unsigned int ino);
 
 /* File-ish functions */
 ssize_t generic_file_read(struct file *file, char *buf, size_t count,
index 96f9417..51374cd 100644 (file)
@@ -580,6 +580,7 @@ void init_sb(struct super_block *sb, struct vfsmount *vmnt,
        /* TODO: do we need to read in the inode?  can we do this on demand? */
        /* if this FS is already mounted, we'll need to do something different. */
        sb->s_op->read_inode(inode);
+       icache_put(sb, inode);
        /* Link the dentry and SB to the VFS mount */
        vmnt->mnt_root = d_root;                                /* ref comes from get_dentry */
        vmnt->mnt_sb = sb;
@@ -877,9 +878,24 @@ struct inode *get_inode(struct dentry *dentry)
 /* Helper: loads/ reads in the inode numbered ino and attaches it to dentry */
 void load_inode(struct dentry *dentry, unsigned int ino)
 {
-       struct inode *inode = get_inode(dentry);
+       struct inode *inode;
+
+       /* look it up in the inode cache first */
+       inode = icache_get(dentry->d_sb, ino);
+       if (inode) {
+               /* connect the dentry to its inode */
+               TAILQ_INSERT_TAIL(&inode->i_dentry, dentry, d_alias);
+               dentry->d_inode = inode;        /* storing the ref we got from icache_get */
+               return;
+       }
+       /* otherwise, we need to do it manually */
+       inode = get_inode(dentry);
        inode->i_ino = ino;
        dentry->d_sb->s_op->read_inode(inode);
+       /* TODO: race here, two creators could miss in the cache, and then get here.
+        * need a way to sync across a blocking call.  needs to be either at this
+        * point in the code or per the ino (dentries could be different) */
+       icache_put(dentry->d_sb, inode);
        kref_put(&inode->i_kref);
 }
 
@@ -977,6 +993,7 @@ void inode_release(struct kref *kref)
 {
        struct inode *inode = container_of(kref, struct inode, i_kref);
        TAILQ_REMOVE(&inode->i_sb->s_inodes, inode, i_sb_list);
+       icache_remove(inode->i_sb, inode->i_ino);
        /* Might need to write back or delete the file/inode */
        if (inode->i_nlink) {
                if (inode->i_state & I_STATE_DIRTY)
@@ -1011,6 +1028,43 @@ void stat_inode(struct inode *inode, struct kstat *kstat)
        kstat->st_ctime = inode->i_ctime;
 }
 
+/* Inode Cache management.  In general, search on the ino, get a refcnt'd value
+ * back.  Remove does not give you a reference back - it should only be called
+ * in inode_release(). */
+struct inode *icache_get(struct super_block *sb, unsigned int ino)
+{
+       /* This is the same style as in pid2proc, it's the "safely create a strong
+        * reference from a weak one, so long as other strong ones exist" pattern */
+       spin_lock(&sb->s_icache_lock);
+       struct inode *inode = hashtable_search(sb->s_icache, (void*)ino);
+       if (inode)
+               if (!kref_get_not_zero(&inode->i_kref, 1))
+                       inode = 0;
+       spin_unlock(&sb->s_icache_lock);
+       return inode;
+}
+
+void icache_put(struct super_block *sb, struct inode *inode)
+{
+       spin_lock(&sb->s_icache_lock);
+       /* there's a race in load_ino() that could trigger this */
+       assert(!hashtable_search(sb->s_icache, (void*)inode->i_ino));
+       hashtable_insert(sb->s_icache, (void*)inode->i_ino, inode);
+       spin_unlock(&sb->s_icache_lock);
+}
+
+struct inode *icache_remove(struct super_block *sb, unsigned int ino)
+{
+       struct inode *inode;
+       /* Presumably these hashtable removals could be easier since callers
+        * actually know who they are (same with the pid2proc hash) */
+       spin_lock(&sb->s_icache_lock);
+       inode = hashtable_remove(sb->s_icache, (void*)ino);
+       spin_unlock(&sb->s_icache_lock);
+       assert(inode && !kref_refcnt(&inode->i_kref));
+       return inode;
+}
+
 /* File functions */
 
 /* Read count bytes from the file into buf, starting at *offset, which is increased