You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Linux内核中使用Map实现键值存储?求示例与保护机制

内核态基于RCU保护的哈希表键值存储实现示例

嘿,刚好做过类似的内核模块开发,给你整理一个实用的示例,同时解答你关于RCU保护机制的疑问~

一、基础结构定义

首先我们需要定义键值对的结构体,以及哈希表本身。内核中常用hlist配合RCU来实现高效的哈希表,同时用自旋锁保护写操作:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/hash.h>
#include <linux/rculist.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>

// 定义键值对结构体,用hlist_bl_node做哈希节点(支持RCU)
struct kv_entry {
    char key[64];          // 用户态传入的键
    int value;             // 存储的值
    struct hlist_bl_node node;  // 哈希节点,用于RCU保护
};

// 定义哈希表和自旋锁(保护写操作)
#define KV_HASH_BITS 10    // 哈希表大小为2^10=1024
static DEFINE_HASHTABLE(kv_hash_table, KV_HASH_BITS);
static DEFINE_SPINLOCK(kv_hash_lock);

二、核心操作实现

1. 添加/更新键值对(带RCU保护)

当需要添加或更新值时,我们需要先加自旋锁,然后查找是否存在对应键,存在则更新,不存在则分配新节点并通过hash_add_rcu加入哈希表:

static int kv_set(const char __user *user_key, int value) {
    char key[64];
    struct kv_entry *entry;
    unsigned int hash;

    // 从用户态拷贝键,检查合法性
    if (copy_from_user(key, user_key, sizeof(key)))
        return -EFAULT;
    key[sizeof(key)-1] = '\0';  // 确保字符串终止

    hash = hash_str(key, KV_HASH_BITS);

    // 加自旋锁保护写操作
    spin_lock(&kv_hash_lock);

    // 查找是否已存在该键
    hash_for_each_possible(kv_hash_table, entry, node, hash) {
        if (!strcmp(entry->key, key)) {
            entry->value = value;  // 更新值
            spin_unlock(&kv_hash_lock);
            return 0;
        }
    }

    // 不存在则分配新节点
    entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
    if (!entry) {
        spin_unlock(&kv_hash_lock);
        return -ENOMEM;
    }
    strscpy(entry->key, key, sizeof(entry->key));
    entry->value = value;

    // 用hash_add_rcu将节点加入哈希表,支持RCU读
    hash_add_rcu(kv_hash_table, &entry->node, hash);

    spin_unlock(&kv_hash_lock);
    return 0;
}

2. 读取键值对(带RCU保护)

读操作不需要加锁,只需要在RCU读临界区内操作,用hash_for_each_possible_rcu遍历查找:

static int kv_get(const char __user *user_key, int __user *user_value) {
    char key[64];
    struct kv_entry *entry;
    unsigned int hash;
    int ret = -ENOENT;

    if (copy_from_user(key, user_key, sizeof(key)))
        return -EFAULT;
    key[sizeof(key)-1] = '\0';

    hash = hash_str(key, KV_HASH_BITS);

    // 进入RCU读临界区,读操作无锁
    rcu_read_lock();
    hash_for_each_possible_rcu(kv_hash_table, entry, node, hash) {
        if (!strcmp(entry->key, key)) {
            if (copy_to_user(user_value, &entry->value, sizeof(int))) {
                ret = -EFAULT;
            } else {
                ret = 0;
            }
            break;
        }
    }
    rcu_read_unlock();

    return ret;
}

3. 删除键值对(带RCU保护)

删除操作需要先加自旋锁,用hash_del_rcu移除节点,然后通过call_rcu延迟释放内存(避免读操作访问已释放的内存):

static void kv_entry_free(struct rcu_head *head) {
    struct kv_entry *entry = container_of(head, struct kv_entry, node.rcu);
    kfree(entry);
}

static int kv_del(const char __user *user_key) {
    char key[64];
    struct kv_entry *entry;
    unsigned int hash;
    int ret = -ENOENT;

    if (copy_from_user(key, user_key, sizeof(key)))
        return -EFAULT;
    key[sizeof(key)-1] = '\0';

    hash = hash_str(key, KV_HASH_BITS);

    spin_lock(&kv_hash_lock);
    hash_for_each_possible(kv_hash_table, entry, node, hash) {
        if (!strcmp(entry->key, key)) {
            // 用hash_del_rcu移除节点,RCU读仍可访问直到RCU grace period结束
            hash_del_rcu(&entry->node);
            // 延迟释放节点,保证读操作完成后再释放
            call_rcu(&entry->node.rcu, kv_entry_free);
            ret = 0;
            break;
        }
    }
    spin_unlock(&kv_hash_lock);

    return ret;
}

三、RCU相关保护机制说明

你提到的hash_add_rcu确实是内核提供的RCU保护哈希操作,配套的还有这些关键函数:

  • hash_add_rcu:将节点添加到哈希表,允许RCU读操作并发访问
  • hash_del_rcu:从哈希表移除节点,节点内存需要通过call_rcu延迟释放
  • hash_for_each_possible_rcu:在RCU读临界区内遍历哈希表的可能节点,无锁高效
  • rcu_read_lock()/rcu_read_unlock():包裹读操作,进入/退出RCU读临界区

这些机制的核心是读操作无锁,写操作加自旋锁(或其他互斥锁),内存延迟释放,非常适合你这种用户态发起读写、内核态处理的场景(通常读操作远多于写操作)。

四、用户态交互示例

可以通过ioctl来实现用户态和内核态的交互,比如定义三个命令:SET、GET、DEL,这里给出简化的ioctl处理逻辑:

#define KV_SET _IOW('k', 1, struct kv_ioctl_arg)
#define KV_GET _IOR('k', 2, struct kv_ioctl_arg)
#define KV_DEL _IOW('k', 3, struct kv_ioctl_arg)

struct kv_ioctl_arg {
    char key[64];
    int value;
};

static long kv_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    struct kv_ioctl_arg arg_buf;

    if (copy_from_user(&arg_buf, (void __user *)arg, sizeof(arg_buf)))
        return -EFAULT;

    switch (cmd) {
        case KV_SET:
            return kv_set(arg_buf.key, arg_buf.value);
        case KV_GET: {
            int ret = kv_get(arg_buf.key, &arg_buf.value);
            if (!ret && copy_to_user((void __user *)arg, &arg_buf, sizeof(arg_buf)))
                return -EFAULT;
            return ret;
        }
        case KV_DEL:
            return kv_del(arg_buf.key);
        default:
            return -ENOTTY;
    }
}

// 这里还需要实现file_operations结构体、模块初始化/退出函数,篇幅原因就不展开了

最后要注意模块开发的规范,比如内存泄漏检查、锁的正确使用、用户空间指针的安全处理等等。

内容的提问来源于stack exchange,提问作者SudipM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:32:46