]> git.proxmox.com Git - mirror_zfs.git/commitdiff
zvol: reduce linear list search
authorChunwei Chen <david.chen@osnexus.com>
Wed, 30 Nov 2016 21:56:50 +0000 (13:56 -0800)
committerChunwei Chen <david.chen@osnexus.com>
Thu, 1 Dec 2016 22:53:11 +0000 (14:53 -0800)
Use kernel ida to generate minor number, and use hash table to find zvol with
name.

Signed-off-by: Chunwei Chen <david.chen@osnexus.com>
module/zfs/zvol.c

index ea6997b5b9e979902a59bf1fba095e99b2e715d5..cddc43c7ce6eff45e1ada634688aa8fb80ff74d0 100644 (file)
@@ -63,6 +63,11 @@ static kmutex_t zvol_state_lock;
 static list_t zvol_state_list;
 void *zvol_tag = "zvol_tag";
 
+#define        ZVOL_HT_SIZE    1024
+static struct hlist_head *zvol_htable;
+#define        ZVOL_HT_HEAD(hash)      (&zvol_htable[(hash) & (ZVOL_HT_SIZE-1)])
+static DEFINE_IDA(zvol_ida);
+
 /*
  * The in-core state of each volume.
  */
@@ -81,6 +86,8 @@ typedef struct zvol_state {
        struct gendisk          *zv_disk;       /* generic disk */
        struct request_queue    *zv_queue;      /* request queue */
        list_node_t             zv_next;        /* next zvol_state_t linkage */
+       uint64_t                zv_hash;        /* name hash */
+       struct hlist_node       zv_hlink;       /* hash link */
 } zvol_state_t;
 
 typedef enum {
@@ -102,30 +109,17 @@ typedef struct {
 
 #define        ZVOL_RDONLY     0x1
 
-/*
- * Find the next available range of ZVOL_MINORS minor numbers.  The
- * zvol_state_list is kept in ascending minor order so we simply need
- * to scan the list for the first gap in the sequence.  This allows us
- * to recycle minor number as devices are created and removed.
- */
-static int
-zvol_find_minor(unsigned *minor)
+static uint64_t
+zvol_name_hash(const char *name)
 {
-       zvol_state_t *zv;
-
-       *minor = 0;
-       ASSERT(MUTEX_HELD(&zvol_state_lock));
-       for (zv = list_head(&zvol_state_list); zv != NULL;
-           zv = list_next(&zvol_state_list, zv), *minor += ZVOL_MINORS) {
-               if (MINOR(zv->zv_dev) != MINOR(*minor))
-                       break;
+       int i;
+       uint64_t crc = -1ULL;
+       uint8_t *p = (uint8_t *)name;
+       ASSERT(zfs_crc64_table[128] == ZFS_CRC64_POLY);
+       for (i = 0; i < MAXNAMELEN - 1 && *p; i++, p++) {
+               crc = (crc >> 8) ^ zfs_crc64_table[(crc ^ (*p)) & 0xFF];
        }
-
-       /* All minors are in use */
-       if (*minor >= (1 << MINORBITS))
-               return (SET_ERROR(ENXIO));
-
-       return (0);
+       return (crc);
 }
 
 /*
@@ -147,23 +141,33 @@ zvol_find_by_dev(dev_t dev)
 }
 
 /*
- * Find a zvol_state_t given the name provided at zvol_alloc() time.
+ * Find a zvol_state_t given the name and hash generated by zvol_name_hash.
  */
 static zvol_state_t *
-zvol_find_by_name(const char *name)
+zvol_find_by_name_hash(const char *name, uint64_t hash)
 {
        zvol_state_t *zv;
+       struct hlist_node *p;
 
        ASSERT(MUTEX_HELD(&zvol_state_lock));
-       for (zv = list_head(&zvol_state_list); zv != NULL;
-           zv = list_next(&zvol_state_list, zv)) {
-               if (strncmp(zv->zv_name, name, MAXNAMELEN) == 0)
+       hlist_for_each(p, ZVOL_HT_HEAD(hash)) {
+               zv = hlist_entry(p, zvol_state_t, zv_hlink);
+               if (zv->zv_hash == hash &&
+                   strncmp(zv->zv_name, name, MAXNAMELEN) == 0)
                        return (zv);
        }
-
        return (NULL);
 }
 
+/*
+ * Find a zvol_state_t given the name provided at zvol_alloc() time.
+ */
+static zvol_state_t *
+zvol_find_by_name(const char *name)
+{
+       return (zvol_find_by_name_hash(name, zvol_name_hash(name)));
+}
+
 
 /*
  * Given a path, return TRUE if path is a ZVOL.
@@ -921,32 +925,26 @@ zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
 }
 
 /*
- * The zvol_state_t's are inserted in increasing MINOR(dev_t) order.
+ * The zvol_state_t's are inserted into zvol_state_list and zvol_htable.
  */
 static void
-zvol_insert(zvol_state_t *zv_insert)
+zvol_insert(zvol_state_t *zv)
 {
-       zvol_state_t *zv = NULL;
-
        ASSERT(MUTEX_HELD(&zvol_state_lock));
-       ASSERT3U(MINOR(zv_insert->zv_dev) & ZVOL_MINOR_MASK, ==, 0);
-       for (zv = list_head(&zvol_state_list); zv != NULL;
-           zv = list_next(&zvol_state_list, zv)) {
-               if (MINOR(zv->zv_dev) > MINOR(zv_insert->zv_dev))
-                       break;
-       }
-
-       list_insert_before(&zvol_state_list, zv, zv_insert);
+       ASSERT3U(MINOR(zv->zv_dev) & ZVOL_MINOR_MASK, ==, 0);
+       list_insert_head(&zvol_state_list, zv);
+       hlist_add_head(&zv->zv_hlink, ZVOL_HT_HEAD(zv->zv_hash));
 }
 
 /*
  * Simply remove the zvol from to list of zvols.
  */
 static void
-zvol_remove(zvol_state_t *zv_remove)
+zvol_remove(zvol_state_t *zv)
 {
        ASSERT(MUTEX_HELD(&zvol_state_lock));
-       list_remove(&zvol_state_list, zv_remove);
+       list_remove(&zvol_state_list, zv);
+       hlist_del(&zv->zv_hlink);
 }
 
 static int
@@ -1334,6 +1332,7 @@ zvol_free(zvol_state_t *zv)
        blk_cleanup_queue(zv->zv_queue);
        put_disk(zv->zv_disk);
 
+       ida_simple_remove(&zvol_ida, MINOR(zv->zv_dev) >> ZVOL_MINOR_BITS);
        kmem_free(zv, sizeof (zvol_state_t));
 }
 
@@ -1352,10 +1351,17 @@ zvol_create_minor_impl(const char *name)
        uint64_t len;
        unsigned minor = 0;
        int error = 0;
+       int idx;
+       uint64_t hash = zvol_name_hash(name);
+
+       idx = ida_simple_get(&zvol_ida, 0, 0, kmem_flags_convert(KM_SLEEP));
+       if (idx < 0)
+               return (SET_ERROR(-idx));
+       minor = idx << ZVOL_MINOR_BITS;
 
        mutex_enter(&zvol_state_lock);
 
-       zv = zvol_find_by_name(name);
+       zv = zvol_find_by_name_hash(name, hash);
        if (zv) {
                error = SET_ERROR(EEXIST);
                goto out;
@@ -1375,15 +1381,12 @@ zvol_create_minor_impl(const char *name)
        if (error)
                goto out_dmu_objset_disown;
 
-       error = zvol_find_minor(&minor);
-       if (error)
-               goto out_dmu_objset_disown;
-
        zv = zvol_alloc(MKDEV(zvol_major, minor), name);
        if (zv == NULL) {
                error = SET_ERROR(EAGAIN);
                goto out_dmu_objset_disown;
        }
+       zv->zv_hash = hash;
 
        if (dmu_objset_is_snapshot(os))
                zv->zv_flags |= ZVOL_RDONLY;
@@ -1449,6 +1452,7 @@ out:
                add_disk(zv->zv_disk);
        } else {
                mutex_exit(&zvol_state_lock);
+               ida_simple_remove(&zvol_ida, idx);
        }
 
        return (SET_ERROR(error));
@@ -1933,16 +1937,25 @@ zvol_rename_minors(spa_t *spa, const char *name1, const char *name2,
 int
 zvol_init(void)
 {
-       int error;
+       int i, error;
 
        list_create(&zvol_state_list, sizeof (zvol_state_t),
            offsetof(zvol_state_t, zv_next));
        mutex_init(&zvol_state_lock, NULL, MUTEX_DEFAULT, NULL);
 
+       zvol_htable = kmem_alloc(ZVOL_HT_SIZE * sizeof (struct hlist_head),
+           KM_SLEEP);
+       if (!zvol_htable) {
+               error = ENOMEM;
+               goto out;
+       }
+       for (i = 0; i < ZVOL_HT_SIZE; i++)
+               INIT_HLIST_HEAD(&zvol_htable[i]);
+
        error = register_blkdev(zvol_major, ZVOL_DRIVER);
        if (error) {
                printk(KERN_INFO "ZFS: register_blkdev() failed %d\n", error);
-               goto out;
+               goto out_free;
        }
 
        blk_register_region(MKDEV(zvol_major, 0), 1UL << MINORBITS,
@@ -1950,6 +1963,8 @@ zvol_init(void)
 
        return (0);
 
+out_free:
+       kmem_free(zvol_htable, ZVOL_HT_SIZE * sizeof (struct hlist_head));
 out:
        mutex_destroy(&zvol_state_lock);
        list_destroy(&zvol_state_list);
@@ -1964,6 +1979,7 @@ zvol_fini(void)
 
        blk_unregister_region(MKDEV(zvol_major, 0), 1UL << MINORBITS);
        unregister_blkdev(zvol_major, ZVOL_DRIVER);
+       kmem_free(zvol_htable, ZVOL_HT_SIZE * sizeof (struct hlist_head));
 
        list_destroy(&zvol_state_list);
        mutex_destroy(&zvol_state_lock);