• 浅析Open vSwitch数据结构:哈希表hmap/smap/shash


    概述

    OVS软件中,hmap提供了基础的哈希表存储结构,smap和shash基于hmap进行实现,其中smap支持存储字符串,而sshash则支持存储任意类型的数据。

    hmap

    OVS软件的hmap是基于分离链接法实现的,分离链接法使用链表解决散列冲突,其做法是散列值相同的元素都保存到一个链表中。当查询的时候,首先根据散列值找到元素所在的链表,然后遍历链表查找对应的元素。
    在这里插入图片描述

    hmap数据结构

    struct hmap {
        struct hmap_node **buckets;     // 哈希数组,本质上是一个分离链表数组,当mask=0时,指向成员one的地址
        struct hmap_node *one;      // 仅在mask为0时使用
        size_t mask;    // 哈希桶的大小
        size_t n;   // 哈希表中存储的hmap_node节点数量
    };
    
    // 哈希节点
    struct hmap_node {
        size_t hash;    // 哈希值
        struct hmap_node *next;    // 单向链表,所有哈希值相同的节点会链接到一个链表中
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    初始化hmap

    void hmap_init(struct hmap *hmap)
    {
        hmap->buckets = &hmap->one;
        hmap->one = NULL;
        hmap->mask = 0;
        hmap->n = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    插入节点

    #define hmap_insert(HMAP, NODE, HASH) \
        hmap_insert_at(HMAP, NODE, HASH, OVS_SOURCE_LOCATOR)
        
    static inline void
    hmap_insert_at(struct hmap *hmap, struct hmap_node *node, size_t hash,
                   const char *where)
    {
        hmap_insert_fast(hmap, node, hash);     // 执行快速插入
        if (hmap->n / 2 > hmap->mask) {     // 当存储节点数量的一半大于mask时,需要对hmap进行扩容
            hmap_expand_at(hmap, where);
        }
    }
    
    static inline void
    hmap_insert_fast(struct hmap *hmap, struct hmap_node *node, size_t hash)
    {
        struct hmap_node **bucket = &hmap->buckets[hash & hmap->mask];      // 计算hash值,并根据hash值确定数组索引
        node->hash = hash;
        node->next = *bucket;
        *bucket = node;     // 新节点插入到链表头部
        hmap->n++;      // 更新hmap存储节点数量
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    扩展hmap空间

    void
    hmap_expand_at(struct hmap *hmap, const char *where)
    {
        size_t new_mask = calc_mask(hmap->n);       // 根据已存储节点的数量计算出新的mask值
        if (new_mask > hmap->mask) {
            COVERAGE_INC(hmap_expand);
            resize(hmap, new_mask, where);
        }
    }
    
    static size_t
    calc_mask(size_t capacity)
    {
        size_t mask = capacity / 2;
        mask |= mask >> 1;
        mask |= mask >> 2;
        mask |= mask >> 4;
        mask |= mask >> 8;
        mask |= mask >> 16;
    #if SIZE_MAX > UINT32_MAX
        mask |= mask >> 32;
    #endif
    
        mask |= (mask & 1) << 1;
    
        return mask;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    resize函数
    static void
    resize(struct hmap *hmap, size_t new_mask, const char *where)
    {
        struct hmap tmp;
        size_t i;
    
        ovs_assert(is_pow2(new_mask + 1));
    
        hmap_init(&tmp);        // 初始化临时的hmap
        if (new_mask) {
            tmp.buckets = xmalloc(sizeof *tmp.buckets * (new_mask + 1));    // 根据new_mask创建哈希数组
            tmp.mask = new_mask;
            for (i = 0; i <= tmp.mask; i++) {
                tmp.buckets[i] = NULL;
            }
        }
        int n_big_buckets = 0;
        int biggest_count = 0;
        int n_biggest_buckets = 0;
        for (i = 0; i <= hmap->mask; i++) {
            struct hmap_node *node, *next;
            int count = 0;
            for (node = hmap->buckets[i]; node; node = next) {
                next = node->next;
                hmap_insert_fast(&tmp, node, node->hash);       // 将原hmap的元素插入到临时hmap中
                count++;
            }
        }
        hmap_swap(hmap, &tmp);  // 交换两个hmap的结构,此时hmap的数据与原tmp是相同的
        hmap_destroy(&tmp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    删除节点

    static inline void
    hmap_remove(struct hmap *hmap, struct hmap_node *node)
    {
        struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
        while (*bucket != node) {
            bucket = &(*bucket)->next;
        }
        *bucket = node->next;
        hmap->n--;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    遍历所有节点

    // 普通版本的遍历,不支持删除节点
    #define HMAP_FOR_EACH(NODE, MEMBER, HMAP) \
        HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, (void) 0)
    
    #define HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, ...)                     \
        for (INIT_CONTAINER(NODE, hmap_first(HMAP), MEMBER), __VA_ARGS__;   \
             (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER))                \
             || ((NODE = NULL), false);                                     \
             ASSIGN_CONTAINER(NODE, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER))
    
    // 安全版本的遍历,支持删除节点
    #define HMAP_FOR_EACH_SAFE(NODE, NEXT, MEMBER, HMAP) \
        HMAP_FOR_EACH_SAFE_INIT(NODE, NEXT, MEMBER, HMAP, (void) 0)
    
    #define HMAP_FOR_EACH_SAFE_INIT(NODE, NEXT, MEMBER, HMAP, ...)          \
        for (INIT_CONTAINER(NODE, hmap_first(HMAP), MEMBER), __VA_ARGS__;   \
             ((NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER))               \
              || ((NODE = NULL), false)                                     \
              ? INIT_CONTAINER(NEXT, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER), 1 \
              : 0);                                                         \
             (NODE) = (NEXT))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    辅助函数hmap_first
    static inline struct hmap_node *
    hmap_next__(const struct hmap *hmap, size_t start)
    {
        size_t i;
        for (i = start; i <= hmap->mask; i++) {
            struct hmap_node *node = hmap->buckets[i];
            if (node) {
                return node;
            }
        }
        return NULL;
    }
    
    static inline struct hmap_node *
    hmap_first(const struct hmap *hmap)
    {
        return hmap_next__(hmap, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    辅助函数hmap_next
    static inline struct hmap_node *
    hmap_next(const struct hmap *hmap, const struct hmap_node *node)
    {
        return (node->next
                ? node->next        // 下一节点非空,返回下一节点
                : hmap_next__(hmap, (node->hash & hmap->mask) + 1));    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    smap

    smap基于hmap实现,是一个专用于存储key-value哈希表,其中key和value类型都是字符串。

    smap数据结构

    struct smap {
        struct hmap map; 
    };
    
    struct smap_node {
        struct hmap_node node;  
        char *key;
        char *value;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    插入节点

    struct smap_node *
    smap_add(struct smap *smap, const char *key, const char *value)
    {
        size_t key_len = strlen(key);
        return smap_add__(smap, xmemdup0(key, key_len), xstrdup(value),
                          hash_bytes(key, key_len, 0));     // 为key和value申请内存空间
    }
    
    static struct smap_node *
    smap_add__(struct smap *smap, char *key, void *value, size_t hash)
    {
        struct smap_node *node = xmalloc(sizeof *node);
        node->key = key;
        node->value = value;
        hmap_insert(&smap->map, &node->node, hash);     // 调用hmap接口插入节点
        return node;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    删除节点

    void
    smap_remove_node(struct smap *smap, struct smap_node *node)
    {
        hmap_remove(&smap->map, &node->node);
        free(node->key);
        free(node->value);
        free(node);
    }
    
    void
    smap_remove(struct smap *smap, const char *key)
    {
        struct smap_node *node = smap_get_node(smap, key);
    
        if (node) {
            smap_remove_node(smap, node);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    查找节点

    struct smap_node *
    smap_get_node(const struct smap *smap, const char *key)
    {
        size_t key_len = strlen(key);
        return smap_find__(smap, key, key_len, hash_bytes(key, key_len, 0));
    }
    
    static struct smap_node *
    smap_find__(const struct smap *smap, const char *key, size_t key_len,
                size_t hash)
    {
        struct smap_node *node;
    
        HMAP_FOR_EACH_WITH_HASH (node, node, hash, &smap->map) {
            if (!strncmp(node->key, key, key_len) && !node->key[key_len]) {
                return node;
            }
        }
    
        return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    遍历所有节点

    // 普通版本的遍历,不支持删除,调用hmap的接口
    #define SMAP_FOR_EACH(SMAP_NODE, SMAP)                                  \
        HMAP_FOR_EACH_INIT (SMAP_NODE, node, &(SMAP)->map,                  \
                            BUILD_ASSERT_TYPE(SMAP_NODE, struct smap_node *), \
                            BUILD_ASSERT_TYPE(SMAP, struct smap *))
    
    // 安全版本的遍历,支持删除,调用hmap的接口
    #define SMAP_FOR_EACH_SAFE(SMAP_NODE, NEXT, SMAP)           \
        HMAP_FOR_EACH_SAFE_INIT (                               \
            SMAP_NODE, NEXT, node, &(SMAP)->map,                \
            BUILD_ASSERT_TYPE(SMAP_NODE, struct smap_node *),   \
            BUILD_ASSERT_TYPE(NEXT, struct smap_node *),        \
            BUILD_ASSERT_TYPE(SMAP, struct smap *))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    shash

    shash也是基于hmap进行扩展,支持存储任意类型的数据。

    shash数据结构

    struct shash_node {
        struct hmap_node node;
        char *name;
        void *data;
    };
    
    struct shash {
        struct hmap map;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    插入节点

    static struct shash_node *
    shash_add_nocopy__(struct shash *sh, char *name, const void *data, size_t hash)
    {
        struct shash_node *node = xmalloc(sizeof *node);
        node->name = name;
        node->data = CONST_CAST(void *, data);      // 去除const属性进行存储
        hmap_insert(&sh->map, &node->node, hash);
        return node;
    }
    
    struct shash_node *
    shash_add_nocopy(struct shash *sh, char *name, const void *data)
    {
        return shash_add_nocopy__(sh, name, data, hash_name(name));
    }
    
    struct shash_node *
    shash_add(struct shash *sh, const char *name, const void *data)
    {
        return shash_add_nocopy(sh, xstrdup(name), data);       // 为key申请内存空间
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    删除节点

    void
    shash_delete(struct shash *sh, struct shash_node *node)
    {
        free(shash_steal(sh, node));
    }
    
    char *
    shash_steal(struct shash *sh, struct shash_node *node)
    {
        char *name = node->name;
    
        hmap_remove(&sh->map, &node->node);
        free(node);
        return name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    查找节点

    static struct shash_node *
    shash_find__(const struct shash *sh, const char *name, size_t name_len,
                 size_t hash)
    {
        struct shash_node *node;
    
        HMAP_FOR_EACH_WITH_HASH (node, node, hash, &sh->map) {
            if (!strncmp(node->name, name, name_len) && !node->name[name_len]) {
                return node;
            }
        }
        return NULL;
    }
    
    struct shash_node *
    shash_find(const struct shash *sh, const char *name)
    {
        return shash_find__(sh, name, strlen(name), hash_name(name));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    遍历所有节点

    #define SHASH_FOR_EACH(SHASH_NODE, SHASH)                               \
        HMAP_FOR_EACH_INIT (SHASH_NODE, node, &(SHASH)->map,                \
                            BUILD_ASSERT_TYPE(SHASH_NODE, struct shash_node *), \
                            BUILD_ASSERT_TYPE(SHASH, struct shash *))
    
    #define SHASH_FOR_EACH_SAFE(SHASH_NODE, NEXT, SHASH)        \
        HMAP_FOR_EACH_SAFE_INIT (                               \
            SHASH_NODE, NEXT, node, &(SHASH)->map,              \
            BUILD_ASSERT_TYPE(SHASH_NODE, struct shash_node *), \
            BUILD_ASSERT_TYPE(NEXT, struct shash_node *),       \
            BUILD_ASSERT_TYPE(SHASH, struct shash *))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    SpringMVC基础入门及工作流程---全方面详细介绍
    Celery笔记八之数据库操作定时任务
    PHP基于thinkphp的教材管理系统#毕业设计
    Ubuntu18.04安装onnxruntime(c++版)与CUDA运行Demo
    uniapp打包安卓apk的隐私政策配置
    redis过期key的清理/删除策略
    【react】点击空白处隐藏
    创建简单的 Docker 数据科学映像
    【气动学】基于Matlab模拟各类导弹跟踪
    Bash中的变量引用:单引号和双引号的差异
  • 原文地址:https://blog.csdn.net/anyegongjuezjd/article/details/132819009