diff options
author | Tejun Heo <tj@kernel.org> | 2014-01-10 08:57:26 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-01-10 13:51:21 -0800 |
commit | 9f010c2ad5194a4b682e747984477850fabd03be (patch) | |
tree | e989b667775b3e1bf9b6da90bbaf2815eb103502 | |
parent | 895a068a524e134900b9d98b519309b7aae7bbb1 (diff) |
kernfs: implement kernfs_{de|re}activate[_self]()
This patch implements four functions to manipulate deactivation state
- deactivate, reactivate and the _self suffixed pair. A new fields
kernfs_node->deact_depth is added so that concurrent and nested
deactivations are handled properly. kernfs_node->hash is moved so
that it's paired with the new field so that it doesn't increase the
size of kernfs_node.
A kernfs user's lock would normally nest inside active ref but during
removal the user may want to perform kernfs_remove() while holding the
said lock, which would introduce a reverse locking dependency. This
function can be used to break such reverse dependency by allowing
deactivation step to performed separately outside user's critical
section.
This will also be used implement kernfs_remove_self().
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | fs/kernfs/dir.c | 118 | ||||
-rw-r--r-- | include/linux/kernfs.h | 7 |
2 files changed, 123 insertions, 2 deletions
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 37dd6408f5f..1aeb57969bf 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -396,6 +396,7 @@ struct kernfs_node *kernfs_new_node(struct kernfs_root *root, const char *name, atomic_set(&kn->count, 1); atomic_set(&kn->active, KN_DEACTIVATED_BIAS); + kn->deact_depth = 1; RB_CLEAR_NODE(&kn->rb); kn->name = name; @@ -461,6 +462,7 @@ int kernfs_add_one(struct kernfs_node *kn, struct kernfs_node *parent) /* Mark the entry added into directory tree */ atomic_sub(KN_DEACTIVATED_BIAS, &kn->active); + kn->deact_depth--; ret = 0; out_unlock: mutex_unlock(&kernfs_mutex); @@ -561,6 +563,7 @@ struct kernfs_root *kernfs_create_root(struct kernfs_dir_ops *kdops, void *priv) } atomic_sub(KN_DEACTIVATED_BIAS, &kn->active); + kn->deact_depth--; kn->priv = priv; kn->dir.root = root; @@ -773,7 +776,8 @@ static void __kernfs_deactivate(struct kernfs_node *kn) /* prevent any new usage under @kn by deactivating all nodes */ pos = NULL; while ((pos = kernfs_next_descendant_post(pos, kn))) { - if (atomic_read(&pos->active) >= 0) { + if (!pos->deact_depth++) { + WARN_ON_ONCE(atomic_read(&pos->active) < 0); atomic_add(KN_DEACTIVATED_BIAS, &pos->active); pos->flags |= KERNFS_JUST_DEACTIVATED; } @@ -797,6 +801,118 @@ static void __kernfs_deactivate(struct kernfs_node *kn) } } +static void __kernfs_reactivate(struct kernfs_node *kn) +{ + struct kernfs_node *pos; + + lockdep_assert_held(&kernfs_mutex); + + pos = NULL; + while ((pos = kernfs_next_descendant_post(pos, kn))) { + if (!--pos->deact_depth) { + WARN_ON_ONCE(atomic_read(&pos->active) >= 0); + atomic_sub(KN_DEACTIVATED_BIAS, &pos->active); + } + WARN_ON_ONCE(pos->deact_depth < 0); + } + + /* some nodes reactivated, kick get_active waiters */ + wake_up_all(&kernfs_root(kn)->deactivate_waitq); +} + +static void __kernfs_deactivate_self(struct kernfs_node *kn) +{ + /* + * Take out ourself out of the active ref dependency chain and + * deactivate. If we're called without an active ref, lockdep will + * complain. + */ + kernfs_put_active(kn); + __kernfs_deactivate(kn); +} + +static void __kernfs_reactivate_self(struct kernfs_node *kn) +{ + __kernfs_reactivate(kn); + /* + * Restore active ref dropped by deactivate_self() so that it's + * balanced on return. put_active() will soon be called on @kn, so + * this can't break anything regardless of @kn's state. + */ + atomic_inc(&kn->active); + if (kernfs_lockdep(kn)) + rwsem_acquire(&kn->dep_map, 0, 1, _RET_IP_); +} + +/** + * kernfs_deactivate - deactivate subtree of a node + * @kn: kernfs_node to deactivate subtree of + * + * Deactivate the subtree of @kn. On return, there's no active operation + * going on under @kn and creation or renaming of a node under @kn is + * blocked until @kn is reactivated or removed. This function can be + * called multiple times and nests properly. Each invocation should be + * paired with kernfs_reactivate(). + * + * For a kernfs user which uses simple locking, the subsystem lock would + * nest inside active reference. This becomes problematic if the user + * tries to remove nodes while holding the subystem lock as it would create + * a reverse locking dependency from the subsystem lock to active ref. + * This function can be used to break such reverse dependency. The user + * can call this function outside the subsystem lock and then proceed to + * invoke kernfs_remove() while holding the subsystem lock without + * introducing such reverse dependency. + */ +void kernfs_deactivate(struct kernfs_node *kn) +{ + mutex_lock(&kernfs_mutex); + __kernfs_deactivate(kn); + mutex_unlock(&kernfs_mutex); +} + +/** + * kernfs_reactivate - reactivate subtree of a node + * @kn: kernfs_node to reactivate subtree of + * + * Undo kernfs_deactivate(). + */ +void kernfs_reactivate(struct kernfs_node *kn) +{ + mutex_lock(&kernfs_mutex); + __kernfs_reactivate(kn); + mutex_unlock(&kernfs_mutex); +} + +/** + * kernfs_deactivate_self - deactivate subtree of a node from its own method + * @kn: the self kernfs_node to deactivate subtree of + * + * The caller must be running off of a kernfs operation which is invoked + * with an active reference - e.g. one of kernfs_ops. Once this function + * is called, @kn may be removed by someone else while the enclosing method + * is in progress. Other than that, this function is equivalent to + * kernfs_deactivate() and should be paired with kernfs_reactivate_self(). + */ +void kernfs_deactivate_self(struct kernfs_node *kn) +{ + mutex_lock(&kernfs_mutex); + __kernfs_deactivate_self(kn); + mutex_unlock(&kernfs_mutex); +} + +/** + * kernfs_reactivate_self - reactivate subtree of a node from its own method + * @kn: the self kernfs_node to reactivate subtree of + * + * Undo kernfs_deactivate_self(). + */ +void kernfs_reactivate_self(struct kernfs_node *kn) +{ + mutex_lock(&kernfs_mutex); + __kernfs_reactivate_self(kn); + mutex_unlock(&kernfs_mutex); +} + static void __kernfs_remove(struct kernfs_node *kn) { struct kernfs_root *root = kernfs_root(kn); diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h index 9b5a4bb88c6..ac869302705 100644 --- a/include/linux/kernfs.h +++ b/include/linux/kernfs.h @@ -80,6 +80,8 @@ struct kernfs_elem_attr { struct kernfs_node { atomic_t count; atomic_t active; + int deact_depth; + unsigned int hash; /* ns + name hash */ #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif @@ -90,7 +92,6 @@ struct kernfs_node { struct rb_node rb; const void *ns; /* namespace tag */ - unsigned int hash; /* ns + name hash */ union { struct kernfs_elem_dir dir; struct kernfs_elem_symlink symlink; @@ -233,6 +234,10 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent, struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, const char *name, struct kernfs_node *target); +void kernfs_deactivate(struct kernfs_node *kn); +void kernfs_reactivate(struct kernfs_node *kn); +void kernfs_deactivate_self(struct kernfs_node *kn); +void kernfs_reactivate_self(struct kernfs_node *kn); void kernfs_remove(struct kernfs_node *kn); int kernfs_remove_by_name_ns(struct kernfs_node *parent, const char *name, const void *ns); |