X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=fs%2Fdcache.c;h=41000305d716ea51c47ed52ddb5abe024045e958;hb=f307cd1a32fab53012b01749a1f5ba10b0a7243f;hp=c932ed32c77b1b4cb80c5456b427c78d13d82e84;hpb=bf2ba3bc185269eca274b458aac46ba1ad7c1121;p=platform%2Fadaptation%2Frenesas_rcar%2Frenesas_kernel.git diff --git a/fs/dcache.c b/fs/dcache.c index c932ed3..4100030 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -90,8 +90,8 @@ static struct kmem_cache *dentry_cache __read_mostly; /** * read_seqbegin_or_lock - begin a sequence number check or locking block - * lock: sequence lock - * seq : sequence number to be checked + * @lock: sequence lock + * @seq : sequence number to be checked * * First try it once optimistically without taking the lock. If that fails, * take the lock. The sequence number is also used as a marker for deciding @@ -103,7 +103,7 @@ static inline void read_seqbegin_or_lock(seqlock_t *lock, int *seq) if (!(*seq & 1)) /* Even */ *seq = read_seqbegin(lock); else /* Odd */ - write_seqlock(lock); + read_seqlock_excl(lock); } static inline int need_seqretry(seqlock_t *lock, int seq) @@ -114,7 +114,7 @@ static inline int need_seqretry(seqlock_t *lock, int seq) static inline void done_seqretry(seqlock_t *lock, int seq) { if (seq & 1) - write_sequnlock(lock); + read_sequnlock_excl(lock); } /* @@ -357,15 +357,80 @@ static void dentry_unlink_inode(struct dentry * dentry) } /* + * The DCACHE_LRU_LIST bit is set whenever the 'd_lru' entry + * is in use - which includes both the "real" per-superblock + * LRU list _and_ the DCACHE_SHRINK_LIST use. + * + * The DCACHE_SHRINK_LIST bit is set whenever the dentry is + * on the shrink list (ie not on the superblock LRU list). + * + * The per-cpu "nr_dentry_unused" counters are updated with + * the DCACHE_LRU_LIST bit. + * + * These helper functions make sure we always follow the + * rules. d_lock must be held by the caller. + */ +#define D_FLAG_VERIFY(dentry,x) WARN_ON_ONCE(((dentry)->d_flags & (DCACHE_LRU_LIST | DCACHE_SHRINK_LIST)) != (x)) +static void d_lru_add(struct dentry *dentry) +{ + D_FLAG_VERIFY(dentry, 0); + dentry->d_flags |= DCACHE_LRU_LIST; + this_cpu_inc(nr_dentry_unused); + WARN_ON_ONCE(!list_lru_add(&dentry->d_sb->s_dentry_lru, &dentry->d_lru)); +} + +static void d_lru_del(struct dentry *dentry) +{ + D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); + dentry->d_flags &= ~DCACHE_LRU_LIST; + this_cpu_dec(nr_dentry_unused); + WARN_ON_ONCE(!list_lru_del(&dentry->d_sb->s_dentry_lru, &dentry->d_lru)); +} + +static void d_shrink_del(struct dentry *dentry) +{ + D_FLAG_VERIFY(dentry, DCACHE_SHRINK_LIST | DCACHE_LRU_LIST); + list_del_init(&dentry->d_lru); + dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST); + this_cpu_dec(nr_dentry_unused); +} + +static void d_shrink_add(struct dentry *dentry, struct list_head *list) +{ + D_FLAG_VERIFY(dentry, 0); + list_add(&dentry->d_lru, list); + dentry->d_flags |= DCACHE_SHRINK_LIST | DCACHE_LRU_LIST; + this_cpu_inc(nr_dentry_unused); +} + +/* + * These can only be called under the global LRU lock, ie during the + * callback for freeing the LRU list. "isolate" removes it from the + * LRU lists entirely, while shrink_move moves it to the indicated + * private list. + */ +static void d_lru_isolate(struct dentry *dentry) +{ + D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); + dentry->d_flags &= ~DCACHE_LRU_LIST; + this_cpu_dec(nr_dentry_unused); + list_del_init(&dentry->d_lru); +} + +static void d_lru_shrink_move(struct dentry *dentry, struct list_head *list) +{ + D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST); + dentry->d_flags |= DCACHE_SHRINK_LIST; + list_move_tail(&dentry->d_lru, list); +} + +/* * dentry_lru_(add|del)_list) must be called with d_lock held. */ static void dentry_lru_add(struct dentry *dentry) { - if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST))) { - if (list_lru_add(&dentry->d_sb->s_dentry_lru, &dentry->d_lru)) - this_cpu_inc(nr_dentry_unused); - dentry->d_flags |= DCACHE_LRU_LIST; - } + if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST))) + d_lru_add(dentry); } /* @@ -377,15 +442,11 @@ static void dentry_lru_add(struct dentry *dentry) */ static void dentry_lru_del(struct dentry *dentry) { - if (dentry->d_flags & DCACHE_SHRINK_LIST) { - list_del_init(&dentry->d_lru); - dentry->d_flags &= ~DCACHE_SHRINK_LIST; - return; + if (dentry->d_flags & DCACHE_LRU_LIST) { + if (dentry->d_flags & DCACHE_SHRINK_LIST) + return d_shrink_del(dentry); + d_lru_del(dentry); } - - if (list_lru_del(&dentry->d_sb->s_dentry_lru, &dentry->d_lru)) - this_cpu_dec(nr_dentry_unused); - dentry->d_flags &= ~DCACHE_LRU_LIST; } /** @@ -837,6 +898,12 @@ static void shrink_dentry_list(struct list_head *list) dentry = list_entry_rcu(list->prev, struct dentry, d_lru); if (&dentry->d_lru == list) break; /* empty */ + + /* + * Get the dentry lock, and re-verify that the dentry is + * this on the shrinking list. If it is, we know that + * DCACHE_SHRINK_LIST and DCACHE_LRU_LIST are set. + */ spin_lock(&dentry->d_lock); if (dentry != list_entry(list->prev, struct dentry, d_lru)) { spin_unlock(&dentry->d_lock); @@ -848,8 +915,7 @@ static void shrink_dentry_list(struct list_head *list) * to the LRU here, so we can simply remove it from the list * here regardless of whether it is referenced or not. */ - list_del_init(&dentry->d_lru); - dentry->d_flags &= ~DCACHE_SHRINK_LIST; + d_shrink_del(dentry); /* * We found an inuse dentry which was not removed from @@ -861,12 +927,20 @@ static void shrink_dentry_list(struct list_head *list) } rcu_read_unlock(); + /* + * If 'try_to_prune()' returns a dentry, it will + * be the same one we passed in, and d_lock will + * have been held the whole time, so it will not + * have been added to any other lists. We failed + * to get the inode lock. + * + * We just add it back to the shrink list. + */ dentry = try_prune_one_dentry(dentry); rcu_read_lock(); if (dentry) { - dentry->d_flags |= DCACHE_SHRINK_LIST; - list_add(&dentry->d_lru, list); + d_shrink_add(dentry, list); spin_unlock(&dentry->d_lock); } } @@ -894,7 +968,7 @@ dentry_lru_isolate(struct list_head *item, spinlock_t *lru_lock, void *arg) * another pass through the LRU. */ if (dentry->d_lockref.count) { - list_del_init(&dentry->d_lru); + d_lru_isolate(dentry); spin_unlock(&dentry->d_lock); return LRU_REMOVED; } @@ -925,9 +999,7 @@ dentry_lru_isolate(struct list_head *item, spinlock_t *lru_lock, void *arg) return LRU_ROTATE; } - dentry->d_flags |= DCACHE_SHRINK_LIST; - list_move_tail(&dentry->d_lru, freeable); - this_cpu_dec(nr_dentry_unused); + d_lru_shrink_move(dentry, freeable); spin_unlock(&dentry->d_lock); return LRU_REMOVED; @@ -972,9 +1044,7 @@ static enum lru_status dentry_lru_isolate_shrink(struct list_head *item, if (!spin_trylock(&dentry->d_lock)) return LRU_SKIP; - dentry->d_flags |= DCACHE_SHRINK_LIST; - list_move_tail(&dentry->d_lru, freeable); - this_cpu_dec(nr_dentry_unused); + d_lru_shrink_move(dentry, freeable); spin_unlock(&dentry->d_lock); return LRU_REMOVED; @@ -1362,9 +1432,13 @@ static enum d_walk_ret select_collect(void *_data, struct dentry *dentry) if (dentry->d_lockref.count) { dentry_lru_del(dentry); } else if (!(dentry->d_flags & DCACHE_SHRINK_LIST)) { - dentry_lru_del(dentry); - list_add_tail(&dentry->d_lru, &data->dispose); - dentry->d_flags |= DCACHE_SHRINK_LIST; + /* + * We can't use d_lru_shrink_move() because we + * need to get the global LRU lock and do the + * LRU accounting. + */ + d_lru_del(dentry); + d_shrink_add(dentry, &data->dispose); data->found++; ret = D_WALK_NORETRY; } @@ -2753,9 +2827,9 @@ static int prepend(char **buffer, int *buflen, const char *str, int namelen) /** * prepend_name - prepend a pathname in front of current buffer pointer - * buffer: buffer pointer - * buflen: allocated length of the buffer - * name: name string and length qstr structure + * @buffer: buffer pointer + * @buflen: allocated length of the buffer + * @name: name string and length qstr structure * * With RCU path tracing, it may race with d_move(). Use ACCESS_ONCE() to * make sure that either the old or the new name pointer and length are @@ -2793,14 +2867,15 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name) * @buffer: pointer to the end of the buffer * @buflen: pointer to buffer length * - * The function tries to write out the pathname without taking any lock other - * than the RCU read lock to make sure that dentries won't go away. It only - * checks the sequence number of the global rename_lock as any change in the - * dentry's d_seq will be preceded by changes in the rename_lock sequence - * number. If the sequence number had been change, it will restart the whole - * pathname back-tracing sequence again. It performs a total of 3 trials of - * lockless back-tracing sequences before falling back to take the - * rename_lock. + * The function will first try to write out the pathname without taking any + * lock other than the RCU read lock to make sure that dentries won't go away. + * It only checks the sequence number of the global rename_lock as any change + * in the dentry's d_seq will be preceded by changes in the rename_lock + * sequence number. If the sequence number had been changed, it will restart + * the whole pathname back-tracing sequence again by taking the rename_lock. + * In this case, there is no need to take the RCU read lock as the recursive + * parent pointer references will keep the dentry chain alive as long as no + * rename operation is performed. */ static int prepend_path(const struct path *path, const struct path *root, @@ -2948,6 +3023,16 @@ static int prepend_unreachable(char **buffer, int *buflen) return prepend(buffer, buflen, "(unreachable)", 13); } +static void get_fs_root_rcu(struct fs_struct *fs, struct path *root) +{ + unsigned seq; + + do { + seq = read_seqcount_begin(&fs->seq); + *root = fs->root; + } while (read_seqcount_retry(&fs->seq, seq)); +} + /** * d_path - return the path of a dentry * @path: path to report @@ -2980,13 +3065,15 @@ char *d_path(const struct path *path, char *buf, int buflen) if (path->dentry->d_op && path->dentry->d_op->d_dname) return path->dentry->d_op->d_dname(path->dentry, buf, buflen); - get_fs_root(current->fs, &root); + rcu_read_lock(); + get_fs_root_rcu(current->fs, &root); br_read_lock(&vfsmount_lock); error = path_with_deleted(path, &root, &res, &buflen); br_read_unlock(&vfsmount_lock); + rcu_read_unlock(); + if (error < 0) res = ERR_PTR(error); - path_put(&root); return res; } EXPORT_SYMBOL(d_path); @@ -3094,6 +3181,18 @@ Elong: return ERR_PTR(-ENAMETOOLONG); } +static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root, + struct path *pwd) +{ + unsigned seq; + + do { + seq = read_seqcount_begin(&fs->seq); + *root = fs->root; + *pwd = fs->pwd; + } while (read_seqcount_retry(&fs->seq, seq)); +} + /* * NOTE! The user-level library version returns a * character pointer. The kernel system call just @@ -3116,23 +3215,25 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) { int error; struct path pwd, root; - char *page = (char *) __get_free_page(GFP_USER); + char *page = __getname(); if (!page) return -ENOMEM; - get_fs_root_and_pwd(current->fs, &root, &pwd); + rcu_read_lock(); + get_fs_root_and_pwd_rcu(current->fs, &root, &pwd); error = -ENOENT; br_read_lock(&vfsmount_lock); if (!d_unlinked(pwd.dentry)) { unsigned long len; - char *cwd = page + PAGE_SIZE; - int buflen = PAGE_SIZE; + char *cwd = page + PATH_MAX; + int buflen = PATH_MAX; prepend(&cwd, &buflen, "\0", 1); error = prepend_path(&pwd, &root, &cwd, &buflen); br_read_unlock(&vfsmount_lock); + rcu_read_unlock(); if (error < 0) goto out; @@ -3145,7 +3246,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) } error = -ERANGE; - len = PAGE_SIZE + page - cwd; + len = PATH_MAX + page - cwd; if (len <= size) { error = len; if (copy_to_user(buf, cwd, len)) @@ -3153,12 +3254,11 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) } } else { br_read_unlock(&vfsmount_lock); + rcu_read_unlock(); } out: - path_put(&pwd); - path_put(&root); - free_page((unsigned long) page); + __putname(page); return error; }