Fix zvol_init() lock inversion

During module load we could deadlock because the zvol_init()
callpath took the spa_namespace_lock before the zvol_state_lock.
The rest of the zvol code takes the locks in the opposite order.
In particular, I observed the following deadlock cause by the
lock inversion.

I've fixed the ording by creating an unlocked version of
zvol_create_minor and zvol_remove_minor.  This allows me to
take the zvol_state_lock before the spa_namespace_lock in
zvol_cr_minors_common and simply call the unlocked version.
This commit is contained in:
Brian Behlendorf 2010-07-30 09:27:40 -07:00
parent f162433deb
commit 88b37fbe57
1 changed files with 44 additions and 27 deletions

View File

@ -1071,13 +1071,8 @@ zvol_free(zvol_state_t *zv)
kmem_free(zv, sizeof (zvol_state_t)); kmem_free(zv, sizeof (zvol_state_t));
} }
/* static int
* Create a block device minor node and setup the linkage between it __zvol_create_minor(const char *name)
* and the specified volume. Once this function returns the block
* device is live and ready for use.
*/
int
zvol_create_minor(const char *name)
{ {
zvol_state_t *zv; zvol_state_t *zv;
objset_t *os; objset_t *os;
@ -1085,7 +1080,7 @@ zvol_create_minor(const char *name)
unsigned minor = 0; unsigned minor = 0;
int error = 0; int error = 0;
mutex_enter(&zvol_state_lock); ASSERT(MUTEX_HELD(&zvol_state_lock));
zv = zvol_find_by_name(name); zv = zvol_find_by_name(name);
if (zv) { if (zv) {
@ -1128,9 +1123,44 @@ zvol_create_minor(const char *name)
out_dmu_objset_disown: out_dmu_objset_disown:
dmu_objset_disown(os, zvol_tag); dmu_objset_disown(os, zvol_tag);
out: out:
return (error);
}
/*
* Create a block device minor node and setup the linkage between it
* and the specified volume. Once this function returns the block
* device is live and ready for use.
*/
int
zvol_create_minor(const char *name)
{
int error;
mutex_enter(&zvol_state_lock);
error = __zvol_create_minor(name);
mutex_exit(&zvol_state_lock); mutex_exit(&zvol_state_lock);
return (-error); return (error);
}
static int
__zvol_remove_minor(const char *name)
{
zvol_state_t *zv;
ASSERT(MUTEX_HELD(&zvol_state_lock));
zv = zvol_find_by_name(name);
if (zv == NULL)
return (ENXIO);
if (zv->zv_open_count > 0)
return (EBUSY);
zvol_remove(zv);
zvol_free(zv);
return (0);
} }
/* /*
@ -1139,25 +1169,10 @@ out:
int int
zvol_remove_minor(const char *name) zvol_remove_minor(const char *name)
{ {
zvol_state_t *zv; int error;
int error = 0;
mutex_enter(&zvol_state_lock); mutex_enter(&zvol_state_lock);
error = __zvol_remove_minor(name);
zv = zvol_find_by_name(name);
if (zv == NULL) {
error = ENXIO;
goto out;
}
if (zv->zv_open_count > 0) {
error = EBUSY;
goto out;
}
zvol_remove(zv);
zvol_free(zv);
out:
mutex_exit(&zvol_state_lock); mutex_exit(&zvol_state_lock);
return (error); return (error);
@ -1170,7 +1185,7 @@ zvol_create_minors_cb(spa_t *spa, uint64_t dsobj,
if (strchr(dsname, '/') == NULL) if (strchr(dsname, '/') == NULL)
return 0; return 0;
return zvol_create_minor(dsname); return __zvol_create_minor(dsname);
} }
/* /*
@ -1183,6 +1198,7 @@ zvol_create_minors(const char *pool)
spa_t *spa = NULL; spa_t *spa = NULL;
int error = 0; int error = 0;
mutex_enter(&zvol_state_lock);
if (pool) { if (pool) {
error = dmu_objset_find_spa(NULL, pool, zvol_create_minors_cb, error = dmu_objset_find_spa(NULL, pool, zvol_create_minors_cb,
NULL, DS_FIND_CHILDREN | DS_FIND_SNAPSHOTS); NULL, DS_FIND_CHILDREN | DS_FIND_SNAPSHOTS);
@ -1197,6 +1213,7 @@ zvol_create_minors(const char *pool)
} }
mutex_exit(&spa_namespace_lock); mutex_exit(&spa_namespace_lock);
} }
mutex_exit(&zvol_state_lock);
return error; return error;
} }